Newer
Older
# ORDERING RESTRICTION: Must grade pages before gathering grade info
# {{{ determine completion time
completion_time = now_datetime
if grading_rule.use_last_activity_as_completion_time:
last_activity = flow_session.last_activity()
if last_activity is not None:
completion_time = last_activity
flow_session.completion_time = completion_time
# }}}
flow_session.save()
return grade_flow_session(fctx, flow_session, grading_rule,
Andreas Klöckner
committed
def expire_flow_session(
fctx: FlowContext,
flow_session: FlowSession,
grading_rule: FlowSessionGradingRule,
now_datetime: datetime.datetime,
past_due_only: bool = False,
) -> bool:
Andreas Klöckner
committed
Andreas Klöckner
committed
# This function does not need to be transactionally atomic.
# It only does one atomic 'thing' in each execution path.
if not flow_session.in_progress:
raise RuntimeError(_("Can't expire a session that's not in progress"))
if flow_session.participation is None:
raise RuntimeError(_("Can't expire an anonymous flow session"))
assert isinstance(grading_rule, FlowSessionGradingRule)
if past_due_only:
if grading_rule.due is None:
return False
elif now_datetime < grading_rule.due:
return False
Andreas Klöckner
committed
adjust_flow_session_page_data(fctx.repo, flow_session,
flow_session.course.identifier, fctx.flow_desc,
respect_preview=False)
Andreas Klöckner
committed
if flow_session.expiration_mode == flow_session_expiration_mode.roll_over:
session_start_rule = get_session_start_rule(
flow_session.course, flow_session.participation,
flow_session.flow_id, fctx.flow_desc, now_datetime,
for_rollover=True)
if not session_start_rule.may_start_new_session:
# No new session allowed: finish.
finish_flow_session(fctx, flow_session, grading_rule,
now_datetime=now_datetime, respect_preview=False)
return True
Andreas Klöckner
committed
else:
Andreas Klöckner
committed
flow_session.access_rules_tag = session_start_rule.tag_session
Andreas Klöckner
committed
# {{{ FIXME: This is weird and should probably not exist.
access_rule = get_session_access_rule(
flow_session, fctx.flow_desc, now_datetime)
if session_start_rule.default_expiration_mode is not None:
flow_session.expiration_mode = \
session_start_rule.default_expiration_mode
elif not is_expiration_mode_allowed(
Andreas Klöckner
committed
flow_session.expiration_mode, access_rule.permissions):
flow_session.expiration_mode = flow_session_expiration_mode.end
Andreas Klöckner
committed
# }}}
Andreas Klöckner
committed
flow_session.save()
Andreas Klöckner
committed
return True
elif flow_session.expiration_mode == flow_session_expiration_mode.end:
finish_flow_session(fctx, flow_session, grading_rule,
now_datetime=now_datetime, respect_preview=False)
return True
raise ValueError(
_("invalid expiration mode '%(mode)s' on flow session ID "
"%(session_id)d") % {
"mode": flow_session.expiration_mode,
"session_id": flow_session.id})
def get_flow_session_attempt_id(flow_session: FlowSession) -> str:
return "flow-session-%d" % flow_session.id
Andreas Klöckner
committed
def grade_flow_session(
fctx: FlowContext,
flow_session: FlowSession,
grading_rule: FlowSessionGradingRule,
answer_visits: list[FlowPageVisit | None] | None = None,
) -> GradeInfo:
Andreas Klöckner
committed
"""Updates the grade on an existing flow session and logs a
grade change with the grade records subsystem.
"""
if answer_visits is None:
answer_visits = assemble_answer_visits(flow_session)
grade_info = gather_grade_info(fctx, flow_session, grading_rule, answer_visits)
assert grade_info is not None
comment = None
points = grade_info.points
if (points is not None
and grading_rule.credit_percent is not None
and grading_rule.credit_percent != 100):
comment = (
_("Counted at %(percent).1f%% of %(point).1f points") % {
"percent": grading_rule.credit_percent,
"point": points})
points = points * grading_rule.credit_percent / 100
flow_session.points = points
flow_session.max_points = grade_info.max_points
flow_session.append_comment(comment)
flow_session.save()
# Need to save grade record even if no grade is available yet, because
# a grade record may *already* be saved, and that one might be mistaken
# for the current one.
if (grading_rule.grade_identifier
and grading_rule.generates_grade
and flow_session.participation is not None):
from course.models import get_flow_grading_opportunity
gopp = get_flow_grading_opportunity(
flow_session.course, flow_session.flow_id, fctx.flow_desc,
grading_rule.grade_identifier,
grading_rule.grade_aggregation_strategy)
from course.models import grade_state_change_types
gchange = GradeChange()
gchange.opportunity = gopp
gchange.participation = flow_session.participation
gchange.state = grade_state_change_types.graded
gchange.attempt_id = get_flow_session_attempt_id(flow_session)
gchange.points = points
gchange.max_points = not_none(grade_info.max_points)
# creator left as NULL
gchange.flow_session = flow_session
gchange.comment = comment
previous_grade_changes = list(GradeChange.objects
.filter(
opportunity=gchange.opportunity,
participation=gchange.participation,
attempt_id=gchange.attempt_id,
flow_session=gchange.flow_session)
.order_by("-grade_time")
[:1])
# only save if modified or no previous grades
do_save = True
if previous_grade_changes:
previous_grade_change, = previous_grade_changes
if (previous_grade_change.points == gchange.points
and previous_grade_change.max_points == gchange.max_points
and previous_grade_change.comment == gchange.comment):
do_save = False
Andreas Klöckner
committed
else:
# no previous grade changes
if points is None:
do_save = False
if do_save:
gchange.save()
return grade_info
def unsubmit_page(
prev_answer_visit: FlowPageVisit, now_datetime: datetime.datetime) -> None:
prev_answer_visit.id = None
prev_answer_visit.visit_time = now_datetime
prev_answer_visit.remote_address = None
prev_answer_visit.user = None
prev_answer_visit.is_synthetic = True
assert prev_answer_visit.is_submitted_answer
prev_answer_visit.is_submitted_answer = False
prev_answer_visit.save()
Andreas Klöckner
committed
def reopen_session(
now_datetime: datetime.datetime,
session: FlowSession,
suppress_log: bool = False,
unsubmit_pages: bool = False,
) -> None:
Andreas Klöckner
committed
with transaction.atomic():
if session.in_progress:
raise RuntimeError(
_("Cannot reopen a session that's already in progress"))
if session.participation is None:
raise RuntimeError(
_("Cannot reopen anonymous sessions"))
session.in_progress = True
session.points = None
session.max_points = None
if not suppress_log:
session.append_comment(
_("Session reopened at %(now)s, previous completion time "
"was '%(complete_time)s'.") % {
"now": format_datetime_local(now_datetime),
"complete_time": format_datetime_local(
as_local_time(not_none(session.completion_time)))
session.completion_time = None
session.save()
if unsubmit_pages:
answer_visits = assemble_answer_visits(session)
for visit in answer_visits:
if visit is not None:
unsubmit_page(visit, now_datetime)
Andreas Klöckner
committed
def finish_flow_session_standalone(
repo: Repo_ish,
course: Course,
session: FlowSession,
force_regrade: bool = False,
now_datetime: datetime.datetime | None = None,
past_due_only: bool = False,
respect_preview: bool = True,
) -> bool:
Andreas Klöckner
committed
Andreas Klöckner
committed
# Do not be tempted to call adjust_flow_session_page_data in here.
# This function may be called from within a transaction.
Andreas Klöckner
committed
assert session.participation is not None
Andreas Klöckner
committed
if now_datetime is None:
from django.utils.timezone import now
Andreas Klöckner
committed
now_datetime_filled = now()
else:
now_datetime_filled = now_datetime
Andreas Klöckner
committed
fctx = FlowContext(repo, course, session.flow_id)
grading_rule = get_session_grading_rule(session, fctx.flow_desc,
now_datetime_filled)
if past_due_only:
if grading_rule.due is None:
return False
elif now_datetime_filled < grading_rule.due:
Andreas Klöckner
committed
return False
finish_flow_session(fctx, session, grading_rule,
Andreas Klöckner
committed
force_regrade=force_regrade,
now_datetime=now_datetime_filled,
respect_preview=respect_preview)
Andreas Klöckner
committed
def expire_flow_session_standalone(
repo: Any,
course: Course,
session: FlowSession,
now_datetime: datetime.datetime,
past_due_only: bool = False,
) -> bool:
assert session.participation is not None
Andreas Klöckner
committed
fctx = FlowContext(repo, course, session.flow_id)
grading_rule = get_session_grading_rule(session, fctx.flow_desc, now_datetime)
return expire_flow_session(fctx, session, grading_rule, now_datetime,
past_due_only=past_due_only)
Andreas Klöckner
committed
def regrade_session(
repo: Repo_ish,
course: Course,
session: FlowSession,
) -> None:
adjust_flow_session_page_data(repo, session, course.identifier,
respect_preview=False)
Andreas Klöckner
committed
Andreas Klöckner
committed
with transaction.atomic():
answer_visits: list[FlowPageVisit | None] = assemble_answer_visits(session)
Andreas Klöckner
committed
for i in range(len(answer_visits)):
answer_visit = answer_visits[i]
Andreas Klöckner
committed
if answer_visit is not None:
if answer_visit.get_most_recent_grade():
# Only make a new grade if there already is one.
grade_page_visit(answer_visit, respect_preview=False)
Andreas Klöckner
committed
prev_completion_time = session.completion_time
now_datetime = local_now()
Andreas Klöckner
committed
with transaction.atomic():
session.append_comment(
_("Session regraded at %(time)s.") % {
"time": format_datetime_local(now_datetime)
Andreas Klöckner
committed
})
session.save()
Andreas Klöckner
committed
reopen_session(now_datetime, session, suppress_log=True)
Andreas Klöckner
committed
finish_flow_session_standalone(
repo, course, session, force_regrade=True,
now_datetime=prev_completion_time,
respect_preview=False)
def recalculate_session_grade(
repo: Repo_ish, course: Course, session: FlowSession) -> None:
"""Only redoes the final grade determination without regrading
individual pages.
"""
if session.in_progress:
raise RuntimeError(_("cannot recalculate grade on in-progress session"))
prev_completion_time = session.completion_time
adjust_flow_session_page_data(repo, session, course.identifier,
respect_preview=False)
Andreas Klöckner
committed
with transaction.atomic():
now_datetime = local_now()
Andreas Klöckner
committed
session.append_comment(
_("Session grade recomputed at %(time)s.") % {
"time": format_datetime_local(now_datetime)
Andreas Klöckner
committed
})
session.save()
reopen_session(now_datetime, session, suppress_log=True)
Andreas Klöckner
committed
finish_flow_session_standalone(
repo, course, session, force_regrade=False,
now_datetime=prev_completion_time,
respect_preview=False)
# }}}
Andreas Klöckner
committed
def lock_down_if_needed(
request: http.HttpRequest,
permissions: frozenset[str],
flow_session: FlowSession,
) -> None:
Andreas Klöckner
committed
if flow_permission.lock_down_as_exam_session in permissions:
request.session[SESSION_LOCKED_TO_FLOW_PK] = flow_session.pk
# {{{ view: start flow
def view_start_flow(pctx: CoursePageContext, flow_id: str) -> http.HttpResponse:
request = pctx.request
fctx = FlowContext(pctx.repo, pctx.course, flow_id,
participation=pctx.participation)
return post_start_flow(pctx, fctx, flow_id)
login_exam_ticket = get_login_exam_ticket(pctx.request)
now_datetime = get_now_or_fake_time(request)
session_start_rule = get_session_start_rule(
pctx.course, pctx.participation,
flow_id, fctx.flow_desc, now_datetime,
facilities=pctx.request.relate_facilities,
login_exam_ticket=login_exam_ticket,
remote_ip_address=remote_address_from_request(pctx.request))
if session_start_rule.may_list_existing_sessions:
past_sessions = (FlowSession.objects
.filter(
participation=pctx.participation,
flow_id=fctx.flow_id,
participation__isnull=False)
.order_by("start_time"))
from collections import namedtuple
SessionProperties = namedtuple("SessionProperties",
["may_view", "may_modify", "due", "grade_description",
"grade_shown"])
past_sessions_and_properties = []
for session in past_sessions:
access_rule = get_session_access_rule(
session, fctx.flow_desc, now_datetime,
facilities=pctx.request.relate_facilities,
login_exam_ticket=login_exam_ticket,
remote_ip_address=remote_address_from_request(pctx.request))
grading_rule = get_session_grading_rule(
session, fctx.flow_desc, now_datetime)
session_properties = SessionProperties(
may_view=flow_permission.view in access_rule.permissions,
may_modify=(
flow_permission.submit_answer in access_rule.permissions
or flow_permission.end_session in access_rule.permissions
),
due=grading_rule.due,
grade_description=grading_rule.description,
grade_shown=(
flow_permission.cannot_see_flow_result
not in access_rule.permissions))
past_sessions_and_properties.append((session, session_properties))
else:
past_sessions_and_properties = []
may_start = session_start_rule.may_start_new_session
new_session_grading_rule = None
start_may_decrease_grade = False
grade_aggregation_strategy_descr = None
potential_session = FlowSession(
course=pctx.course,
participation=pctx.participation,
flow_id=flow_id,
in_progress=True,
# default_expiration_mode ignored
expiration_mode=flow_session_expiration_mode.end,
access_rules_tag=session_start_rule.tag_session)
new_session_grading_rule = get_session_grading_rule(
potential_session, fctx.flow_desc, now_datetime)
start_may_decrease_grade = (
bool(past_sessions_and_properties)
and new_session_grading_rule.grade_aggregation_strategy
not in [
grade_aggregation_strategy.max_grade,
grade_aggregation_strategy.use_earliest])
grade_aggregation_strategy_descr = (
dict(GRADE_AGGREGATION_STRATEGY_CHOICES).get(
new_session_grading_rule.grade_aggregation_strategy))
return render_course_page(pctx, "course/flow-start.html", {
"flow_desc": fctx.flow_desc,
"flow_identifier": flow_id,
"flow_description_html": markup_to_html(
fctx.course, fctx.repo, fctx.course_commit_sha,
getattr(fctx.flow_desc, "description", "")),
"now": now_datetime,
"may_start": may_start,
"new_session_grading_rule": new_session_grading_rule,
"grade_aggregation_strategy_descr": grade_aggregation_strategy_descr,
"start_may_decrease_grade": start_may_decrease_grade,
"past_sessions_and_properties": past_sessions_and_properties,
},
allow_instant_flow_requests=False)
@retry_transaction_decorator(serializable=True)
def post_start_flow(
pctx: CoursePageContext, fctx: FlowContext, flow_id: str
) -> http.HttpResponse:
now_datetime = get_now_or_fake_time(pctx.request)
login_exam_ticket = get_login_exam_ticket(pctx.request)
past_sessions = (FlowSession.objects
.filter(
participation=pctx.participation,
flow_id=fctx.flow_id,
participation__isnull=False)
.order_by("start_time"))
if past_sessions:
latest_session = past_sessions.reverse()[0]
cooldown_seconds = getattr(
settings, "RELATE_SESSION_RESTART_COOLDOWN_SECONDS", 10)
from datetime import timedelta
if (
timedelta(seconds=0)
<= (now_datetime - latest_session.start_time)
return redirect("relate-view_flow_page",
pctx.course.identifier, latest_session.id, 0)
session_start_rule = get_session_start_rule(
pctx.course, pctx.participation,
flow_id, fctx.flow_desc, now_datetime,
facilities=pctx.request.relate_facilities,
login_exam_ticket=login_exam_ticket,
remote_ip_address=remote_address_from_request(pctx.request))
if not session_start_rule.may_start_new_session:
raise PermissionDenied(_("new session not allowed"))
if not pctx.request.user.is_authenticated:
flow_user: User | None = None
else:
flow_user = pctx.request.user
session = start_flow(
pctx.repo, pctx.course, pctx.participation,
user=flow_user,
flow_id=flow_id, flow_desc=fctx.flow_desc,
session_start_rule=session_start_rule,
now_datetime=now_datetime)
access_rule = get_session_access_rule(
session, fctx.flow_desc, now_datetime,
facilities=pctx.request.relate_facilities,
login_exam_ticket=login_exam_ticket,
remote_ip_address=remote_address_from_request(pctx.request))
lock_down_if_needed(pctx.request, access_rule.permissions, session)
return redirect("relate-view_flow_page",
pctx.course.identifier, session.id, 0)
# {{{ view: resume flow
# The purpose of this interstitial redirection page is to set the exam
# lockdown flag upon resumption/review. Without this, the exam lockdown
# middleware will refuse access to flow pages in a locked-down facility.
@course_view
def view_resume_flow(
pctx: CoursePageContext, flow_session_id: int) -> http.HttpResponse:
now_datetime = get_now_or_fake_time(pctx.request)
flow_session = get_and_check_flow_session(pctx, int(flow_session_id))
fctx = FlowContext(pctx.repo, pctx.course, flow_session.flow_id,
participation=pctx.participation)
login_exam_ticket = get_login_exam_ticket(pctx.request)
access_rule = get_session_access_rule(
flow_session, fctx.flow_desc, now_datetime,
facilities=pctx.request.relate_facilities,
login_exam_ticket=login_exam_ticket,
remote_ip_address=remote_address_from_request(pctx.request))
lock_down_if_needed(pctx.request, access_rule.permissions,
flow_session)
return redirect("relate-view_flow_page",
pctx.course.identifier, flow_session.id, 0)
# }}}
# {{{ view: flow page
def get_and_check_flow_session(
pctx: CoursePageContext, flow_session_id: int) -> FlowSession:
flow_session = (FlowSession.objects
.select_related("participation")
.get(id=flow_session_id))
except ObjectDoesNotExist:
raise http.Http404()
if flow_session.course.pk != pctx.course.pk:
raise http.Http404()
Andreas Klöckner
committed
my_session = (
pctx.participation == flow_session.participation
# anonymous by participation
flow_session.participation is None
and (
# We don't know whose (legacy)
# Truly anonymous sessions belong to everyone.
flow_session.user is None
or pctx.request.user == flow_session.user)))
if not my_session:
from course.enrollment import get_participation_role_identifiers
owner_roles = get_participation_role_identifiers(
pctx.course, flow_session.participation)
allowed = False
for orole in owner_roles:
for perm, arg in my_perms:
if (
perm == pperm.view_flow_sessions_from_role
and arg == orole):
allowed = True
break
if allowed:
break
if not allowed:
raise PermissionDenied(_("may not view other people's sessions"))
return flow_session
def will_receive_feedback(permissions: frozenset[str]) -> bool:
Andreas Klöckner
committed
return (
flow_permission.see_correctness in permissions
or flow_permission.see_answer_after_submission in permissions)
def may_send_email_about_flow_page(
flow_session: FlowSession, permissions: frozenset[str]) -> bool:
return (
flow_session.participation is not None
and flow_session.user is not None
and flow_permission.send_email_about_flow_page in permissions)
Andreas Klöckner
committed
def get_page_behavior(
page: PageBase,
permissions: frozenset[str],
session_in_progress: bool,
answer_was_graded: bool,
generates_grade: bool,
is_unenrolled_session: bool,
viewing_prior_version: bool = False,
) -> PageBehavior:
Andreas Klöckner
committed
show_correctness = False
Andreas Klöckner
committed
if page.expects_answer():
if answer_was_graded:
show_correctness = flow_permission.see_correctness in permissions
Andreas Klöckner
committed
show_answer = flow_permission.see_answer_after_submission in permissions
Andreas Klöckner
committed
if session_in_progress:
# Don't reveal the answer if they can still change their mind
show_answer = (show_answer
and flow_permission.change_answer not in permissions)
Andreas Klöckner
committed
show_answer = show_answer or (
flow_permission.see_answer_before_submission in permissions)
Andreas Klöckner
committed
else:
# Don't show answer yet
show_answer = (
flow_permission.see_answer_before_submission in permissions)
else:
show_answer = (
flow_permission.see_answer_before_submission in permissions
or flow_permission.see_answer_after_submission in permissions)
not viewing_prior_version
and (not answer_was_graded
or (flow_permission.change_answer in permissions))
# can happen if no answer was ever saved
and session_in_progress
and (flow_permission.submit_answer in permissions)
and ((generates_grade and not is_unenrolled_session)
from course.page.base import PageBehavior
return PageBehavior(
show_correctness=show_correctness,
show_answer=show_answer,
def add_buttons_to_form(
form: StyledForm,
fpctx: FlowPageContext,
flow_session: FlowSession,
permissions: frozenset[str]) -> StyledForm:
from crispy_forms.layout import Submit
show_save_button = getattr(form, "show_save_button", True)
if show_save_button:
form.helper.add_input(
Submit("save", _("Save answer"),
css_class="relate-save-button"))
if will_receive_feedback(permissions):
if flow_permission.change_answer in permissions:
"submit", _("Submit answer for feedback"),
accesskey="g",
css_class="relate-save-button relate-submit-button"))
form.helper.add_input(
css_class="relate-save-button relate-submit-button"))
else:
# Only offer 'save and move on' if student will receive no feedback
if (not_none(fpctx.page_data.page_ordinal) + 1
< not_none(flow_session.page_count)):
Submit("save_and_next",
string_concat(
_("Save answer and move on"),
" »")),
else:
form.helper.add_input(
Submit("save_and_finish",
string_concat(
_("Save answer and finish"),
" »")),
def create_flow_page_visit(
request: http.HttpRequest,
flow_session: FlowSession,
page_data: FlowPageData) -> None:
if request.user.is_authenticated:
# The access to 'is_authenticated' ought to wake up SimpleLazyObject.
user = request.user
else:
user = None
flow_session=flow_session,
page_data=page_data,
remote_address=request.META["REMOTE_ADDR"],
Dong Zhuang
committed
is_submitted_answer=None)
if hasattr(request, "relate_impersonate_original_user"):
visit.impersonated_by = request.relate_impersonate_original_user
visit.save()
@course_view
def view_flow_page(
pctx: CoursePageContext,
flow_session_id: int,
page_ordinal: int) -> http.HttpResponse:
request = pctx.request
login_exam_ticket = get_login_exam_ticket(request)
page_ordinal = int(page_ordinal)
flow_session_id = int(flow_session_id)
flow_session = get_and_check_flow_session(pctx, flow_session_id)
adjust_flow_session_page_data(pctx.repo, flow_session, pctx.course.identifier,
respect_preview=True)
fpctx = FlowPageContext(pctx.repo, pctx.course, flow_id, page_ordinal,
participation=pctx.participation,
flow_session=flow_session,
request=pctx.request)
except PageOrdinalOutOfRange:
return redirect("relate-view_flow_page",
pctx.course.identifier,
not_none(flow_session.page_count)-1)
Andreas Klöckner
committed
if fpctx.page is None:
raise http.Http404()
assert fpctx.page_context is not None
assert fpctx.page_data is not None
Andreas Klöckner
committed
now_datetime = get_now_or_fake_time(request)
access_rule = get_session_access_rule(
flow_session, fpctx.flow_desc, now_datetime,
facilities=pctx.request.relate_facilities,
login_exam_ticket=login_exam_ticket,
remote_ip_address=remote_address_from_request(pctx.request))
Andreas Klöckner
committed
grading_rule = get_session_grading_rule(
flow_session, fpctx.flow_desc, now_datetime)
generates_grade = (
grading_rule.grade_identifier is not None
Andreas Klöckner
committed
del grading_rule
permissions = fpctx.page.get_modified_permissions_for_page(
access_rule.permissions)
if access_rule.message:
messages.add_message(request, messages.INFO, access_rule.message)
lock_down_if_needed(pctx.request, permissions, flow_session)
page_context = fpctx.page_context
page_data = fpctx.page_data
answer_data = None
grade_data = None
if flow_permission.view not in permissions:
raise PermissionDenied(_("not allowed to view flow"))
answer_visit = None
prev_visit_id = None
viewing_prior_version = False
if request.method == "POST":
if "finish" in request.POST:
return redirect("relate-finish_flow_session_view",
pctx.course.identifier, flow_session_id)
post_result = post_flow_page(
flow_session, fpctx, request, permissions, generates_grade)
if not isinstance(post_result, tuple):
# ought to be an HTTP response
return post_result
(
page_behavior,
prev_answer_visits,
form,
feedback,
answer_data,
answer_was_graded) = post_result
Andreas Klöckner
committed
if prev_answer_visits:
prev_visit_id = prev_answer_visits[0].id
# continue at common flow page generation below
Dong Zhuang
committed
create_flow_page_visit(request, flow_session, fpctx.page_data)
prev_answer_visits = list(
get_prev_answer_visits_qset(fpctx.page_data))
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
# {{{ fish out previous answer_visit
prev_visit_id = pctx.request.GET.get("visit_id")
if prev_visit_id is not None:
try:
prev_visit_id = int(prev_visit_id)
except ValueError:
raise SuspiciousOperation("non-integer passed for 'visit_id'")
if prev_answer_visits and prev_visit_id is not None:
answer_visit = prev_answer_visits[0]
for ivisit, pvisit in enumerate(prev_answer_visits):
if pvisit.id == prev_visit_id:
answer_visit = pvisit
if ivisit > 0:
viewing_prior_version = True
break
if viewing_prior_version:
from django.template import defaultfilters
messages.add_message(request, messages.INFO, (
_("Viewing prior submission dated %(date)s. ")
% {
"date": defaultfilters.date(
as_local_time(answer_visit.visit_time),
'role="button">« {}</a>'.format(_("Go back"))))
prev_visit_id = answer_visit.id
elif prev_answer_visits:
answer_visit = prev_answer_visits[0]
prev_visit_id = answer_visit.id
else:
answer_visit = None
# }}}
if answer_visit is not None:
answer_was_graded = answer_visit.is_submitted_answer
else:
answer_was_graded = False
page_behavior = get_page_behavior(
page=fpctx.page,
permissions=permissions,
session_in_progress=flow_session.in_progress,
Andreas Klöckner
committed
answer_was_graded=answer_was_graded,
generates_grade=generates_grade,
is_unenrolled_session=flow_session.participation is None,
viewing_prior_version=viewing_prior_version)
if answer_visit is not None:
answer_data = answer_visit.answer
most_recent_grade = answer_visit.get_most_recent_grade()
if most_recent_grade is not None:
feedback = get_feedback_for_grade(most_recent_grade)
grade_data = most_recent_grade.grade_data
else:
feedback = None
grade_data = None
else:
feedback = None
try:
form = fpctx.page.make_form(
page_context, page_data.data,
answer_data, page_behavior)
except InvalidPageData as e:
messages.add_message(request, messages.ERROR,
"The page data stored in the database was found "
"to be invalid for the page as given in the "
"course content. Likely the course content was "
"changed in an incompatible way (say, by adding "
"an option to a choice question) without changing "
"the question ID. The precise error encountered "
"was the following: ")+str(e))
return render_course_page(pctx, "course/course-base.html", {})
feedback = None
# start common flow page generation
# defined at this point:
# form, page_behavior, answer_was_graded, feedback
# answer_data, grade_data
if form is not None and page_behavior.may_change_answer:
form = add_buttons_to_form(form, fpctx, flow_session,
shown_feedback = None
if (fpctx.page.expects_answer() and answer_was_graded
and (
page_behavior.show_correctness
or page_behavior.show_answer)):
shown_feedback = feedback
title = fpctx.page.title(page_context, page_data.data)
body = fpctx.page.body(page_context, page_data.data)
if page_behavior.show_answer:
correct_answer = fpctx.page.correct_answer(
page_context, page_data.data,
answer_data, grade_data)
else:
correct_answer = None
and flow_session.participation is None