def test_end(self): expmode = constants.flow_session_expiration_mode.end permissions = frozenset([constants.flow_permission.end_session]) self.assertTrue( constants.is_expiration_mode_allowed(expmode, permissions)) permissions = frozenset( [constants.flow_permission.set_roll_over_expiration_mode]) self.assertTrue( constants.is_expiration_mode_allowed(expmode, permissions))
def test_end(self): expmode = constants.flow_session_expiration_mode.end permissions = frozenset([constants.flow_permission.end_session]) self.assertTrue( constants.is_expiration_mode_allowed(expmode, permissions)) permissions = frozenset([ constants.flow_permission.set_roll_over_expiration_mode ]) self.assertTrue( constants.is_expiration_mode_allowed(expmode, permissions))
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, participation=pctx.participation, flow_session=flow_session ) access_rule = get_session_access_rule( flow_session, pctx.role, fctx.flow_desc, get_now_or_fake_time(pctx.request), pctx.remote_address ) if is_expiration_mode_allowed(expmode, access_rule.permissions): flow_session.expiration_mode = expmode flow_session.save() return http.HttpResponse("OK") else: raise PermissionDenied()
def test_unkown_mode(self): expmode = "unknown_mode" permissions = frozenset([]) expected_error_msg = "unknown expiration mode" with self.assertRaises(ValueError) as cm: self.assertTrue( constants.is_expiration_mode_allowed(expmode, permissions)) self.assertEqual(expected_error_msg, str(cm.exception)) permissions = frozenset([ constants.flow_permission.set_roll_over_expiration_mode, constants.flow_permission.end_session ]) with self.assertRaises(ValueError) as cm: self.assertTrue( constants.is_expiration_mode_allowed(expmode, permissions)) self.assertEqual(expected_error_msg, str(cm.exception))
def expire_flow_session(fctx, flow_session, grading_rule, now_datetime, past_due_only=False): if not flow_session.in_progress: raise RuntimeError(_("Can't expire a session that's not in progress")) if flow_session.participation is None: raise RuntimeError(_("Can't expire an anonymous flow session")) assert isinstance(grading_rule, FlowSessionGradingRule) if past_due_only and grading_rule.due is not None and now_datetime < grading_rule.due: return False if flow_session.expiration_mode == flow_session_expiration_mode.roll_over: session_start_rule = get_session_start_rule( flow_session.course, flow_session.participation, flow_session.participation.role, flow_session.flow_id, fctx.flow_desc, now_datetime, for_rollover=True, ) if not session_start_rule.may_start_new_session: # No new session allowed: finish. return finish_flow_session(fctx, flow_session, grading_rule, now_datetime=now_datetime) flow_session.access_rules_tag = session_start_rule.tag_session access_rule = get_session_access_rule( flow_session, flow_session.participation.role, fctx.flow_desc, now_datetime ) if not is_expiration_mode_allowed(flow_session.expiration_mode, access_rule.permissions): flow_session.expiration_mode = flow_session_expiration_mode.end flow_session.save() return True elif flow_session.expiration_mode == flow_session_expiration_mode.end: return finish_flow_session(fctx, flow_session, grading_rule, now_datetime=now_datetime) else: raise ValueError( _("invalid expiration mode '%(mode)s' on flow session ID " "%(session_id)d") % {"mode": flow_session.expiration_mode, "session_id": flow_session.id} )
def 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) flow_id = flow_session.flow_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, flow_id) try: fpctx = FlowPageContext( pctx.repo, pctx.course, flow_id, ordinal, participation=pctx.participation, flow_session=flow_session ) except PageOrdinalOutOfRange: return redirect("relate-view_flow_page", pctx.course.identifier, flow_session.id, flow_session.page_count - 1) access_rule = get_session_access_rule( flow_session, pctx.role, fpctx.flow_desc, get_now_or_fake_time(request), pctx.remote_address ) permissions = fpctx.page.get_modified_permissions_for_page(access_rule.permissions) if access_rule.message: messages.add_message(request, messages.INFO, access_rule.message) 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")) if request.method == "POST": if "finish" in request.POST: return redirect("relate-finish_flow_session_view", pctx.course.identifier, flow_session_id) else: 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 ( fpctx.prev_answer_visit is not None and fpctx.prev_answer_visit.is_submitted_answer and flow_permission.change_answer not in permissions ): messages.add_message(request, messages.ERROR, _("Already have final answer.")) submission_allowed = False form = fpctx.page.post_form( fpctx.page_context, fpctx.page_data.data, post_data=request.POST, files_data=request.FILES ) pressed_button = get_pressed_button(form) if submission_allowed and form.is_valid(): # {{{ form validated, process answer messages.add_message(request, messages.INFO, _("Answer saved.")) page_visit = FlowPageVisit() page_visit.flow_session = flow_session page_visit.page_data = fpctx.page_data page_visit.remote_address = request.META["REMOTE_ADDR"] answer_data = page_visit.answer = fpctx.page.answer_data( fpctx.page_context, fpctx.page_data.data, form, request.FILES ) page_visit.is_submitted_answer = pressed_button == "submit" page_visit.save() answer_was_graded = page_visit.is_submitted_answer may_change_answer = not answer_was_graded or flow_permission.change_answer in permissions if fpctx.page.is_answer_gradable(): feedback = fpctx.page.grade(page_context, page_data.data, page_visit.answer, grade_data=None) if page_visit.is_submitted_answer: grade = FlowPageVisitGrade() grade.visit = page_visit grade.max_points = fpctx.page.max_points(page_data.data) grade.graded_at_git_commit_sha = pctx.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", pctx.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", pctx.course.identifier, flow_session_id) else: form = fpctx.page.make_form(page_context, page_data.data, page_visit.answer, not may_change_answer) # continue at common flow page generation below # }}} del page_visit else: # form did not validate create_flow_page_visit(request, flow_session, fpctx.page_data) answer_was_graded = False may_change_answer = True # because we were allowed this far in by the check above feedback = None # continue at common flow page generation below else: create_flow_page_visit(request, flow_session, fpctx.page_data) if fpctx.prev_answer_visit is not None: answer_was_graded = fpctx.prev_answer_visit.is_submitted_answer else: answer_was_graded = False may_change_answer = ( (not answer_was_graded or (flow_permission.change_answer in permissions)) # can happen if no answer was ever saved and flow_session.in_progress and (flow_permission.submit_answer in permissions) ) if fpctx.page.expects_answer(): if fpctx.prev_answer_visit is not None: answer_data = fpctx.prev_answer_visit.answer most_recent_grade = fpctx.prev_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 form = fpctx.page.make_form(page_context, page_data.data, answer_data, not may_change_answer) else: form = None feedback = None # start common flow page generation # defined at this point: # form, form_html, may_change_answer, answer_was_graded, feedback if form is not None and may_change_answer: form = add_buttons_to_form(form, fpctx, flow_session, permissions) show_correctness = None show_answer = None shown_feedback = None if fpctx.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 if show_correctness or show_answer: shown_feedback = feedback elif fpctx.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 ) title = fpctx.page.title(page_context, page_data.data) body = fpctx.page.body(page_context, page_data.data) if show_answer: correct_answer = fpctx.page.correct_answer(page_context, page_data.data, answer_data, grade_data) else: correct_answer = None # {{{ render flow page 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)) args = { "flow_identifier": fpctx.flow_id, "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, "page_numbers": zip(range(flow_session.page_count), range(1, flow_session.page_count + 1)), "title": title, "body": body, "form": form, "form_html": form_html, "feedback": shown_feedback, "correct_answer": correct_answer, "show_correctness": show_correctness, "may_change_answer": may_change_answer, "may_change_graded_answer": ((flow_permission.change_answer in permissions) and flow_session.in_progress), "will_receive_feedback": will_receive_feedback(permissions), "show_answer": show_answer, "expiration_mode_choices": expiration_mode_choices, "expiration_mode_choice_count": len(expiration_mode_choices), "expiration_mode": flow_session.expiration_mode, } 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)