Newer
Older
Andreas Klöckner
committed
def expire_flow_session(
fctx, # type: FlowContext
flow_session, # type: FlowSession
grading_rule, # type: FlowSessionGradingRule
now_datetime, # type: datetime.datetime
past_due_only=False, # type:bool
):
# type: (...) -> bool
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
and grading_rule.due is not None
and 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.
return finish_flow_session(fctx, flow_session, grading_rule,
now_datetime=now_datetime, respect_preview=False)
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:
return finish_flow_session(fctx, flow_session, grading_rule,
now_datetime=now_datetime, respect_preview=False)
raise ValueError(
_("invalid expiration mode '%(mode)s' on flow session ID "
"%(session_id)d") % {
'mode': flow_session.expiration_mode,
'session_id': flow_session.id})
Andreas Klöckner
committed
def grade_flow_session(
fctx, # type: FlowContext
flow_session, # type: FlowSession
grading_rule, # type: FlowSessionGradingRule
answer_visits=None, # type: Optional[List[Optional[FlowPageVisit]]]
):
# type: (...) -> GradeInfo
"""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 = "flow-session-%d" % flow_session.id
gchange.points = points
gchange.max_points = 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,
state=gchange.state,
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, now_datetime):
# type: (FlowPageVisit, 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, # type: datetime.datetime
session, # type: FlowSession
force=False, # type: bool
suppress_log=False, # type: bool
unsubmit_pages=False, # type: bool
):
# type: (...) -> 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(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, # type: Repo_ish
course, # type: Course
session, # type: FlowSession
force_regrade=False, # type: bool
now_datetime=None, # type: Optional[datetime.datetime]
past_due_only=False, # type: bool
respect_preview=True, # type:bool
Andreas Klöckner
committed
):
# type: (...) -> bool
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)
Andreas Klöckner
committed
if grading_rule.due is not None:
if (
past_due_only
and now_datetime_filled < grading_rule.due):
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, # type: Any
course, # type: Course
session, # type: FlowSession
now_datetime, # type: datetime.datetime
past_due_only=False, # type: bool
):
# type: (...) -> 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, # type: Repo_ish
course, # type: Course
session, # type: FlowSession
):
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 = assemble_answer_visits(session) # type: List[Optional[FlowPageVisit]] # noqa
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, force=True, 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, course, session):
# type: (Repo_ish, Course, 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, force=True, 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, # type: http.HttpRequest
permissions, # type: frozenset[Text]
flow_session, # type: FlowSession
):
# type: (...) -> None
if flow_permission.lock_down_as_exam_session in permissions:
request.session[
"relate_session_locked_to_exam_flow_session_pk"] = \
flow_session.pk
# {{{ view: start flow
Andreas Klöckner
committed
# type: (CoursePageContext, Text) -> http.HttpResponse
request = pctx.request
login_exam_ticket = get_login_exam_ticket(pctx.request)
now_datetime = get_now_or_fake_time(request)
fctx = FlowContext(pctx.repo, pctx.course, flow_id,
participation=pctx.participation)
return post_start_flow(pctx, fctx, flow_id)
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)
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", # noqa
["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)
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
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])
return render_course_page(pctx, "course/flow-start.html", {
"may_start": may_start,
"new_session_grading_rule": new_session_grading_rule,
"grade_aggregation_strategy_descr": (
dict(GRADE_AGGREGATION_STRATEGY_CHOICES).get(
new_session_grading_rule.grade_aggregation_strategy)),
"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, fctx, flow_id):
Andreas Klöckner
committed
# type: (CoursePageContext, FlowContext, Text) -> 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)
if not session_start_rule.may_start_new_session:
raise PermissionDenied(_("new session not allowed"))
flow_user = pctx.request.user
if not flow_user.is_authenticated:
flow_user = None
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)
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, flow_session_id):
Andreas Klöckner
committed
# type: (CoursePageContext, 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)
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, flow_session_id):
Andreas Klöckner
committed
# type: (CoursePageContext, 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
or
(
# 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):
Andreas Klöckner
committed
# type: (frozenset[Text]) -> bool
return (
flow_permission.see_correctness in permissions
or flow_permission.see_answer_after_submission in permissions)
def may_send_email_about_flow_page(permissions):
# type: (frozenset[Text]) -> bool
return flow_permission.send_email_about_flow_page in permissions
Andreas Klöckner
committed
def get_page_behavior(
page, # type: PageBase
permissions, # type: frozenset[Text]
session_in_progress, # type: bool
answer_was_graded, # type: bool
generates_grade, # type: bool
is_unenrolled_session, # type: bool
viewing_prior_version=False, # type: bool
):
# type: (...) -> PageBehavior
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)
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
or (not generates_grade))
)
Andreas Klöckner
committed
from course.page.base import PageBehavior # noqa
return PageBehavior(
show_correctness=show_correctness,
show_answer=show_answer,
def add_buttons_to_form(form, fpctx, flow_session, permissions):
Andreas Klöckner
committed
# type: (StyledForm, FlowPageContext, FlowSession, frozenset[Text]) -> 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 fpctx.page_data.ordinal + 1 < flow_session.page_count:
Submit("save_and_next",
mark_safe_lazy(
string_concat(
_("Save answer and move on"),
" »")),
else:
form.helper.add_input(
Submit("save_and_finish",
mark_safe_lazy(
string_concat(
_("Save answer and finish"),
" »")),
Dong Zhuang
committed
def create_flow_page_visit(request, flow_session, page_data):
Andreas Klöckner
committed
# type: (http.HttpRequest, FlowSession, 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, flow_session_id, ordinal):
Andreas Klöckner
committed
# type: (CoursePageContext, int, int) -> http.HttpResponse
request = pctx.request
login_exam_ticket = get_login_exam_ticket(request)
ordinal = int(ordinal)
flow_session_id = int(flow_session_id)
flow_session = get_and_check_flow_session(pctx, flow_session_id)
if flow_session is None:
messages.add_message(request, messages.WARNING,
_("No in-progress session record found for this flow. "
"Redirected to flow start page."))
return redirect("relate-view_start_flow",
pctx.course.identifier,
adjust_flow_session_page_data(pctx.repo, flow_session, pctx.course.identifier,
respect_preview=True)
try:
fpctx = FlowPageContext(pctx.repo, pctx.course, flow_id, ordinal,
participation=pctx.participation,
flow_session=flow_session,
request=pctx.request)
except PageOrdinalOutOfRange:
return redirect("relate-view_flow_page",
pctx.course.identifier,
flow_session.id,
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)
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
and
grading_rule.generates_grade)
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
# 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))
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
# {{{ 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),
}
+
'<a class="btn btn-default btn-sm" href="?" '
'role="button">« %s</a>'
% _("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,
ugettext(
"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
and flow_permission.submit_answer in permissions):
messages.add_message(request, messages.INFO,
_("Changes to this session are being prevented "
"because this session yields a permanent grade, but "
"you have not completed your enrollment process in "
"this course."))
if form is not None:
form_html = fpctx.page.form_to_html(
pctx.request, page_context, form, answer_data)
else:
form_html = None
expiration_mode_choices = []
for key, descr in FLOW_SESSION_EXPIRATION_MODE_CHOICES:
if is_expiration_mode_allowed(key, permissions):
expiration_mode_choices.append((key, descr))
session_minutes = None
time_factor = 1
if flow_permission.see_session_time in permissions:
if not flow_session.in_progress:
end_time = as_local_time(flow_session.completion_time)
else: