Newer
Older
end_time - 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)
from django.db import connection
with connection.cursor() as c:
c.execute(
"SELECT DISTINCT course_flowpagedata.ordinal "
"FROM course_flowpagevisit "
"INNER JOIN course_flowpagedata "
"ON course_flowpagedata.id = course_flowpagevisit.page_data_id "
"WHERE course_flowpagedata.flow_session_id = %s "
"AND course_flowpagevisit.answer IS NOT NULL "
"ORDER BY course_flowpagedata.ordinal",
[flow_session.id])
flow_page_ordinals_with_answers = set(row[0] for row in c.fetchall())
"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,
"flow_page_ordinals_with_answers": flow_page_ordinals_with_answers,
"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,
"may_send_email_about_flow_page":
may_send_email_about_flow_page(permissions),
"expects_answer": fpctx.page.expects_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),
"viewing_prior_version": viewing_prior_version,
"prev_answer_visits": prev_answer_visits,
"prev_visit_id": prev_visit_id,
# Wrappers used by JavaScript template (tmpl) so as not to
# conflict with Django template's tag wrapper
"JQ_OPEN": '{%',
'JQ_CLOSE': '%}',
if fpctx.page.expects_answer() and fpctx.page.is_answer_gradable():
args["max_points"] = fpctx.page.max_points(fpctx.page_data)
args["page_expect_answer_and_gradable"] = True
if fpctx.page.is_optional_page:
assert not getattr(args, "max_points", None)
args["is_optional_page"] = True
return render_course_page(
pctx, "course/flow-page.html", args,
allow_instant_flow_requests=False)
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
@course_view
def get_prev_answer_visits_dropdown_content(pctx, flow_session_id, page_ordinal):
"""
:return: serialized prev_answer_visits items for past-submission-dropdown
"""
request = pctx.request
if not request.is_ajax() or request.method != "GET":
raise PermissionDenied()
try:
page_ordinal = int(page_ordinal)
flow_session_id = int(flow_session_id)
except ValueError:
raise http.Http404()
flow_session = get_and_check_flow_session(pctx, int(flow_session_id))
page_data = get_object_or_404(
FlowPageData, flow_session=flow_session, ordinal=page_ordinal)
prev_answer_visits = get_prev_answer_visits_qset(page_data)
def serialize(obj):
return {
"id": obj.id,
"visit_time": (
format_datetime_local(as_local_time(obj.visit_time))),
"is_submitted_answer": obj.is_submitted_answer,
}
return http.JsonResponse(
{"result": [serialize(visit) for visit in prev_answer_visits]})
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()
def post_flow_page(
flow_session, # type: FlowSession
fpctx, # type: FlowPageContext
request, # type: http.HttpRequest
permissions, # type: frozenset[Text]
generates_grade, # type: bool
):
# type: (...) -> Tuple[PageBehavior, List[FlowPageVisit], forms.Form, Optional[AnswerFeedback], Any, bool] # noqa
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
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
# 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(
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."))
Dong Zhuang
committed
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(
Dong Zhuang
committed
answer_visit.is_submitted_answer = pressed_button == "submit"
if hasattr(request, "relate_impersonate_original_user"):
answer_visit.impersonated_by = \
request.relate_impersonate_original_user
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,
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
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
Dong Zhuang
committed
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)
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
# {{{ view: send interaction email to course staffs in flow pages
@course_view
def send_email_about_flow_page(pctx, flow_session_id, ordinal):
# {{{ check if interaction email is allowed for this page.
ordinal = int(ordinal)
flow_session_id = int(flow_session_id)
flow_session = get_and_check_flow_session(pctx, flow_session_id)
flow_id = flow_session.flow_id
fpctx = FlowPageContext(pctx.repo, pctx.course, flow_id, ordinal,
participation=pctx.participation,
flow_session=flow_session,
request=pctx.request)
if fpctx.page is None:
raise http.Http404()
request = pctx.request
now_datetime = get_now_or_fake_time(request)
login_exam_ticket = get_login_exam_ticket(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)
permissions = fpctx.page.get_modified_permissions_for_page(
access_rule.permissions)
if not may_send_email_about_flow_page(permissions):
raise http.Http404()
# }}}
from django.conf import settings
from course.models import FlowSession
flow_session = get_object_or_404(
FlowSession, id=int(flow_session_id))
from course.models import FlowPageData
page_id = FlowPageData.objects.get(
flow_session=flow_session_id, ordinal=ordinal).page_id
review_url = reverse(
"relate-view_flow_page",
kwargs={'course_identifier': pctx.course.identifier,
'flow_session_id': flow_session_id,
'ordinal': ordinal
}
)
from six.moves.urllib.parse import urljoin
review_uri = urljoin(getattr(settings, "RELATE_BASE_URL"),
review_url)
if request.method == "POST":
form = FlowPageInteractionEmailForm(review_uri, request.POST)
if form.is_valid():
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
from_email = getattr(
settings,
"STUDENT_INTERACT_EMAIL_FROM",
settings.ROBOT_EMAIL_FROM)
student_email = flow_session.participation.user.email
from course.constants import (
participation_permission as pperm)
ta_email_list = Participation.objects.filter(
course=pctx.course,
roles__permissions__permission=pperm.assign_grade,
roles__identifier="ta",
).values_list("user__email", flat=True)
instructor_email_list = Participation.objects.filter(
course=pctx.course,
roles__permissions__permission=pperm.assign_grade,
roles__identifier="instructor"
).values_list("user__email", flat=True)
recipient_list = ta_email_list
if not recipient_list:
recipient_list = instructor_email_list
from django.utils import translation
with translation.override(settings.RELATE_ADMIN_EMAIL_LOCALE):
from django.template.loader import render_to_string
from course.utils import will_use_masked_profile_for_email
use_masked_profile_for_email = (
will_use_masked_profile_for_email(recipient_list)
)
if use_masked_profile_for_email:
username = pctx.participation.user.get_masked_profile()
else:
username = pctx.participation.user.get_full_name()
message = render_to_string(
"course/flow-page-interaction-email.txt", {
"page_id": page_id,
"flow_session_id": flow_session_id,
"course": pctx.course,
"question_text": form.cleaned_data["message"],
"review_uri": review_uri,
"username": username
})
from django.core.mail import EmailMessage
msg = EmailMessage(
subject=string_concat(
"[%(identifier)s:%(flow_id)s--%(page_id)s] ",
_("Interaction request from %(username)s"))
% {
'identifier': pctx.course_identifier,
'flow_id': flow_session_id,
'page_id': page_id,
'username': username
body=message,
from_email=from_email,
to=recipient_list,
)
# TODO: add instructors to msg.bcc according to
# settings in Course model.
msg.bcc = [student_email]
msg.reply_to = [student_email]
from relate.utils import get_outbound_mail_connection
msg.connection = get_outbound_mail_connection("student_interact")
msg.send()
messages.add_message(
request, messages.SUCCESS,
_("Email sent, and notice that you will "
"also receive a copy of the email."))
return redirect("relate-view_flow_page",
pctx.course.identifier, flow_session_id, ordinal)
form = FlowPageInteractionEmailForm(review_uri)
return render_course_page(
pctx, "course/generic-course-form.html", {
"form": form,
"form_description": _("Send interaction email"),
})
class FlowPageInteractionEmailForm(StyledForm):
def __init__(self, review_uri, *args, **kwargs):
super(FlowPageInteractionEmailForm, self).__init__(*args, **kwargs)
self.fields["message"] = forms.CharField(
required=True,
widget=forms.Textarea,
_("Your questions about page %s . ") % review_uri,
_("Notice that <strong>only</strong> questions "
"for that page will be answered."),
),
label=_("Message"))
self.helper.add_input(
Submit(
"submit", _("Send Email"),
css_class="relate-submit-button"))
def clean_message(self):
cleaned_data = super(FlowPageInteractionEmailForm, self).clean()
message = cleaned_data.get("message")
if len(message) < 20:
raise forms.ValidationError(
_("At least 20 characters are required for submission."))
return message
# }}}
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
# {{{ view: update page bookmark state
@course_view
def update_page_bookmark_state(pctx, flow_session_id, ordinal):
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"))
bookmark_state = pctx.request.POST.get("bookmark_state")
if bookmark_state not in ["0", "1"]:
raise SuspiciousOperation(_("invalid bookmark state"))
bookmark_state = bookmark_state == "1"
fpd = get_object_or_404(FlowPageData.objects,
flow_session=flow_session,
ordinal=ordinal)
fpd.bookmarked = bookmark_state
fpd.save()
return http.HttpResponse("OK")
# }}}
# {{{ view: update expiration mode
@course_view
def update_expiration_mode(pctx, flow_session_id):
Andreas Klöckner
committed
# type: (CoursePageContext, int) -> http.HttpRequest
if pctx.request.method != "POST":
raise SuspiciousOperation(_("only POST allowed"))
login_exam_ticket = get_login_exam_ticket(pctx.request)
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, fctx.flow_desc,
get_now_or_fake_time(pctx.request),
facilities=pctx.request.relate_facilities,
login_exam_ticket=login_exam_ticket)
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
# type: (CoursePageContext, int) -> http.HttpResponse
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)
login_exam_ticket = get_login_exam_ticket(pctx.request)
Andreas Klöckner
committed
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, fctx.flow_desc, now_datetime,
facilities=pctx.request.relate_facilities,
login_exam_ticket=login_exam_ticket)
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, respect_preview=True)
Andreas Klöckner
committed
answer_visits = assemble_answer_visits(flow_session) # type: List[Optional[FlowPageVisit]] # noqa
(answered_page_data_list, unanswered_page_data_list, is_interactive_flow) =\
get_session_answered_page_data(
fctx, flow_session, answer_visits)
if flow_permission.view not in access_rule.permissions:
raise PermissionDenied()
def render_finish_response(template, **kwargs):
Andreas Klöckner
committed
# type: (...) -> http.HttpResponse
"flow_desc": fctx.flow_desc,
}
render_args.update(kwargs)
return render_course_page(
pctx, template, render_args,
allow_instant_flow_requests=False)
grading_rule = get_session_grading_rule(
flow_session, fctx.flow_desc, now_datetime)
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"))
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):
from course.utils import will_use_masked_profile_for_email
staff_email = (
fctx.flow_desc.notify_on_submit + [fctx.course.notify_email])
use_masked_profile = will_use_masked_profile_for_email(staff_email)
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.grade_identifier,
grading_rule.grade_aggregation_strategy).id))
else:
review_uri = reverse("relate-view_flow_page",
args=(
pctx.course.identifier,
flow_session.id,
0))
with translation.override(settings.RELATE_ADMIN_EMAIL_LOCALE):
from django.template.loader import render_to_string
participation = flow_session.participation
message = render_to_string("course/submit-notify.txt", {
"course": fctx.course,
"flow_session": flow_session,
"use_masked_profile": use_masked_profile,
"review_uri": pctx.request.build_absolute_uri(review_uri)
})
participation_desc = repr(participation)
if use_masked_profile:
participation_desc = _(
"%(user)s in %(course)s as %(role)s") % {
"user": participation.user.get_masked_profile(),
"course": flow_session.course,
"role": "/".join(
role.identifier
for role in participation.roles.all())
}
from django.core.mail import EmailMessage
msg = EmailMessage(
string_concat("[%(identifier)s:%(flow_id)s] ",
_("Submission by %(participation_desc)s"))
% {'participation_desc': participation_desc,
'identifier': fctx.course.identifier,
'flow_id': flow_session.flow_id},
message,
getattr(settings, "NOTIFICATION_EMAIL_FROM",
settings.ROBOT_EMAIL_FROM),
fctx.flow_desc.notify_on_submit)
msg.bcc = [fctx.course.notify_email]
from relate.utils import get_outbound_mail_connection
get_outbound_mail_connection("notification")
if hasattr(settings, "NOTIFICATION_EMAIL_FROM")
else get_outbound_mail_connection("robot"))
if is_interactive_flow:
if flow_permission.cannot_see_flow_result in access_rule.permissions:
grade_info = None
return render_finish_response(
"course/flow-completion-grade.html",
completion_text=completion_text,
grade_info=grade_info)
else:
return render_finish_response(
"course/flow-completion.html",
last_page_nr=None,
flow_session=flow_session,
completion_text=completion_text)
if (not is_interactive_flow
(flow_session.in_progress
and flow_permission.end_session not in access_rule.permissions)):
# No ability to end--just show completion page.
return render_finish_response(
"course/flow-completion.html",
flow_session=flow_session,
completion_text=completion_text)
elif not flow_session.in_progress:
# Just reviewing: re-show grades.
grade_info = gather_grade_info(
fctx, flow_session, grading_rule, answer_visits)
if flow_permission.cannot_see_flow_result in access_rule.permissions:
grade_info = None
return render_finish_response(
"course/flow-completion-grade.html",
completion_text=completion_text,
grade_info=grade_info)
else:
# confirm ending flow
answered_count = len(answered_page_data_list)
unanswered_count = len(unanswered_page_data_list)
required_count = answered_count + unanswered_count
session_may_generate_grade = (
grading_rule.generates_grade and required_count)
return render_finish_response(
"course/flow-confirm-completion.html",
flow_session=flow_session,
answered_count=answered_count,
unanswered_count=unanswered_count,
unanswered_page_data_list=unanswered_page_data_list,
required_count=required_count,
session_may_generate_grade=session_may_generate_grade)
# {{{ view: regrade flow
class RegradeFlowForm(StyledForm):
def __init__(self, flow_ids, *args, **kwargs):
Andreas Klöckner
committed
# type: (List[Text], *Any, **Any) -> None
super(RegradeFlowForm, self).__init__(*args, **kwargs)
self.fields["flow_id"] = forms.ChoiceField(
choices=[(fid, fid) for fid in flow_ids],
label=_("Flow ID"),
widget=Select2Widget())
self.fields["access_rules_tag"] = forms.CharField(
help_text=_("If non-empty, limit the regrading to sessions "
"started under this access rules tag."),
self.fields["regraded_session_in_progress"] = forms.ChoiceField(
choices=(
_("Regrade in-progress and not-in-progress sessions")),
_("Regrade in-progress sessions only")),
_("Regrade not-in-progress sessions only")),
),
label=_("Regraded session in progress"))
Andreas Klöckner
committed
# type: (CoursePageContext) -> http.HttpResponse
if not pctx.has_permission(pperm.batch_regrade_flow_session):
Andreas Klöckner
committed
raise PermissionDenied(_("may not batch-regrade flows"))
from course.content import list_flow_ids
flow_ids = list_flow_ids(pctx.repo, pctx.course_commit_sha)
request = pctx.request
if request.method == "POST":
form = RegradeFlowForm(flow_ids, request.POST, request.FILES)
if form.is_valid():
inprog_value = {
"any": None,
"yes": True,
"no": False,
}[form.cleaned_data["regraded_session_in_progress"]]
from course.tasks import regrade_flow_sessions
async_res = regrade_flow_sessions.delay(
pctx.course.id,
form.cleaned_data["flow_id"],
form.cleaned_data["access_rules_tag"],
inprog_value)
return redirect("relate-monitor_task", async_res.id)
else:
form = RegradeFlowForm(flow_ids)
return render_course_page(pctx, "course/generic-course-form.html", {
"form": form,
"form_text": string_concat(
"<p>",
_("This regrading process is only intended for flows that do"
"not show up in the grade book. If you would like to regrade"
"for-credit flows, use the corresponding functionality in "
"form_description": _("Regrade not-for-credit Flow Sessions"),
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
# {{{ view: unsubmit flow page
class UnsubmitFlowPageForm(forms.Form):
def __init__(self, *args, **kwargs):
self.helper = FormHelper()
super(UnsubmitFlowPageForm, self).__init__(*args, **kwargs)
self.helper.add_input(Submit("submit", _("Re-allow changes")))
self.helper.add_input(Submit("cancel", _("Cancel")))
@course_view
def view_unsubmit_flow_page(pctx, flow_session_id, ordinal):
# type: (CoursePageContext, int, int) -> http.HttpResponse
request = pctx.request
now_datetime = get_now_or_fake_time(request)
ordinal = int(ordinal)
flow_session_id = int(flow_session_id)
try:
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()
adjust_flow_session_page_data(pctx.repo, flow_session, pctx.course.identifier,
respect_preview=True)
# {{{ permission checking
if flow_session is None:
raise SuspiciousOperation("no flow session found")
if pctx.participation is None:
raise http.Http403()
if flow_session.course.pk != pctx.participation.course.pk:
raise http.Http403()
if not pctx.has_permission(pperm.reopen_flow_session):
raise http.Http403()
# }}}
page_data = get_object_or_404(
FlowPageData, flow_session=flow_session, ordinal=ordinal)
visit = get_first_from_qset(
get_prev_answer_visits_qset(page_data)
.filter(is_submitted_answer=True))
if visit is None:
messages.add_message(request, messages.INFO,
_("No prior answers found that could be un-submitted."))
return redirect("relate-view_flow_page",
pctx.course.identifier, flow_session_id, ordinal)
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
if request.method == 'POST':
form = UnsubmitFlowPageForm(request.POST)
if form.is_valid():
if "submit" in request.POST:
unsubmit_page(visit, now_datetime)
messages.add_message(request, messages.INFO,
_("Flow page changes reallowed. "))
return redirect("relate-view_flow_page",
pctx.course.identifier, flow_session_id, ordinal)
else:
form = UnsubmitFlowPageForm()
return render_course_page(pctx, "course/generic-course-form.html", {
"form_description": _("Re-allow Changes to Flow Page"),
"form": form
})
# }}}