Newer
Older
expiration_mode_choices = []
for key, descr in FLOW_SESSION_EXPIRATION_MODE_CHOICES:
if is_expiration_mode_allowed(key, permissions):
expiration_mode_choices.append((key, descr))
session_minutes = None
time_factor = 1
if flow_permission.see_session_time in permissions:
if not flow_session.in_progress:
end_time = as_local_time(flow_session.completion_time)
else:
end_time = now_datetime
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.page_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.page_ordinal",
[flow_session.id])
flow_page_ordinals_with_answers = set(row[0] for row in c.fetchall())
"page_ordinal": fpctx.page_ordinal,
"percentage": int(100 * (fpctx.page_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)
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()
page_ordinal = int(page_ordinal)
flow_session_id = int(flow_session_id)
flow_session = get_and_check_flow_session(pctx, flow_session_id)
page_data = get_object_or_404(
FlowPageData, flow_session=flow_session, page_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
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
prev_answer_visits = list(
get_prev_answer_visits_qset(fpctx.page_data))
# 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 LanguageOverride(course=fpctx.course):
feedback = fpctx.page.grade(
page_context, page_data.data, answer_visit.answer,
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
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.page_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)
# {{{ view: send interaction email to course staffs in flow pages
@course_view
def send_email_about_flow_page(pctx, flow_session_id, page_ordinal):
# {{{ check if interaction email is allowed for this page.
page_ordinal = int(page_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
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,
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
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()
# }}}
review_url = reverse(
"relate-view_flow_page",
kwargs={'course_identifier': pctx.course.identifier,
'flow_session_id': flow_session_id,
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():
from_email = getattr(
settings,
"STUDENT_INTERACT_EMAIL_FROM",
settings.ROBOT_EMAIL_FROM)
student_email = flow_session.participation.user.email
ta_email_list = Participation.objects.filter(
course=pctx.course,
roles__permissions__permission=pperm.assign_grade,
roles__identifier="ta",
).values_list("user__email", flat=True)
recipient_list = ta_email_list
if not recipient_list:
# instructors to receive the email
recipient_list = Participation.objects.filter(
course=pctx.course,
roles__permissions__permission=pperm.assign_grade,
roles__identifier="instructor"
with LanguageOverride(course=pctx.course):
from course.utils import will_use_masked_profile_for_email
if will_use_masked_profile_for_email(recipient_list):
username = pctx.participation.user.get_masked_profile()
else:
username = pctx.participation.user.get_full_name()
page_id = FlowPageData.objects.get(
flow_session=flow_session_id, page_ordinal=page_ordinal).page_id
from relate.utils import render_email_template
message = render_email_template(
"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, page_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
# }}}
# {{{ view: update page bookmark state
@course_view
def update_page_bookmark_state(pctx, flow_session_id, page_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,
page_ordinal=page_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):
# type: (CoursePageContext, int) -> http.HttpResponse
Andreas Klöckner
committed
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):
staff_email = (
fctx.flow_desc.notify_on_submit + [fctx.course.notify_email])
from course.utils import will_use_masked_profile_for_email
use_masked_profile = will_use_masked_profile_for_email(staff_email)
if flow_session.participation is None or flow_session.user is None:
use_masked_profile = False
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 LanguageOverride(course=pctx.course):
from relate.utils import render_email_template
participation = flow_session.participation
message = render_email_template("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"),
# {{{ view: unsubmit flow page
class UnsubmitFlowPageForm(forms.Form):
def __init__(self, *args, **kwargs):
# type: (*Any, **Any) -> None
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, page_ordinal):
# type: (CoursePageContext, int, int) -> http.HttpResponse
if pctx.participation is None:
raise PermissionDenied()
if not pctx.has_permission(pperm.reopen_flow_session):
raise PermissionDenied()
request = pctx.request
now_datetime = get_now_or_fake_time(request)
page_ordinal = int(page_ordinal)
flow_session_id = int(flow_session_id)
flow_session = get_and_check_flow_session(pctx, flow_session_id)
adjust_flow_session_page_data(pctx.repo, flow_session, pctx.course.identifier,
respect_preview=True)
page_data = get_object_or_404(
FlowPageData, flow_session=flow_session, page_ordinal=page_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, page_ordinal)
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, page_ordinal)
else:
form = UnsubmitFlowPageForm()
return render_course_page(pctx, "course/generic-course-form.html", {
"form_description": _("Re-allow Changes to Flow Page"),
"form": form
})
# }}}
# {{{ purge page view data
def get_pv_purgeable_courses_for_user_qs(user):
course_qs = Course.objects.all()
if user.is_superuser:
# do not filter queryset
pass
else:
course_qs = course_qs.filter(
participations__user=user,
participations__roles__permissions__permission=(
pperm.use_admin_interface))
return course_qs
class PurgePageViewData(StyledForm):
def __init__(self, user, *args, **kwargs):
self.helper = FormHelper()
super(PurgePageViewData, self).__init__(*args, **kwargs)
self.fields["course"] = forms.ModelChoiceField(
queryset=get_pv_purgeable_courses_for_user_qs(user),
required=True)
self.helper.add_input(
Submit("submit", _("Purge Page View Data"),
css_class="btn btn-danger"))
def purge_page_view_data(request):
purgeable_courses = get_pv_purgeable_courses_for_user_qs(request.user)
if not purgeable_courses.count():
raise PermissionDenied()
if request.method == 'POST':
form = PurgePageViewData(request.user, request.POST)
if form.is_valid():
if "submit" in request.POST:
course = form.cleaned_data["course"]
# This actually won't happen, because it will fail at
# form validation stage. We leave it here as a double
# check for data security.
if course not in list(purgeable_courses):
raise PermissionDenied()
from course.tasks import purge_page_view_data
async_res = purge_page_view_data.delay(course.id)
return redirect("relate-monitor_task", async_res.id)
else:
form = PurgePageViewData(request.user)
return render(request, "generic-form.html", {
"form_description": _("Purge Page View Data"),
"form": form
})
# }}}