Newer
Older
_("Cannot reopen anonymous sessions"))
session.in_progress = True
session.points = None
session.max_points = None
Andreas Klöckner
committed
if not suppress_log:
session.append_comment(
_("Session reopened at %(now)s, previous completion time "
"was '%(complete_time)s'.") % {
'now': format_datetime_local(local_now()),
'complete_time': format_datetime_local(
as_local_time(session.completion_time))
})
session.completion_time = None
Andreas Klöckner
committed
def finish_flow_session_standalone(repo, course, session, force_regrade=False,
now_datetime=None, past_due_only=False):
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
now_datetime = now()
Andreas Klöckner
committed
fctx = FlowContext(repo, course, session.flow_id)
grading_rule = get_session_grading_rule(
session, session.participation.role, fctx.flow_desc, now_datetime)
if (past_due_only
and grading_rule.due is not None
and now_datetime < grading_rule.due):
finish_flow_session(fctx, session, grading_rule,
Andreas Klöckner
committed
force_regrade=force_regrade, now_datetime=now_datetime)
def expire_flow_session_standalone(repo, course, session, now_datetime,
past_due_only=False):
assert session.participation is not None
Andreas Klöckner
committed
fctx = FlowContext(repo, course, session.flow_id)
grading_rule = get_session_grading_rule(
session, session.participation.role, fctx.flow_desc, now_datetime)
return expire_flow_session(fctx, session, grading_rule, now_datetime,
past_due_only=past_due_only)
def regrade_session(repo, course, session):
Andreas Klöckner
committed
adjust_flow_session_page_data(repo, session, course.identifier)
Andreas Klöckner
committed
fctx = FlowContext(repo, course, session.flow_id,
participation=session.participation)
Andreas Klöckner
committed
with transaction.atomic():
answer_visits = 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 and answer_visit.get_most_recent_grade():
# Only make a new grade if there already is one.
grade_page_visit(answer_visit,
graded_at_git_commit_sha=fctx.course_commit_sha)
Andreas Klöckner
committed
prev_completion_time = session.completion_time
Andreas Klöckner
committed
with transaction.atomic():
session.append_comment(
_("Session regraded at %(time)s.") % {
'time': format_datetime_local(local_now())
})
session.save()
Andreas Klöckner
committed
Andreas Klöckner
committed
reopen_session(session, force=True, suppress_log=True)
finish_flow_session_standalone(
repo, course, session, force_regrade=True,
now_datetime=prev_completion_time)
def recalculate_session_grade(repo, course, session):
"""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
Andreas Klöckner
committed
adjust_flow_session_page_data(repo, session, course.identifier)
Andreas Klöckner
committed
with transaction.atomic():
session.append_comment(
_("Session grade recomputed at %(time)s.") % {
'time': format_datetime_local(local_now())
})
session.save()
reopen_session(session, force=True, suppress_log=True)
finish_flow_session_standalone(
repo, course, session, force_regrade=False,
now_datetime=prev_completion_time)
# }}}
def lock_down_if_needed(request, permissions, flow_session):
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
request = 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,
pctx.role, flow_id, fctx.flow_desc, now_datetime,
facilities=pctx.request.relate_facilities)
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, pctx.role, fctx.flow_desc, now_datetime,
facilities=pctx.request.relate_facilities)
grading_rule = get_session_grading_rule(
session, pctx.role, 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,
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, pctx.role, 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):
now_datetime = get_now_or_fake_time(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,
pctx.role, flow_id, fctx.flow_desc, now_datetime,
facilities=pctx.request.relate_facilities)
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,
access_rules_tag=session_start_rule.tag_session,
now_datetime=now_datetime)
access_rule = get_session_access_rule(
session, pctx.role, fctx.flow_desc, now_datetime,
facilities=pctx.request.relate_facilities)
lock_down_if_needed(pctx.request, access_rule.permissions, session)
return redirect("relate-view_flow_page",
pctx.course.identifier, session.id, 0)
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
# {{{ 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):
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)
access_rule = get_session_access_rule(
flow_session, pctx.role, fctx.flow_desc, now_datetime,
facilities=pctx.request.relate_facilities)
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):
try:
flow_session = FlowSession.objects.get(id=flow_session_id)
except ObjectDoesNotExist:
raise http.Http404()
if pctx.role in [
participation_role.instructor,
participation_role.teaching_assistant]:
pass
elif pctx.role in [
participation_role.student,
participation_role.observer,
participation_role.auditor,
if (pctx.participation != flow_session.participation
and flow_session.participation is not None):
raise PermissionDenied(_("may not view other people's sessions"))
Andreas Klöckner
committed
if (flow_session.user is not None
and pctx.request.user != flow_session.user):
raise PermissionDenied(_("may not view other people's sessions"))
else:
raise PermissionDenied()
if flow_session.course.pk != pctx.course.pk:
raise SuspiciousOperation()
return flow_session
def will_receive_feedback(permissions):
return (
flow_permission.see_correctness in permissions
or flow_permission.see_answer_after_submission in permissions)
Andreas Klöckner
committed
def get_page_behavior(page, permissions, session_in_progress, answer_was_graded,
generates_grade, is_unenrolled_session, viewing_prior_version=False):
show_correctness = None
show_answer = None
if page.expects_answer() and answer_was_graded:
show_correctness = flow_permission.see_correctness in permissions
show_answer = flow_permission.see_answer_after_submission in permissions
elif page.expects_answer() and not answer_was_graded:
# 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))
)
from course.page.base import PageBehavior
return PageBehavior(
show_correctness=show_correctness,
show_answer=show_answer,
def add_buttons_to_form(form, fpctx, flow_session, permissions):
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"),
" »")),
def create_flow_page_visit(request, flow_session, page_data):
FlowPageVisit(
flow_session=flow_session,
page_data=page_data,
remote_address=request.META['REMOTE_ADDR'],
is_submitted_answer=None).save()
@course_view
def view_flow_page(pctx, flow_session_id, ordinal):
request = pctx.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,
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
adjust_flow_session_page_data(pctx.repo, flow_session,
pctx.course.identifier, fpctx.flow_desc)
Andreas Klöckner
committed
now_datetime = get_now_or_fake_time(request)
access_rule = get_session_access_rule(
Andreas Klöckner
committed
flow_session, pctx.role, fpctx.flow_desc, now_datetime,
facilities=pctx.request.relate_facilities)
Andreas Klöckner
committed
grading_rule = get_session_grading_rule(
flow_session, pctx.role, 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
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
create_flow_page_visit(request, flow_session, fpctx.page_data)
prev_answer_visits = list(
get_prev_answer_visits_qset(fpctx.page_data))
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
# {{{ 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'")
viewing_prior_version = False
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
from relate.utils import as_local_time
messages.add_message(request, messages.INFO,
_("Viewing prior submission dated %(date)s.")
% {
"date": defaultfilters.date(
as_local_time(pvisit.visit_time),
"DATETIME_FORMAT"),
})
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:
session_minutes = (
now_datetime - flow_session.start_time).total_seconds() / 60
if flow_session.participation is not None:
time_factor = flow_session.participation.time_factor
all_page_data = get_all_page_data(flow_session)
"flow_desc": fpctx.flow_desc,
"ordinal": fpctx.ordinal,
"page_data": fpctx.page_data,
"percentage": int(100*(fpctx.ordinal+1) / flow_session.page_count),
"flow_session": flow_session,
"feedback": shown_feedback,
"correct_answer": correct_answer,
"show_correctness": page_behavior.show_correctness,
"may_change_answer": page_behavior.may_change_answer,
"may_change_graded_answer": (
page_behavior.may_change_answer
and
(flow_permission.change_answer in permissions)),
"will_receive_feedback": will_receive_feedback(permissions),
"show_answer": page_behavior.show_answer,
"session_minutes": session_minutes,
"time_factor": time_factor,
"expiration_mode_choices": expiration_mode_choices,
"expiration_mode_choice_count": len(expiration_mode_choices),
"expiration_mode": flow_session.expiration_mode,
"flow_session_interaction_kind": flow_session_interaction_kind,
fpctx, flow_session, generates_grade, all_page_data),
"prev_answer_visits": prev_answer_visits,
"prev_visit_id": prev_visit_id,
if fpctx.page.expects_answer() and fpctx.page.is_answer_gradable():
args["max_points"] = fpctx.page.max_points(fpctx.page_data)
return render_course_page(
pctx, "course/flow-page.html", args,
allow_instant_flow_requests=False)
def get_pressed_button(form):
buttons = ["save", "save_and_next", "save_and_finish", "submit"]
for button in buttons:
if button in form.data:
return button
raise SuspiciousOperation(_("could not find which button was pressed"))
@retry_transaction_decorator()
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
def post_flow_page(flow_session, fpctx, request, permissions, generates_grade):
page_context = fpctx.page_context
page_data = fpctx.page_data
prev_answer_visits = list(
get_prev_answer_visits_qset(fpctx.page_data))
submission_allowed = True
# reject answer update if permission not present
if flow_permission.submit_answer not in permissions:
messages.add_message(request, messages.ERROR,
_("Answer submission not allowed."))
submission_allowed = False
# reject if previous answer was final
if (prev_answer_visits
and prev_answer_visits[0].is_submitted_answer
and flow_permission.change_answer
not in permissions):
messages.add_message(request, messages.ERROR,
_("Already have final answer."))
submission_allowed = False
page_behavior = get_page_behavior(
page=fpctx.page,
permissions=permissions,
session_in_progress=flow_session.in_progress,
answer_was_graded=False,
generates_grade=generates_grade,
is_unenrolled_session=flow_session.participation is None)
form = fpctx.page.process_form_post(
fpctx.page_context, fpctx.page_data.data,
post_data=request.POST, files_data=request.FILES,
page_behavior=page_behavior)
pressed_button = get_pressed_button(form)
if submission_allowed and form.is_valid():
# {{{ form validated, process answer
messages.add_message(request, messages.SUCCESS,
_("Answer saved."))
answer_visit = FlowPageVisit()
answer_visit.flow_session = flow_session
answer_visit.page_data = fpctx.page_data
answer_visit.remote_address = request.META['REMOTE_ADDR']
answer_data = answer_visit.answer = fpctx.page.answer_data(
fpctx.page_context, fpctx.page_data.data,
form, request.FILES)
answer_visit.is_submitted_answer = pressed_button == "submit"
answer_visit.save()
prev_answer_visits.insert(0, answer_visit)
answer_was_graded = answer_visit.is_submitted_answer
page_behavior = get_page_behavior(
page=fpctx.page,
permissions=permissions,
session_in_progress=flow_session.in_progress,
answer_was_graded=answer_was_graded,
generates_grade=generates_grade,
is_unenrolled_session=flow_session.participation is None)
if fpctx.page.is_answer_gradable():
with translation.override(settings.RELATE_ADMIN_EMAIL_LOCALE):
feedback = fpctx.page.grade(
page_context, page_data.data, answer_visit.answer,
grade_data=None)
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
if answer_visit.is_submitted_answer:
grade = FlowPageVisitGrade()
grade.visit = answer_visit
grade.max_points = fpctx.page.max_points(page_data.data)
grade.graded_at_git_commit_sha = fpctx.course_commit_sha
bulk_feedback_json = None
if feedback is not None:
grade.correctness = feedback.correctness
grade.feedback, bulk_feedback_json = feedback.as_json()
grade.save()
update_bulk_feedback(page_data, grade, bulk_feedback_json)
del grade
else:
feedback = None
if (pressed_button == "save_and_next"
and not will_receive_feedback(permissions)):
return redirect("relate-view_flow_page",
fpctx.course.identifier,
flow_session.id,
fpctx.ordinal + 1)
elif (pressed_button == "save_and_finish"
and not will_receive_feedback(permissions)):
return redirect("relate-finish_flow_session_view",
fpctx.course.identifier, flow_session.id)
else:
# The form needs to be recreated here, although there
# already is a form from the process_form_post above. This
# is because the value of 'answer_was_graded' may have
# changed between then and now (and page_behavior with
# it)--and that value depends on form validity, which we
# can only decide once we have a form.
form = fpctx.page.make_form(
page_context, page_data.data,
answer_data, page_behavior)
# }}}
else:
# form did not validate
create_flow_page_visit(request, flow_session, fpctx.page_data)
answer_data = None
answer_was_graded = False
if prev_answer_visits:
answer_data = prev_answer_visits[0].answer
feedback = None
messages.add_message(request, messages.ERROR,
_("Failed to submit answer."))
return (
page_behavior,
prev_answer_visits,
form,
feedback,
answer_data,
answer_was_graded)
@course_view
def update_expiration_mode(pctx, flow_session_id):
if pctx.request.method != "POST":
raise SuspiciousOperation(_("only POST allowed"))
flow_session = get_object_or_404(FlowSession, id=flow_session_id)
if flow_session.participation != pctx.participation:
raise PermissionDenied(
_("may only change your own flow sessions"))
if not flow_session.in_progress:
raise PermissionDenied(
_("may only change in-progress flow sessions"))
expmode = pctx.request.POST.get("expiration_mode")
if not any(expmode == em_key
for em_key, _ in FLOW_SESSION_EXPIRATION_MODE_CHOICES):
raise SuspiciousOperation(_("invalid expiration mode"))
fctx = FlowContext(pctx.repo, pctx.course, flow_session.flow_id,
Andreas Klöckner
committed
participation=pctx.participation)
access_rule = get_session_access_rule(
flow_session, pctx.role, fctx.flow_desc,
get_now_or_fake_time(pctx.request),
facilities=pctx.request.relate_facilities)
if is_expiration_mode_allowed(expmode, access_rule.permissions):
flow_session.expiration_mode = expmode
flow_session.save()
return http.HttpResponse("OK")
else:
raise PermissionDenied()
# {{{ view: finish flow
def finish_flow_session_view(pctx, flow_session_id):
Andreas Klöckner
committed
# Does not need to be atomic: All writing to the db
# is done in 'finish_flow_session' below.
Andreas Klöckner
committed
now_datetime = get_now_or_fake_time(pctx.request)
request = pctx.request
flow_session_id = int(flow_session_id)
flow_session = get_and_check_flow_session(
pctx, flow_session_id)
fctx = FlowContext(pctx.repo, pctx.course, flow_id,
Andreas Klöckner
committed
participation=pctx.participation)
access_rule = get_session_access_rule(
flow_session, pctx.role, fctx.flow_desc, now_datetime,
facilities=pctx.request.relate_facilities)
answer_visits = assemble_answer_visits(flow_session)
from course.content import markup_to_html
completion_text = markup_to_html(
fctx.course, fctx.repo, pctx.course_commit_sha,
getattr(fctx.flow_desc, "completion_text", ""))
Andreas Klöckner
committed
adjust_flow_session_page_data(pctx.repo, flow_session, pctx.course.identifier,
fctx.flow_desc)
(answered_count, unanswered_count) = count_answered_gradable(
fctx, flow_session, answer_visits)
is_graded_flow = bool(answered_count + unanswered_count)
if flow_permission.view not in access_rule.permissions:
raise PermissionDenied()
def render_finish_response(template, **kwargs):
render_args = {
"flow_desc": fctx.flow_desc,
}
render_args.update(kwargs)
return render_course_page(
pctx, template, render_args,
allow_instant_flow_requests=False)
if request.method == "POST":
if "submit" not in request.POST:
raise SuspiciousOperation(_("odd POST parameters"))
if not flow_session.in_progress:
messages.add_message(request, messages.ERROR,
_("Cannot end a session that's already ended"))
if flow_permission.end_session not in access_rule.permissions:
raise PermissionDenied(
_("not permitted to end session"))
grading_rule = get_session_grading_rule(
flow_session, pctx.role, fctx.flow_desc, now_datetime)
Andreas Klöckner
committed
grade_info = finish_flow_session(
fctx, flow_session, grading_rule,
Andreas Klöckner
committed
now_datetime=now_datetime)
# {{{ send notify email if requested
if (hasattr(fctx.flow_desc, "notify_on_submit")
and fctx.flow_desc.notify_on_submit):
if (grading_rule.grade_identifier
and flow_session.participation is not None):
from course.models import get_flow_grading_opportunity
review_uri = reverse("relate-view_single_grade",
args=(
pctx.course.identifier,
flow_session.participation.id,
get_flow_grading_opportunity(
pctx.course, flow_session.flow_id, fctx.flow_desc,
grading_rule).id))