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:
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):
# type: (FlowSession) -> Text
return "flow-session-%d" % 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 = get_flow_session_attempt_id(flow_session)
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,
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)
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, # 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]
Andreas Klöckner
committed
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
fctx = FlowContext(pctx.repo, pctx.course, flow_id,
participation=pctx.participation)
return post_start_flow(pctx, fctx, flow_id)
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
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)
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
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,
"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, 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):
# type: (FrozenSet[Text]) -> 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(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]
Andreas Klöckner
committed
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):
# type: (StyledForm, FlowPageContext, FlowSession, FrozenSet[Text]) -> StyledForm
Andreas Klöckner
committed
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.page_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, page_ordinal):
Andreas Klöckner
committed
# type: (CoursePageContext, int, 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)
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)
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,
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
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))
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
# {{{ 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."))