def __init__(self, repo, course, flow_id, ordinal, participation, flow_session, request=None): FlowContext.__init__(self, repo, course, flow_id, participation, flow_session=flow_session) from course.content import adjust_flow_session_page_data adjust_flow_session_page_data(repo, flow_session, course.identifier, self.flow_desc) if ordinal >= flow_session.page_count: raise PageOrdinalOutOfRange() from course.models import FlowPageData page_data = self.page_data = get_object_or_404( FlowPageData, flow_session=flow_session, ordinal=ordinal) from course.content import get_flow_page_desc try: self.page_desc = get_flow_page_desc(flow_session, self.flow_desc, page_data.group_id, page_data.page_id) except ObjectDoesNotExist: self.page_desc = None self.page = None self.page_context = None else: self.page = instantiate_flow_page_with_ctx(self, page_data) page_uri = None if request is not None: from django.core.urlresolvers import reverse page_uri = request.build_absolute_uri( reverse("relate-view_flow_page", args=(course.identifier, flow_session.id, ordinal))) from course.page import PageContext self.page_context = PageContext(course=self.course, repo=self.repo, commit_sha=self.course_commit_sha, flow_session=flow_session, page_uri=page_uri) self._prev_answer_visit = False
def __init__(self, repo, course, flow_id, ordinal, participation, flow_session): FlowContext.__init__(self, repo, course, flow_id, participation, flow_session=flow_session) from course.content import adjust_flow_session_page_data adjust_flow_session_page_data(repo, flow_session, course.identifier, self.flow_desc, self.course_commit_sha) if ordinal >= flow_session.page_count: raise PageOrdinalOutOfRange() from course.models import FlowPageData page_data = self.page_data = get_object_or_404( FlowPageData, flow_session=flow_session, ordinal=ordinal) from course.content import get_flow_page_desc try: self.page_desc = get_flow_page_desc(flow_session, self.flow_desc, page_data.group_id, page_data.page_id) except ObjectDoesNotExist: self.page_desc = None self.page = None self.page_context = None else: self.page = instantiate_flow_page_with_ctx(self, page_data) from course.page import PageContext self.page_context = PageContext(course=self.course, repo=self.repo, commit_sha=self.course_commit_sha, flow_session=flow_session) self._prev_answer_visit = False
def page_analytics(pctx, flow_id, group_id, page_id): if pctx.role not in [ participation_role.teaching_assistant, participation_role.instructor, participation_role.observer, ]: raise PermissionDenied(_("must be at least TA to view analytics")) flow_desc = get_flow_desc(pctx.repo, pctx.course, flow_id, pctx.course_commit_sha) restrict_to_first_attempt = int( bool(pctx.request.GET.get("restrict_to_first_attempt") == "1")) is_multiple_submit = is_flow_multiple_submit(flow_desc) page_cache = PageInstanceCache(pctx.repo, pctx.course, flow_id) visits = (FlowPageVisit.objects .filter( flow_session__course=pctx.course, flow_session__flow_id=flow_id, page_data__group_id=group_id, page_data__page_id=page_id, is_submitted_answer=True, )) if connection.features.can_distinct_on_fields: if restrict_to_first_attempt: visits = (visits .distinct("flow_session__participation__id") .order_by("flow_session__participation__id", "visit_time")) elif is_multiple_submit: visits = (visits .distinct("page_data__id") .order_by("page_data__id", "-visit_time")) visits = (visits .select_related("flow_session") .select_related("page_data")) normalized_answer_and_correctness_to_count = {} title = None body = None total_count = 0 graded_count = 0 for visit in visits: page = page_cache.get_page(group_id, page_id, pctx.course_commit_sha) from course.page import PageContext grading_page_context = PageContext( course=pctx.course, repo=pctx.repo, commit_sha=pctx.course_commit_sha, flow_session=visit.flow_session) title = page.title(grading_page_context, visit.page_data.data) body = page.body(grading_page_context, visit.page_data.data) normalized_answer = page.normalized_answer( grading_page_context, visit.page_data.data, visit.answer) answer_feedback = visit.get_most_recent_feedback() if answer_feedback is not None: key = (normalized_answer, answer_feedback.correctness) normalized_answer_and_correctness_to_count[key] = \ normalized_answer_and_correctness_to_count.get(key, 0) + 1 graded_count += 1 else: key = (normalized_answer, None) normalized_answer_and_correctness_to_count[key] = \ normalized_answer_and_correctness_to_count.get(key, 0) + 1 total_count += 1 answer_stats = [] for (normalized_answer, correctness), count in \ normalized_answer_and_correctness_to_count.iteritems(): answer_stats.append( AnswerStats( normalized_answer=normalized_answer, correctness=correctness, count=count, percentage=safe_div(100 * count, total_count))) answer_stats = sorted( answer_stats, key=lambda astats: astats.percentage, reverse=True) return render_course_page(pctx, "course/analytics-page.html", { "flow_identifier": flow_id, "group_id": group_id, "page_id": page_id, "title": title, "body": body, "answer_stats_list": answer_stats, "restrict_to_first_attempt": restrict_to_first_attempt, })
def make_page_answer_stats_list(pctx, flow_id, restrict_to_first_attempt): flow_desc = get_flow_desc(pctx.repo, pctx.course, flow_id, pctx.course_commit_sha) is_multiple_submit = is_flow_multiple_submit(flow_desc) page_cache = PageInstanceCache(pctx.repo, pctx.course, flow_id) page_info_list = [] for group_desc in flow_desc.groups: for page_desc in group_desc.pages: points = 0 graded_count = 0 answer_count = 0 total_count = 0 visits = (FlowPageVisit.objects .filter( flow_session__course=pctx.course, flow_session__flow_id=flow_id, page_data__group_id=group_desc.id, page_data__page_id=page_desc.id, is_submitted_answer=True, )) if connection.features.can_distinct_on_fields: if restrict_to_first_attempt: visits = (visits .distinct("flow_session__participation__id") .order_by("flow_session__participation__id", "visit_time")) elif is_multiple_submit: visits = (visits .distinct("page_data__id") .order_by("page_data__id", "-visit_time")) visits = (visits .select_related("flow_session") .select_related("page_data")) answer_expected = False title = None for visit in visits: page = page_cache.get_page(group_desc.id, page_desc.id, pctx.course_commit_sha) answer_expected = answer_expected or page.expects_answer() from course.page import PageContext grading_page_context = PageContext( course=pctx.course, repo=pctx.repo, commit_sha=pctx.course_commit_sha, flow_session=visit.flow_session) title = page.title(grading_page_context, visit.page_data.data) answer_feedback = visit.get_most_recent_feedback() if visit.answer is not None: answer_count += 1 total_count += 1 if (answer_feedback is not None and answer_feedback.correctness is not None): if visit.answer is None: assert answer_feedback.correctness == 0 else: points += answer_feedback.correctness graded_count += 1 if not answer_expected: continue page_info_list.append( PageAnswerStats( group_id=group_desc.id, page_id=page_desc.id, title=title, average_correctness=safe_div(points, graded_count), average_emptiness=safe_div( graded_count - answer_count, graded_count), answer_count=answer_count, total_count=total_count, url=reverse( "relate-page_analytics", args=( pctx.course_identifier, flow_id, group_desc.id, page_desc.id, )))) return page_info_list
def view_page_sandbox(pctx): # type: (CoursePageContext) -> http.HttpResponse if not pctx.has_permission(pperm.use_page_sandbox): raise PermissionDenied() from course.validation import ValidationError from relate.utils import dict_to_struct, Struct import yaml PAGE_SESSION_KEY = make_sandbox_session_key( # noqa PAGE_SESSION_KEY_PREFIX, pctx.course.identifier) ANSWER_DATA_SESSION_KEY = make_sandbox_session_key( # noqa ANSWER_DATA_SESSION_KEY_PREFIX, pctx.course.identifier) PAGE_DATA_SESSION_KEY = make_sandbox_session_key( # noqa PAGE_DATA_SESSION_KEY_PREFIX, pctx.course.identifier) request = pctx.request page_source = pctx.request.session.get(PAGE_SESSION_KEY) page_errors = None page_warnings = None is_clear_post = (request.method == "POST" and "clear" in request.POST) is_clear_response_post = (request.method == "POST" and "clear_response" in request.POST) is_preview_post = (request.method == "POST" and "preview" in request.POST) def make_form(data=None): # type: (Optional[Text]) -> PageSandboxForm return PageSandboxForm(page_source, "yaml", request.user.editor_mode, ugettext("Enter YAML markup for a flow page."), data) if is_preview_post: edit_form = make_form(pctx.request.POST) new_page_source = None if edit_form.is_valid(): try: from pytools.py_codegen import remove_common_indentation new_page_source = remove_common_indentation( edit_form.cleaned_data["content"], require_leading_newline=False) from course.content import expand_yaml_macros new_page_source = expand_yaml_macros(pctx.repo, pctx.course_commit_sha, new_page_source) yaml_data = yaml.load(new_page_source) # type: ignore page_desc = dict_to_struct(yaml_data) if not isinstance(page_desc, Struct): raise ValidationError( "Provided page source code is not " "a dictionary. Do you need to remove a leading " "list marker ('-') or some stray indentation?") from course.validation import validate_flow_page, ValidationContext vctx = ValidationContext(repo=pctx.repo, commit_sha=pctx.course_commit_sha) validate_flow_page(vctx, "sandbox", page_desc) page_warnings = vctx.warnings except Exception: import sys tp, e, _ = sys.exc_info() page_errors = (ugettext("Page failed to load/validate") + ": " + "%(err_type)s: %(err_str)s" % { "err_type": tp.__name__, "err_str": e }) # type: ignore else: # Yay, it did validate. request.session[ PAGE_SESSION_KEY] = page_source = new_page_source del new_page_source edit_form = make_form(pctx.request.POST) elif is_clear_post: page_source = None pctx.request.session[PAGE_DATA_SESSION_KEY] = None pctx.request.session[ANSWER_DATA_SESSION_KEY] = None del pctx.request.session[PAGE_DATA_SESSION_KEY] del pctx.request.session[ANSWER_DATA_SESSION_KEY] edit_form = make_form() elif is_clear_response_post: page_source = None pctx.request.session[PAGE_DATA_SESSION_KEY] = None pctx.request.session[ANSWER_DATA_SESSION_KEY] = None del pctx.request.session[PAGE_DATA_SESSION_KEY] del pctx.request.session[ANSWER_DATA_SESSION_KEY] edit_form = make_form(pctx.request.POST) else: edit_form = make_form() have_valid_page = page_source is not None if have_valid_page: yaml_data = yaml.load(page_source) # type: ignore page_desc = cast(FlowPageDesc, dict_to_struct(yaml_data)) from course.content import instantiate_flow_page try: page = instantiate_flow_page("sandbox", pctx.repo, page_desc, pctx.course_commit_sha) except Exception: import sys tp, e, _ = sys.exc_info() page_errors = (ugettext("Page failed to load/validate") + ": " + "%(err_type)s: %(err_str)s" % { "err_type": tp.__name__, "err_str": e }) # type: ignore have_valid_page = False if have_valid_page: page_desc = cast(FlowPageDesc, page_desc) # Try to recover page_data, answer_data page_data = get_sandbox_data_for_page(pctx, page_desc, PAGE_DATA_SESSION_KEY) answer_data = get_sandbox_data_for_page(pctx, page_desc, ANSWER_DATA_SESSION_KEY) from course.models import FlowSession from course.page import PageContext page_context = PageContext( course=pctx.course, repo=pctx.repo, commit_sha=pctx.course_commit_sha, # This helps code pages retrieve the editor pref. flow_session=FlowSession(course=pctx.course, participation=pctx.participation), in_sandbox=True) if page_data is None: page_data = page.initialize_page_data(page_context) pctx.request.session[PAGE_DATA_SESSION_KEY] = (page_desc.type, page_desc.id, page_data) title = page.title(page_context, page_data) body = page.body(page_context, page_data) feedback = None page_form_html = None if page.expects_answer(): from course.page.base import PageBehavior page_behavior = PageBehavior(show_correctness=True, show_answer=True, may_change_answer=True) if request.method == "POST" and not is_preview_post: page_form = page.process_form_post(page_context, page_data, request.POST, request.FILES, page_behavior) if page_form.is_valid(): answer_data = page.answer_data(page_context, page_data, page_form, request.FILES) feedback = page.grade(page_context, page_data, answer_data, grade_data=None) pctx.request.session[ANSWER_DATA_SESSION_KEY] = ( page_desc.type, page_desc.id, answer_data) else: try: page_form = page.make_form(page_context, page_data, answer_data, page_behavior) except Exception: import sys tp, e, _ = sys.exc_info() page_errors = ( ugettext("Page failed to load/validate " "(change page ID to clear faults)") + ": " + "%(err_type)s: %(err_str)s" % { "err_type": tp.__name__, "err_str": e }) # type: ignore # noqa: E501 have_valid_page = False page_form = None if page_form is not None: page_form.helper.add_input( Submit("submit", ugettext("Submit answer"), accesskey="g")) page_form_html = page.form_to_html(pctx.request, page_context, page_form, answer_data) correct_answer = page.correct_answer(page_context, page_data, answer_data, grade_data=None) return render_course_page( pctx, "course/sandbox-page.html", { "edit_form": edit_form, "page_errors": page_errors, "page_warnings": page_warnings, "form": edit_form, # to placate form.media "have_valid_page": True, "title": title, "body": body, "page_form_html": page_form_html, "feedback": feedback, "correct_answer": correct_answer, }) else: return render_course_page( pctx, "course/sandbox-page.html", { "edit_form": edit_form, "form": edit_form, # to placate form.media "have_valid_page": False, "page_errors": page_errors, "page_warnings": page_warnings, })
def view_page_sandbox(pctx): from relate.utils import dict_to_struct import yaml PAGE_SESSION_KEY = "cf_validated_sandbox_page:" + pctx.course.identifier ANSWER_DATA_SESSION_KEY = "cf_page_sandbox_answer_data:" + pctx.course.identifier request = pctx.request page_source = pctx.request.session.get(PAGE_SESSION_KEY) page_errors = None is_preview_post = (request.method == "POST" and "preview" in request.POST) def make_form(data=None): return SandboxForm( page_source, "yaml", vim_mode, "Enter YAML markup for a flow page.", data) vim_mode = pctx.request.session.get(CF_SANDBOX_VIM_MODE, False) if is_preview_post: edit_form = make_form(pctx.request.POST) if edit_form.is_valid(): pctx.request.session[CF_SANDBOX_VIM_MODE] = \ vim_mode = edit_form.cleaned_data["vim_mode"] try: new_page_source = edit_form.cleaned_data["content"] page_desc = dict_to_struct(yaml.load(new_page_source)) from course.validation import validate_flow_page, ValidationContext vctx = ValidationContext( repo=pctx.repo, commit_sha=pctx.course_commit_sha) validate_flow_page(vctx, "sandbox", page_desc) except: import sys tp, e, _ = sys.exc_info() page_errors = ( "Page failed to load/validate: " "%s: %s" % (tp.__name__, e)) else: # Yay, it did validate. request.session[PAGE_SESSION_KEY] = page_source = new_page_source del new_page_source edit_form = make_form(pctx.request.POST) else: edit_form = make_form() have_valid_page = page_source is not None if have_valid_page: page_desc = dict_to_struct(yaml.load(page_source)) from course.content import instantiate_flow_page page = instantiate_flow_page("sandbox", pctx.repo, page_desc, pctx.course_commit_sha) page_data = page.make_page_data() from course.page import PageContext page_context = PageContext( course=pctx.course, repo=pctx.repo, commit_sha=pctx.course_commit_sha, flow_session=None) title = page.title(page_context, page_data) body = page.body(page_context, page_data) # {{{ try to recover answer_data answer_data = None stored_answer_data_tuple = \ pctx.request.session.get(ANSWER_DATA_SESSION_KEY) # Session storage uses JSON and may turn tuples into lists. if (isinstance(stored_answer_data_tuple, (list, tuple)) and len(stored_answer_data_tuple) == 2): stored_answer_data_page_id, stored_answer_data = \ stored_answer_data_tuple if stored_answer_data_page_id == page_desc.id: answer_data = stored_answer_data # }}} feedback = None page_form_html = None if page.expects_answer(): if request.method == "POST" and not is_preview_post: page_form = page.post_form(page_context, page_data, request.POST, request.FILES) if page_form.is_valid(): answer_data = page.answer_data(page_context, page_data, page_form, request.FILES) feedback = page.grade(page_context, page_data, answer_data, grade_data=None) pctx.request.session[ANSWER_DATA_SESSION_KEY] = ( page_desc.id, answer_data) else: page_form = page.make_form(page_context, page_data, answer_data, answer_is_final=False) if page_form is not None: page_form.helper.add_input( Submit("submit", "Submit answer", accesskey="g", css_class="col-lg-offset-2")) page_form_html = page.form_to_html( pctx.request, page_context, page_form, answer_data) correct_answer = page.correct_answer( page_context, page_data, answer_data, grade_data=None) return render_course_page(pctx, "course/sandbox-page.html", { "edit_form": edit_form, "page_errors": page_errors, "form": edit_form, # to placate form.media "have_valid_page": True, "title": title, "body": body, "page_form_html": page_form_html, "feedback": feedback, "correct_answer": correct_answer, }) else: return render_course_page(pctx, "course/sandbox-page.html", { "edit_form": edit_form, "form": edit_form, # to placate form.media "have_valid_page": False, "page_errors": page_errors, })
def download_all_submissions(pctx, flow_id): if pctx.role not in [ participation_role.teaching_assistant, participation_role.instructor, participation_role.observer, ]: raise PermissionDenied( _("must be at least TA to download submissions")) from course.content import get_flow_desc flow_desc = get_flow_desc(pctx.repo, pctx.course, flow_id, pctx.course_commit_sha) # {{{ find access rules tag if hasattr(flow_desc, "rules"): access_rules_tags = getattr(flow_desc.rules, "tags", []) else: access_rules_tags = [] ALL_SESSION_TAG = string_concat("<<<", _("ALL"), ">>>") # noqa session_tag_choices = [(tag, tag) for tag in access_rules_tags] + [ (ALL_SESSION_TAG, string_concat("(", _("ALL"), ")")) ] # }}} page_ids = [ "%s/%s" % (group_desc.id, page_desc.id) for group_desc in flow_desc.groups for page_desc in group_desc.pages ] request = pctx.request if request.method == "POST": form = DownloadAllSubmissionsForm(page_ids, session_tag_choices, request.POST) if form.is_valid(): which_attempt = form.cleaned_data["which_attempt"] slash_index = form.cleaned_data["page_id"].index("/") group_id = form.cleaned_data["page_id"][:slash_index] page_id = form.cleaned_data["page_id"][slash_index + 1:] from course.utils import PageInstanceCache page_cache = PageInstanceCache(pctx.repo, pctx.course, flow_id) visits = ( FlowPageVisit.objects.filter( flow_session__course=pctx.course, flow_session__flow_id=flow_id, page_data__group_id=group_id, page_data__page_id=page_id, is_submitted_answer=True, ).select_related("flow_session").select_related( "flow_session__participation__user").select_related( "page_data") # We overwrite earlier submissions with later ones # in a dictionary below. .order_by("visit_time")) if form.cleaned_data["non_in_progress_only"]: visits = visits.filter(flow_session__in_progress=False) if form.cleaned_data["restrict_to_rules_tag"] != ALL_SESSION_TAG: visits = visits.filter(flow_session__access_rules_tag=( form.cleaned_data["restrict_to_rules_tag"])) submissions = {} for visit in visits: page = page_cache.get_page(group_id, page_id, pctx.course_commit_sha) from course.page import PageContext grading_page_context = PageContext( course=pctx.course, repo=pctx.repo, commit_sha=pctx.course_commit_sha, flow_session=visit.flow_session) bytes_answer = page.normalized_bytes_answer( grading_page_context, visit.page_data.data, visit.answer) if which_attempt in ["first", "last"]: key = (visit.flow_session.participation.user.username, ) elif which_attempt == "all": key = (visit.flow_session.participation.user.username, str(visit.flow_session.id)) else: raise NotImplementedError() if bytes_answer is not None: if (which_attempt == "first" and key in submissions): # Already there, disregard further ones continue submissions[key] = bytes_answer from six import BytesIO from zipfile import ZipFile bio = BytesIO() with ZipFile(bio, "w") as subm_zip: for key, (extension, bytes_answer) in \ six.iteritems(submissions): subm_zip.writestr("-".join(key) + extension, bytes_answer) extra_file = request.FILES.get("extra_file") if extra_file is not None: subm_zip.writestr(extra_file.name, extra_file.read()) response = http.HttpResponse(bio.getvalue(), content_type="application/zip") response['Content-Disposition'] = ( 'attachment; filename="submissions_%s_%s_%s_%s_%s.zip"' % (pctx.course.identifier, flow_id, group_id, page_id, now().date().strftime("%Y-%m-%d"))) return response else: form = DownloadAllSubmissionsForm(page_ids, session_tag_choices) return render_course_page( pctx, "course/generic-course-form.html", { "form": form, "form_description": _("Download All Submissions in Zip file") })
def view_page_sandbox(pctx): if pctx.role not in [ participation_role.instructor, participation_role.teaching_assistant]: raise PermissionDenied( ugettext("must be instructor or TA to access sandbox")) from course.validation import ValidationError from relate.utils import dict_to_struct, Struct import yaml PAGE_SESSION_KEY = ( # noqa "cf_validated_sandbox_page:" + pctx.course.identifier) ANSWER_DATA_SESSION_KEY = ( # noqa "cf_page_sandbox_answer_data:" + pctx.course.identifier) request = pctx.request page_source = pctx.request.session.get(PAGE_SESSION_KEY) page_errors = None page_warnings = None is_preview_post = (request.method == "POST" and "preview" in request.POST) from course.models import get_user_status ustatus = get_user_status(request.user) def make_form(data=None): return SandboxForm( page_source, "yaml", ustatus.editor_mode, ugettext("Enter YAML markup for a flow page."), data) if is_preview_post: edit_form = make_form(pctx.request.POST) if edit_form.is_valid(): try: new_page_source = edit_form.cleaned_data["content"] page_desc = dict_to_struct(yaml.load(new_page_source)) if not isinstance(page_desc, Struct): raise ValidationError("Provided page source code is not " "a dictionary. Do you need to remove a leading " "list marker ('-') or some stray indentation?") from course.validation import validate_flow_page, ValidationContext vctx = ValidationContext( repo=pctx.repo, commit_sha=pctx.course_commit_sha) validate_flow_page(vctx, "sandbox", page_desc) page_warnings = vctx.warnings except: import sys tp, e, _ = sys.exc_info() page_errors = ( ugettext("Page failed to load/validate") + ": " + "%(err_type)s: %(err_str)s" % { "err_type": tp.__name__, "err_str": e}) else: # Yay, it did validate. request.session[PAGE_SESSION_KEY] = page_source = new_page_source del new_page_source edit_form = make_form(pctx.request.POST) else: edit_form = make_form() have_valid_page = page_source is not None if have_valid_page: page_desc = dict_to_struct(yaml.load(page_source)) from course.content import instantiate_flow_page try: page = instantiate_flow_page("sandbox", pctx.repo, page_desc, pctx.course_commit_sha) except: import sys tp, e, _ = sys.exc_info() page_errors = ( ugettext("Page failed to load/validate") + ": " + "%(err_type)s: %(err_str)s" % { "err_type": tp.__name__, "err_str": e}) have_valid_page = False if have_valid_page: page_data = page.make_page_data() from course.models import FlowSession from course.page import PageContext page_context = PageContext( course=pctx.course, repo=pctx.repo, commit_sha=pctx.course_commit_sha, # This helps code pages retrieve the editor pref. flow_session=FlowSession( course=pctx.course, participation=pctx.participation), in_sandbox=True) title = page.title(page_context, page_data) body = page.body(page_context, page_data) # {{{ try to recover answer_data answer_data = None stored_answer_data_tuple = \ pctx.request.session.get(ANSWER_DATA_SESSION_KEY) # Session storage uses JSON and may turn tuples into lists. if (isinstance(stored_answer_data_tuple, (list, tuple)) and len(stored_answer_data_tuple) == 3): stored_answer_data_page_type, stored_answer_data_page_id, stored_answer_data = \ stored_answer_data_tuple if ( stored_answer_data_page_type == page_desc.type and stored_answer_data_page_id == page_desc.id): answer_data = stored_answer_data # }}} feedback = None page_form_html = None if page.expects_answer(): from course.page.base import PageBehavior page_behavior = PageBehavior( show_correctness=True, show_answer=True, may_change_answer=True) if request.method == "POST" and not is_preview_post: page_form = page.process_form_post(page_context, page_data, request.POST, request.FILES, page_behavior) if page_form.is_valid(): answer_data = page.answer_data(page_context, page_data, page_form, request.FILES) feedback = page.grade(page_context, page_data, answer_data, grade_data=None) pctx.request.session[ANSWER_DATA_SESSION_KEY] = ( page_desc.type, page_desc.id, answer_data) else: page_form = page.make_form(page_context, page_data, answer_data, page_behavior) if page_form is not None: page_form.helper.add_input( Submit("submit", ugettext("Submit answer"), accesskey="g")) page_form_html = page.form_to_html( pctx.request, page_context, page_form, answer_data) correct_answer = page.correct_answer( page_context, page_data, answer_data, grade_data=None) return render_course_page(pctx, "course/sandbox-page.html", { "edit_form": edit_form, "page_errors": page_errors, "page_warnings": page_warnings, "form": edit_form, # to placate form.media "have_valid_page": True, "title": title, "body": body, "page_form_html": page_form_html, "feedback": feedback, "correct_answer": correct_answer, }) else: return render_course_page(pctx, "course/sandbox-page.html", { "edit_form": edit_form, "form": edit_form, # to placate form.media "have_valid_page": False, "page_errors": page_errors, "page_warnings": page_warnings, })