Newer
Older
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,
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)
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
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
# 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,
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
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)
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
# {{{ 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():
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
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
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": pctx.participation.user.get_full_name()
})
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': pctx.participation.user.get_full_name()
},
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
# }}}
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
# {{{ 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):
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
message = render_to_string("course/submit-notify.txt", {
"course": fctx.course,
"flow_session": flow_session,
"review_uri": pctx.request.build_absolute_uri(review_uri)
})
from django.core.mail import EmailMessage
msg = EmailMessage(
string_concat("[%(identifier)s:%(flow_id)s] ",
_("Submission by %(participation)s"))
% {'participation': flow_session.participation,
'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"),
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
# {{{ 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_prev_answer_visit(page_data)
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
})
# }}}