def generate_flat_topic_tree(node_cache=None, lang_code=settings.LANGUAGE_CODE, alldata=False): with i18n.translate_block(lang_code): categories = node_cache or get_node_cache(language=i18n.lcode_to_django_lang(lang_code)) result = dict() # make sure that we only get the slug of child of a topic # to avoid redundancy for category_name, category in categories.iteritems(): result[category_name] = {} for node_name, node in category.iteritems(): if alldata: relevant_data = node else: relevant_data = { 'title': _(node['title']), 'path': node['path'], 'kind': node['kind'], 'available': node.get('available', True), 'keywords': node.get('keywords', []), } result[category_name][node_name] = relevant_data return result
def tabular_view(request, report_type="exercise"): """Tabular view also gets data server-side.""" # important for setting the defaults for the coach nav bar language = lcode_to_django_lang(request.language) facility, group_id, context = coach_nav_context(request, "tabular") # Define how students are ordered--used to be as efficient as possible. student_ordering = ["last_name", "first_name", "username"] # Get a list of topics (sorted) and groups topics = [get_node_cache("Topic", language=language).get(tid["id"]) for tid in get_knowledgemap_topics(language=language) if report_type.title() in tid["contains"]] playlists = Playlist.all() (groups, facilities, ungrouped_available) = get_accessible_objects_from_logged_in_user(request, facility=facility) context.update(plotting_metadata_context(request, facility=facility)) context.update({ # For translators: the following two translations are nouns "report_types": ({"value": "exercise", "name":_("exercise")}, {"value": "video", "name": _("video")}), "request_report_type": report_type, "topics": [{"id": t["id"], "title": t["title"]} for t in topics if t], "playlists": [{"id": p.id, "title": p.title, "tag": p.tag} for p in playlists if p], }) # get querystring info topic_id = request.GET.get("topic", "") playlist_id = request.GET.get("playlist", "") # No valid data; just show generic # Exactly one of topic_id or playlist_id should be present if not ((topic_id or playlist_id) and not (topic_id and playlist_id)): if playlists: messages.add_message(request, WARNING, _("Please select a playlist.")) elif topics: messages.add_message(request, WARNING, _("Please select a topic.")) return context playlist = (filter(lambda p: p.id == playlist_id, Playlist.all()) or [None])[0] if group_id: # Narrow by group if group_id == control_panel_api_resources.UNGROUPED_KEY: users = FacilityUser.objects.filter(group__isnull=True, is_teacher=False) if facility: # filter only those ungrouped students for the facility users = users.filter(facility=facility) users = users.order_by(*student_ordering) else: # filter all ungroup students users = FacilityUser.objects.filter(group__isnull=True, is_teacher=False).order_by(*student_ordering) else: users = FacilityUser.objects.filter( group=group_id, is_teacher=False).order_by(*student_ordering) elif facility: # Narrow by facility search_groups = [groups_dict["groups"] for groups_dict in groups if groups_dict["facility"] == facility.id] assert len(search_groups) <= 1, "Should only have one or zero matches." # Return groups and ungrouped search_groups = search_groups[0] # make sure to include ungrouped students users = FacilityUser.objects.filter( Q(group__in=search_groups) | Q(group=None, facility=facility), is_teacher=False).order_by(*student_ordering) else: # Show all (including ungrouped) search_groups = [] for groups_dict in groups: search_groups += groups_dict["groups"] users = FacilityUser.objects.filter( Q(group__in=search_groups) | Q(group=None), is_teacher=False).order_by(*student_ordering) # We have enough data to render over a group of students # Get type-specific information if report_type == "exercise": # Fill in exercises if topic_id: exercises = get_topic_exercises(topic_id=topic_id) elif playlist: exercises = playlist.get_playlist_entries("Exercise", language=language) context["exercises"] = exercises # More code, but much faster exercise_names = [ex["id"] for ex in context["exercises"]] # Get students context["students"] = [] exlogs = ExerciseLog.objects \ .filter(user__in=users, exercise_id__in=exercise_names) \ .order_by(*["user__%s" % field for field in student_ordering]) \ .values("user__id", "struggling", "complete", "exercise_id") exlogs = list(exlogs) # force the query to be evaluated exlog_idx = 0 for user in users: log_table = {} while exlog_idx < len(exlogs) and exlogs[exlog_idx]["user__id"] == user.id: log_table[exlogs[exlog_idx]["exercise_id"]] = exlogs[exlog_idx] exlog_idx += 1 context["students"].append({ # this could be DRYer "first_name": user.first_name, "last_name": user.last_name, "username": user.username, "name": user.get_name(), "id": user.id, "exercise_logs": log_table, }) elif report_type == "video": # Fill in videos if topic_id: context["videos"] = get_topic_videos(topic_id=topic_id) elif playlist: context["videos"] = playlist.get_playlist_entries("Video", language=language) # More code, but much faster video_ids = [vid["id"] for vid in context["videos"]] # Get students context["students"] = [] vidlogs = VideoLog.objects \ .filter(user__in=users, video_id__in=video_ids) \ .order_by(*["user__%s" % field for field in student_ordering])\ .values("user__id", "complete", "video_id", "total_seconds_watched", "points") vidlogs = list(vidlogs) # force the query to be executed now vidlog_idx = 0 for user in users: log_table = {} while vidlog_idx < len(vidlogs) and vidlogs[vidlog_idx]["user__id"] == user.id: log_table[vidlogs[vidlog_idx]["video_id"]] = vidlogs[vidlog_idx] vidlog_idx += 1 context["students"].append({ # this could be DRYer "first_name": user.first_name, "last_name": user.last_name, "username": user.username, "name": user.get_name(), "id": user.id, "video_logs": log_table, }) else: raise Http404(_("Unknown report_type: %(report_type)s") % {"report_type": report_type}) # Validate results by showing user messages. if not users: # 1. check group facility groups if len(groups) > 0 and not groups[0]['groups']: # 1. No groups available (for facility) and "no students" returned. messages.add_message(request, WARNING, _("No learner accounts have been created for selected facility/group.")) elif topic_id and playlist_id: # 2. Both topic and playlist are selected. messages.add_message(request, WARNING, _("Please select either a topic or a playlist above, but not both.")) elif not topic_id and not playlist_id: # 3. Group was selected, but data not queried because a topic or playlist was not selected. if playlists: # 4. No playlist was selected. messages.add_message(request, WARNING, _("Please select a playlist.")) elif topics: # 5. No topic was selected. messages.add_message(request, WARNING, _("Please select a topic.")) else: # 6. Everything specified, but no users fit the query. messages.add_message(request, WARNING, _("No learner accounts in this group have been created.")) # End: Validate results by showing user messages. log_coach_report_view(request) return context
def login(self, request, **kwargs): self.method_check(request, allowed=['post']) logout(request) data = self.deserialize(request, request.body, format=request.META.get( 'CONTENT_TYPE', 'application/json')) username = data.get('username', '') password = data.get('password', '') facility = data.get('facility', '') # first try logging in as a Django user if not settings.CENTRAL_SERVER: user = authenticate(username=username, password=password) if user: login(request, user) return self.create_response( request, { 'success': True, 'redirect': reverse("zone_redirect") }) # Find all matching users users = FacilityUser.objects.filter(username=username, facility=facility) if users.count() == 0: if Facility.objects.count() > 1: error_message = _( "Username was not found for this facility. Did you type your username correctly, and choose the right facility?" ) else: error_message = _( "Username was not found. Did you type your username correctly?" ) return self.create_response( request, { 'messages': { 'error': error_message }, 'error_highlight': "username" }, HttpUnauthorized) for user in users: if settings.SIMPLIFIED_LOGIN and not user.is_teacher: # For simplified login, as long as it is a student account just take the first one! break # if we find a user whose password matches, stop looking if user.check_password(password): break else: user = None if not user: return self.create_response( request, { 'messages': { 'error': _("Password was incorrect. Please try again.") }, # Specify which field to highlight as in error. 'error_highlight': "password" }, HttpUnauthorized) else: try: UserLog.begin_user_activity( user, activity_type="login", language=lcode_to_django_lang(request.language) ) # Success! Log the event (ignoring validation failures) except ValidationError as e: logging.error("Failed to begin_user_activity upon login: %s" % e) request.session["facility_user"] = user messages.success( request, _("You've been logged in! We hope you enjoy your time with KA Lite " ) + _("-- be sure to log out when you finish.")) extras = {'success': True} if user.is_teacher: extras.update({ "redirect": reverse("coach_reports", kwargs={ "zone_id": getattr(Device.get_own_device().get_zone(), "id", "None") }) }) return self.create_response(request, extras)
def tabular_view(request, report_type="exercise"): """Tabular view also gets data server-side.""" # important for setting the defaults for the coach nav bar language = lcode_to_django_lang(request.language) facility, group_id, context = coach_nav_context(request, "tabular") # Define how students are ordered--used to be as efficient as possible. student_ordering = ["last_name", "first_name", "username"] # Get a list of topics (sorted) and groups topics = [ get_node_cache("Topic", language=language).get(tid["id"]) for tid in get_knowledgemap_topics(language=language) if report_type.title() in tid["contains"] ] playlists = Playlist.all() (groups, facilities, ungrouped_available) = get_accessible_objects_from_logged_in_user( request, facility=facility) context.update(plotting_metadata_context(request, facility=facility)) context.update({ # For translators: the following two translations are nouns "report_types": ({ "value": "exercise", "name": _("exercise") }, { "value": "video", "name": _("video") }), "request_report_type": report_type, "topics": [{ "id": t["id"], "title": t["title"] } for t in topics if t], "playlists": [{ "id": p.id, "title": p.title, "tag": p.tag } for p in playlists if p], }) # get querystring info topic_id = request.GET.get("topic", "") playlist_id = request.GET.get("playlist", "") # No valid data; just show generic # Exactly one of topic_id or playlist_id should be present if not ((topic_id or playlist_id) and not (topic_id and playlist_id)): if playlists: messages.add_message(request, WARNING, _("Please select a playlist.")) elif topics: messages.add_message(request, WARNING, _("Please select a topic.")) return context playlist = (filter(lambda p: p.id == playlist_id, Playlist.all()) or [None])[0] if group_id: # Narrow by group if group_id == control_panel_api_resources.UNGROUPED_KEY: users = FacilityUser.objects.filter(group__isnull=True, is_teacher=False) if facility: # filter only those ungrouped students for the facility users = users.filter(facility=facility) users = users.order_by(*student_ordering) else: # filter all ungroup students users = FacilityUser.objects.filter( group__isnull=True, is_teacher=False).order_by(*student_ordering) else: users = FacilityUser.objects.filter( group=group_id, is_teacher=False).order_by(*student_ordering) elif facility: # Narrow by facility search_groups = [ groups_dict["groups"] for groups_dict in groups if groups_dict["facility"] == facility.id ] assert len(search_groups) <= 1, "Should only have one or zero matches." # Return groups and ungrouped search_groups = search_groups[ 0] # make sure to include ungrouped students users = FacilityUser.objects.filter( Q(group__in=search_groups) | Q(group=None, facility=facility), is_teacher=False).order_by(*student_ordering) else: # Show all (including ungrouped) search_groups = [] for groups_dict in groups: search_groups += groups_dict["groups"] users = FacilityUser.objects.filter( Q(group__in=search_groups) | Q(group=None), is_teacher=False).order_by(*student_ordering) # We have enough data to render over a group of students # Get type-specific information if report_type == "exercise": # Fill in exercises if topic_id: exercises = get_topic_exercises(topic_id=topic_id) elif playlist: exercises = playlist.get_playlist_entries("Exercise", language=language) context["exercises"] = exercises # More code, but much faster exercise_names = [ex["id"] for ex in context["exercises"]] # Get students context["students"] = [] exlogs = ExerciseLog.objects \ .filter(user__in=users, exercise_id__in=exercise_names) \ .order_by(*["user__%s" % field for field in student_ordering]) \ .values("user__id", "struggling", "complete", "exercise_id") exlogs = list(exlogs) # force the query to be evaluated exlog_idx = 0 for user in users: log_table = {} while exlog_idx < len( exlogs) and exlogs[exlog_idx]["user__id"] == user.id: log_table[exlogs[exlog_idx]["exercise_id"]] = exlogs[exlog_idx] exlog_idx += 1 context["students"].append({ # this could be DRYer "first_name": user.first_name, "last_name": user.last_name, "username": user.username, "name": user.get_name(), "id": user.id, "exercise_logs": log_table, }) elif report_type == "video": # Fill in videos if topic_id: context["videos"] = get_topic_videos(topic_id=topic_id) elif playlist: context["videos"] = playlist.get_playlist_entries( "Video", language=language) # More code, but much faster video_ids = [vid["id"] for vid in context["videos"]] # Get students context["students"] = [] vidlogs = VideoLog.objects \ .filter(user__in=users, video_id__in=video_ids) \ .order_by(*["user__%s" % field for field in student_ordering])\ .values("user__id", "complete", "video_id", "total_seconds_watched", "points") vidlogs = list(vidlogs) # force the query to be executed now vidlog_idx = 0 for user in users: log_table = {} while vidlog_idx < len( vidlogs) and vidlogs[vidlog_idx]["user__id"] == user.id: log_table[vidlogs[vidlog_idx] ["video_id"]] = vidlogs[vidlog_idx] vidlog_idx += 1 context["students"].append({ # this could be DRYer "first_name": user.first_name, "last_name": user.last_name, "username": user.username, "name": user.get_name(), "id": user.id, "video_logs": log_table, }) else: raise Http404( _("Unknown report_type: %(report_type)s") % {"report_type": report_type}) # Validate results by showing user messages. if not users: # 1. check group facility groups if len(groups) > 0 and not groups[0]['groups']: # 1. No groups available (for facility) and "no students" returned. messages.add_message( request, WARNING, _("No learner accounts have been created for selected facility/group." )) elif topic_id and playlist_id: # 2. Both topic and playlist are selected. messages.add_message( request, WARNING, _("Please select either a topic or a playlist above, but not both." )) elif not topic_id and not playlist_id: # 3. Group was selected, but data not queried because a topic or playlist was not selected. if playlists: # 4. No playlist was selected. messages.add_message(request, WARNING, _("Please select a playlist.")) elif topics: # 5. No topic was selected. messages.add_message(request, WARNING, _("Please select a topic.")) else: # 6. Everything specified, but no users fit the query. messages.add_message( request, WARNING, _("No learner accounts in this group have been created.")) # End: Validate results by showing user messages. log_coach_report_view(request) return context
def api_data(request, xaxis="", yaxis=""): """Request contains information about what data are requested (who, what, and how). Response should be a JSON object * data contains the data, structred by user and then datatype * the rest of the data is metadata, useful for displaying detailed info about data. """ language = lcode_to_django_lang(request.language) # Get the request form try: form = get_data_form(request, xaxis=xaxis, yaxis=yaxis) # (data=request.REQUEST) except Exception as e: # In investigating #1509: we can catch SQL errors here and communicate clearer error # messages with the user here. For now, we have no such error to catch, so just # pass the errors on to the user (via the @api_handle_error_with_json decorator). raise e # Query out the data: who? if form.data.get("user"): facility = [] groups = [] users = [get_object_or_404(FacilityUser, id=form.data.get("user"))] elif form.data.get("group"): facility = [] if form.data.get("group") == "Ungrouped": groups = [] users = FacilityUser.objects.filter( facility__in=[form.data.get("facility")], group__isnull=True, is_teacher=False).order_by("last_name", "first_name") else: groups = [ get_object_or_404(FacilityGroup, id=form.data.get("group")) ] users = FacilityUser.objects.filter(group=form.data.get("group"), is_teacher=False).order_by( "last_name", "first_name") elif form.data.get("facility"): facility = get_object_or_404(Facility, id=form.data.get("facility")) groups = FacilityGroup.objects.filter( facility__in=[form.data.get("facility")]) users = FacilityUser.objects.filter( facility__in=[form.data.get("facility")], is_teacher=False).order_by("last_name", "first_name") else: # Allow superuser to see the data. if request.user.is_authenticated() and request.user.is_superuser: facility = [] groups = [] users = FacilityUser.objects.all().order_by( "last_name", "first_name") else: return HttpResponseNotFound( _("Did not specify facility, group, nor user.")) # Query out the data: where? if not form.data.get("topic_path"): return HttpResponseNotFound(_("Must specify a topic path")) # Query out the data: what? computed_data = compute_data( data_types=[form.data.get("xaxis"), form.data.get("yaxis")], who=users, where=form.data.get("topic_path"), language=language) # Quickly add back in exercise meta-data (could potentially be used in future for other data too!) ex_nodes = get_node_cache(language=language)["Exercise"] exercises = [] for e in computed_data["exercises"]: exercises.append({ "slug": e, "full_name": ex_nodes[e]["display_name"], "url": ex_nodes[e]["path"], }) json_data = { "data": computed_data["data"], "exercises": exercises, "videos": computed_data["videos"], "users": dict( zip([u.id for u in users], [ "%s, %s" % (u.last_name, u.first_name) if u.last_name or u.first_name else u.username for u in users ])), "groups": dict( zip( [g.id for g in groups], dict(zip(["id", "name"], [(g.id, g.name) for g in groups])), )), "facility": None if not facility else { "name": facility.name, "id": facility.id, } } if "facility_user" in request.session: try: # Log a "begin" and end here user = request.session["facility_user"] UserLog.begin_user_activity(user, activity_type="coachreport") UserLog.update_user_activity( user, activity_type="login" ) # to track active login time for teachers UserLog.end_user_activity(user, activity_type="coachreport") except ValidationError as e: # Never report this error; don't want this logging to block other functionality. logging.error( "Failed to update Teacher userlog activity login: %s" % e) # Now we have data, stream it back with a handler for date-times return JsonResponse(json_data)
def login(self, request, **kwargs): self.method_check(request, allowed=['post']) logout(request) data = self.deserialize(request, request.body, format=request.META.get('CONTENT_TYPE', 'application/json')) username = data.get('username', '') password = data.get('password', '') facility = data.get('facility', '') # first try logging in as a Django user if not settings.CENTRAL_SERVER: user = authenticate(username=username, password=password) if user: login(request, user) return self.create_response(request, { 'success': True, 'redirect': reverse("zone_redirect") }) # Find all matching users users = FacilityUser.objects.filter(username=username, facility=facility) if users.count() == 0: if Facility.objects.count() > 1: error_message = _("Username was not found for this facility. Did you type your username correctly, and choose the right facility?") else: error_message = _("Username was not found. Did you type your username correctly?") return self.create_response(request, { 'messages': {'error': error_message}, 'error_highlight': "username" }, HttpUnauthorized ) for user in users: if settings.SIMPLIFIED_LOGIN and not user.is_teacher: # For simplified login, as long as it is a student account just take the first one! break # if we find a user whose password matches, stop looking if user.check_password(password): break else: user = None if not user: return self.create_response(request, { 'messages': {'error': _("Password was incorrect. Please try again.")}, # Specify which field to highlight as in error. 'error_highlight': "password" }, HttpUnauthorized ) else: try: UserLog.begin_user_activity(user, activity_type="login", language=lcode_to_django_lang(request.language)) # Success! Log the event (ignoring validation failures) except ValidationError as e: logging.error("Failed to begin_user_activity upon login: %s" % e) request.session["facility_user"] = user messages.success(request, _("You've been logged in! We hope you enjoy your time with KA Lite ") + _("-- be sure to log out when you finish.")) extras = {'success': True} if user.is_teacher: extras.update({ "redirect": reverse("coach_reports") }) return self.create_response(request, extras)
def api_data(request, xaxis="", yaxis=""): """Request contains information about what data are requested (who, what, and how). Response should be a JSON object * data contains the data, structred by user and then datatype * the rest of the data is metadata, useful for displaying detailed info about data. """ language = lcode_to_django_lang(request.language) # Get the request form try: form = get_data_form(request, xaxis=xaxis, yaxis=yaxis) # (data=request.REQUEST) except Exception as e: # In investigating #1509: we can catch SQL errors here and communicate clearer error # messages with the user here. For now, we have no such error to catch, so just # pass the errors on to the user (via the @api_handle_error_with_json decorator). raise e # Query out the data: who? if form.data.get("user"): facility = [] groups = [] users = [get_object_or_404(FacilityUser, id=form.data.get("user"))] elif form.data.get("group"): facility = [] if form.data.get("group") == "Ungrouped": groups = [] users = FacilityUser.objects.filter(facility__in=[form.data.get("facility")], group__isnull=True, is_teacher=False).order_by("last_name", "first_name") else: groups = [get_object_or_404(FacilityGroup, id=form.data.get("group"))] users = FacilityUser.objects.filter(group=form.data.get("group"), is_teacher=False).order_by("last_name", "first_name") elif form.data.get("facility"): facility = get_object_or_404(Facility, id=form.data.get("facility")) groups = FacilityGroup.objects.filter(facility__in=[form.data.get("facility")]) users = FacilityUser.objects.filter(facility__in=[form.data.get("facility")], is_teacher=False).order_by("last_name", "first_name") else: # Allow superuser to see the data. if request.user.is_authenticated() and request.user.is_superuser: facility = [] groups = [] users = FacilityUser.objects.all().order_by("last_name", "first_name") else: return HttpResponseNotFound(_("Did not specify facility, group, nor user.")) # Query out the data: where? if not form.data.get("topic_path"): return HttpResponseNotFound(_("Must specify a topic path")) # Query out the data: what? computed_data = compute_data(data_types=[form.data.get("xaxis"), form.data.get("yaxis")], who=users, where=form.data.get("topic_path"), language=language) # Quickly add back in exercise meta-data (could potentially be used in future for other data too!) ex_nodes = get_node_cache(language=language)["Exercise"] exercises = [] for e in computed_data["exercises"]: exercises.append({ "slug": e, "full_name": ex_nodes[e]["display_name"], "url": ex_nodes[e]["path"], }) json_data = { "data": computed_data["data"], "exercises": exercises, "videos": computed_data["videos"], "users": dict(zip([u.id for u in users], ["%s, %s" % (u.last_name, u.first_name) if u.last_name or u.first_name else u.username for u in users] )), "groups": dict(zip([g.id for g in groups], dict(zip(["id", "name"], [(g.id, g.name) for g in groups])), )), "facility": None if not facility else { "name": facility.name, "id": facility.id, } } if "facility_user" in request.session: try: # Log a "begin" and end here user = request.session["facility_user"] UserLog.begin_user_activity(user, activity_type="coachreport") UserLog.update_user_activity(user, activity_type="login") # to track active login time for teachers UserLog.end_user_activity(user, activity_type="coachreport") except ValidationError as e: # Never report this error; don't want this logging to block other functionality. logging.error("Failed to update Teacher userlog activity login: %s" % e) # Now we have data, stream it back with a handler for date-times return JsonResponse(json_data)
def login(request, facility): if request.user.is_authenticated(): return HttpResponseRedirect(reverse("homepage")) facility_id = (facility and facility.id) or None facilities = list(Facility.objects.all()) #Fix for #2047: prompt user to create an admin account if none exists if not User.objects.exists(): messages.warning(request, _("No administrator account detected. Please run 'kalite manage createsuperuser' from the terminal to create one.")) # Fix for #1211: refresh cached facility info when it's free and relevant refresh_session_facility_info(request, facility_count=len(facilities)) if request.method != 'POST': # render the unbound login form referer = urlparse.urlparse(request.META["HTTP_REFERER"]).path if request.META.get("HTTP_REFERER") else None # never use the homepage as the referer if referer in [reverse("homepage"), reverse("add_facility_student"), reverse("add_facility_teacher"), reverse("facility_user_signup")]: referer = None form = LoginForm(initial={"facility": facility_id, "callback_url": referer}) else: # process the login form # log out any Django user or facility user logout(request) username = request.POST.get("username", "") password = request.POST.get("password", "") # first try logging in as a Django user if not settings.CENTRAL_SERVER: user = authenticate(username=username, password=password) if user: auth_login(request, user) return HttpResponseRedirect(request.next or reverse("zone_redirect")) # try logging in as a facility user form = LoginForm(data=request.POST, request=request, initial={"facility": facility_id}) if not form.is_valid(): messages.error( request, _("There was an error logging you in. Please correct any errors listed below, and try again."), ) else: user = form.get_user() try: UserLog.begin_user_activity(user, activity_type="login", language=lcode_to_django_lang(request.language)) # Success! Log the event (ignoring validation failures) except ValidationError as e: logging.error("Failed to begin_user_activity upon login: %s" % e) request.session["facility_user"] = user messages.success(request, _("You've been logged in! We hope you enjoy your time with KA Lite ") + _("-- be sure to log out when you finish.")) # Send them back from whence they came (unless it's the sign up page) landing_page = form.cleaned_data["callback_url"] if form.cleaned_data["callback_url"] != reverse("facility_user_signup") else None if not landing_page or landing_page == reverse("login"): # Just going back to the homepage? We can do better than that. if form.get_user().is_teacher: landing_page = reverse("tabular_view") else: landing_page = reverse("learn") return HttpResponseRedirect(request.next or landing_page) return { "form": form, "facilities": facilities, }