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 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 grant_exception_stage_3(pctx, participation_id, flow_id, session_id): # type: (CoursePageContext, int, Text, int) -> http.HttpResponse if not pctx.has_permission(pperm.grant_exception): raise PermissionDenied(_("may not grant exceptions")) participation = get_object_or_404(Participation, id=participation_id) from course.content import get_flow_desc try: flow_desc = get_flow_desc(pctx.repo, pctx.course, flow_id, pctx.course_commit_sha) except ObjectDoesNotExist: raise http.Http404() session = FlowSession.objects.get(id=int(session_id)) now_datetime = get_now_or_fake_time(pctx.request) from course.utils import (get_session_access_rule, get_session_grading_rule) access_rule = get_session_access_rule(session, flow_desc, now_datetime) grading_rule = get_session_grading_rule(session, flow_desc, now_datetime) request = pctx.request if request.method == "POST": form = ExceptionStage3Form({}, flow_desc, session.access_rules_tag, request.POST) from course.constants import flow_rule_kind if form.is_valid(): permissions = [ key for key, _ in FLOW_PERMISSION_CHOICES if form.cleaned_data[key] ] from course.validation import (validate_session_access_rule, validate_session_grading_rule, ValidationContext) from relate.utils import dict_to_struct vctx = ValidationContext(repo=pctx.repo, commit_sha=pctx.course_commit_sha) flow_desc = get_flow_desc(pctx.repo, pctx.course, flow_id, pctx.course_commit_sha) tags = [] # type: List[Text] if hasattr(flow_desc, "rules"): try: from typing import Text # noqa except ImportError: Text = None # noqa tags = cast(List[Text], getattr(flow_desc.rules, "tags", [])) # type: ignore # noqa # {{{ put together access rule if form.cleaned_data["create_access_exception"]: new_access_rule = {"permissions": permissions} if (form.cleaned_data.get("restrict_to_same_tag") and session.access_rules_tag is not None): new_access_rule["if_has_tag"] = session.access_rules_tag validate_session_access_rule( vctx, ugettext("newly created exception"), dict_to_struct(new_access_rule), tags) fre_access = FlowRuleException( flow_id=flow_id, participation=participation, expiration=form.cleaned_data["access_expires"], creator=pctx.request.user, comment=form.cleaned_data["comment"], kind=flow_rule_kind.access, rule=new_access_rule) fre_access.save() # }}} new_access_rules_tag = form.cleaned_data.get( "set_access_rules_tag") if new_access_rules_tag == NONE_SESSION_TAG: new_access_rules_tag = None if session.access_rules_tag != new_access_rules_tag: session.access_rules_tag = new_access_rules_tag session.save() # {{{ put together grading rule if form.cleaned_data["create_grading_exception"]: due = form.cleaned_data["due"] if form.cleaned_data["due_same_as_access_expiration"]: due = form.cleaned_data["access_expires"] descr = ugettext("Granted excecption") if form.cleaned_data["credit_percent"] is not None: descr += string_concat(" (%.1f%% ", ugettext('credit'), ")") \ % form.cleaned_data["credit_percent"] due_local_naive = due if due_local_naive is not None: from relate.utils import as_local_time due_local_naive = (as_local_time(due_local_naive).replace( tzinfo=None)) new_grading_rule = { "description": descr, } if due_local_naive is not None: new_grading_rule["due"] = due_local_naive new_grading_rule["if_completed_before"] = due_local_naive for attr_name in [ "credit_percent", "bonus_points", "max_points", "max_points_enforced_cap", "generates_grade" ]: if form.cleaned_data[attr_name] is not None: new_grading_rule[attr_name] = form.cleaned_data[ attr_name] if (form.cleaned_data.get("restrict_to_same_tag") and session.access_rules_tag is not None): new_grading_rule["if_has_tag"] = session.access_rules_tag validate_session_grading_rule( vctx, ugettext("newly created exception"), dict_to_struct(new_grading_rule), tags, grading_rule.grade_identifier) fre_grading = FlowRuleException( flow_id=flow_id, participation=participation, creator=pctx.request.user, comment=form.cleaned_data["comment"], kind=flow_rule_kind.grading, rule=new_grading_rule) fre_grading.save() # }}} messages.add_message( pctx.request, messages.SUCCESS, ugettext("Exception granted to '%(participation)s' " "for '%(flow_id)s'.") % { 'participation': participation, 'flow_id': flow_id }) return redirect("relate-grant_exception", pctx.course.identifier) else: data = { "restrict_to_same_tag": session.access_rules_tag is not None, #"due_same_as_access_expiration": True, "due": grading_rule.due, "generates_grade": grading_rule.generates_grade, "credit_percent": grading_rule.credit_percent, "bonus_points": grading_rule.bonus_points, "max_points": grading_rule.max_points, "max_points_enforced_cap": grading_rule.max_points_enforced_cap, } for perm in access_rule.permissions: data[perm] = True form = ExceptionStage3Form(data, flow_desc, session.access_rules_tag) return render_course_page( pctx, "course/generic-course-form.html", { "form": form, "form_description": ugettext("Grant Exception"), "form_text": string_concat( "<div class='well'>", ugettext("Granting exception to '%(participation)s' " "for '%(flow_id)s' (session %(session)s)."), "</div>") % { 'participation': participation, 'flow_id': flow_id, 'session': strify_session_for_exception(session) }, })
def grant_exception_stage_3(pctx, participation_id, flow_id, session_id): # type: (CoursePageContext, int, Text, int) -> http.HttpResponse if not pctx.has_permission(pperm.grant_exception): raise PermissionDenied(_("may not grant exceptions")) participation = get_object_or_404(Participation, id=participation_id) from course.content import get_flow_desc try: flow_desc = get_flow_desc(pctx.repo, pctx.course, flow_id, pctx.course_commit_sha) except ObjectDoesNotExist: raise http.Http404() session = FlowSession.objects.get(id=int(session_id)) now_datetime = get_now_or_fake_time(pctx.request) from course.utils import ( get_session_access_rule, get_session_grading_rule) access_rule = get_session_access_rule(session, flow_desc, now_datetime) grading_rule = get_session_grading_rule(session, flow_desc, now_datetime) request = pctx.request if request.method == "POST": form = ExceptionStage3Form( {}, flow_desc, session.access_rules_tag, request.POST) from course.constants import flow_rule_kind if form.is_valid(): permissions = [ key for key, _ in FLOW_PERMISSION_CHOICES if form.cleaned_data[key]] from course.validation import ( validate_session_access_rule, validate_session_grading_rule, ValidationContext) from relate.utils import dict_to_struct vctx = ValidationContext( repo=pctx.repo, commit_sha=pctx.course_commit_sha) flow_desc = get_flow_desc(pctx.repo, pctx.course, flow_id, pctx.course_commit_sha) tags = None if hasattr(flow_desc, "rules"): tags = getattr(flow_desc.rules, "tags", None) # {{{ put together access rule if form.cleaned_data["create_access_exception"]: new_access_rule = {"permissions": permissions} if (form.cleaned_data.get("restrict_to_same_tag") and session.access_rules_tag is not None): new_access_rule["if_has_tag"] = session.access_rules_tag validate_session_access_rule( vctx, ugettext("newly created exception"), dict_to_struct(new_access_rule), tags) fre_access = FlowRuleException( flow_id=flow_id, participation=participation, expiration=form.cleaned_data["access_expires"], creator=pctx.request.user, comment=form.cleaned_data["comment"], kind=flow_rule_kind.access, rule=new_access_rule) fre_access.save() # }}} new_access_rules_tag = form.cleaned_data.get("set_access_rules_tag") if new_access_rules_tag == NONE_SESSION_TAG: new_access_rules_tag = None if session.access_rules_tag != new_access_rules_tag: session.access_rules_tag = new_access_rules_tag session.save() # {{{ put together grading rule if form.cleaned_data["create_grading_exception"]: due = form.cleaned_data["due"] if form.cleaned_data["due_same_as_access_expiration"]: due = form.cleaned_data["access_expires"] descr = ugettext("Granted excecption") if form.cleaned_data["credit_percent"] is not None: descr += string_concat(" (%.1f%% ", ugettext('credit'), ")") \ % form.cleaned_data["credit_percent"] due_local_naive = due if due_local_naive is not None: from relate.utils import as_local_time due_local_naive = ( as_local_time(due_local_naive) .replace(tzinfo=None)) new_grading_rule = { "description": descr, } if due_local_naive is not None: new_grading_rule["due"] = due_local_naive new_grading_rule["if_completed_before"] = due_local_naive for attr_name in ["credit_percent", "bonus_points", "max_points", "max_points_enforced_cap"]: if form.cleaned_data[attr_name] is not None: new_grading_rule[attr_name] = form.cleaned_data[attr_name] if (form.cleaned_data.get("restrict_to_same_tag") and session.access_rules_tag is not None): new_grading_rule["if_has_tag"] = session.access_rules_tag if hasattr(grading_rule, "generates_grade"): new_grading_rule["generates_grade"] = \ grading_rule.generates_grade validate_session_grading_rule( vctx, ugettext("newly created exception"), dict_to_struct(new_grading_rule), tags, grading_rule.grade_identifier) fre_grading = FlowRuleException( flow_id=flow_id, participation=participation, creator=pctx.request.user, comment=form.cleaned_data["comment"], kind=flow_rule_kind.grading, rule=new_grading_rule) fre_grading.save() # }}} messages.add_message(pctx.request, messages.SUCCESS, ugettext( "Exception granted to '%(participation)s' " "for '%(flow_id)s'.") % { 'participation': participation, 'flow_id': flow_id}) return redirect( "relate-grant_exception", pctx.course.identifier) else: data = { "restrict_to_same_tag": session.access_rules_tag is not None, "credit_percent": grading_rule.credit_percent, #"due_same_as_access_expiration": True, "due": grading_rule.due, } for perm in access_rule.permissions: data[perm] = True form = ExceptionStage3Form(data, flow_desc, session.access_rules_tag) return render_course_page(pctx, "course/generic-course-form.html", { "form": form, "form_description": ugettext("Grant Exception"), "form_text": string_concat( "<div class='well'>", ugettext("Granting exception to '%(participation)s' " "for '%(flow_id)s' (session %(session)s)."), "</div>") % { 'participation': participation, 'flow_id': flow_id, 'session': strify_session_for_exception(session)}, })
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)
def view_start_flow(pctx, flow_id): request = pctx.request now_datetime = get_now_or_fake_time(request) fctx = FlowContext(pctx.repo, pctx.course, flow_id, participation=pctx.participation) session_start_rule = get_session_start_rule( pctx.course, pctx.participation, pctx.role, flow_id, fctx.flow_desc, now_datetime, remote_address=pctx.remote_address, ) if request.method == "POST": if "start" in request.POST: if not session_start_rule.may_start_new_session: raise PermissionDenied(_("new session not allowed")) session = start_flow( pctx.repo, pctx.course, pctx.participation, flow_id, fctx.flow_desc, access_rules_tag=session_start_rule.tag_session, now_datetime=now_datetime, ) return redirect("relate-view_flow_page", pctx.course.identifier, session.id, 0) else: raise SuspiciousOperation(_("unrecognized POST action")) else: 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", ["may_view", "may_modify", "due", "grade_description"] # noqa ) past_sessions_and_properties = [] for session in past_sessions: access_rule = get_session_access_rule( session, pctx.role, fctx.flow_desc, now_datetime, remote_address=pctx.remote_address ) 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, ) 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 [ None, grade_aggregation_strategy.max_grade, grade_aggregation_strategy.use_earliest, ] return render_course_page( pctx, "course/flow-start.html", { "flow_desc": fctx.flow_desc, "flow_identifier": flow_id, "now": now_datetime, "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, )
def finish_flow_session_view(pctx, flow_session_id): 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) flow_id = flow_session.flow_id fctx = FlowContext(pctx.repo, pctx.course, flow_id, participation=pctx.participation, flow_session=flow_session) access_rule = get_session_access_rule(flow_session, pctx.role, fctx.flow_desc, now_datetime, pctx.remote_address) 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, fctx.flow_desc.completion_text) (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_identifier": fctx.flow_id, "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: raise PermissionDenied(_("Can't 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) grade_info = finish_flow_session(fctx, flow_session, grading_rule, now_datetime=now_datetime) if is_graded_flow: 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_graded_flow or (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", last_page_nr=flow_session.page_count - 1, 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, answer_visits) return render_finish_response( "course/flow-completion-grade.html", completion_text=completion_text, grade_info=grade_info ) else: # confirm ending flow return render_finish_response( "course/flow-confirm-completion.html", last_page_nr=flow_session.page_count - 1, flow_session=flow_session, answered_count=answered_count, unanswered_count=unanswered_count, total_count=answered_count + unanswered_count, )
def grant_exception_stage_3(pctx, participation_id, flow_id, session_id): if pctx.role not in [ participation_role.instructor, participation_role.teaching_assistant]: raise PermissionDenied("must be instructor or TA to grant exceptions") participation = get_object_or_404(Participation, id=participation_id) from course.content import get_flow_desc try: flow_desc = get_flow_desc(pctx.repo, pctx.course, flow_id, pctx.course_commit_sha) except ObjectDoesNotExist: raise http.Http404() session = FlowSession.objects.get(id=int(session_id)) now_datetime = get_now_or_fake_time(pctx.request) from course.utils import ( get_session_access_rule, get_session_grading_rule) access_rule = get_session_access_rule( session, pctx.role, flow_desc, now_datetime) grading_rule = get_session_grading_rule( session, pctx.role, flow_desc, now_datetime) request = pctx.request if request.method == "POST": form = ExceptionStage3Form({}, session.access_rules_tag, request.POST) from course.constants import flow_rule_kind if form.is_valid(): permissions = [ key for key, _ in FLOW_PERMISSION_CHOICES if form.cleaned_data[key]] from course.validation import ( validate_session_access_rule, validate_session_grading_rule, ValidationContext) from relate.utils import dict_to_struct vctx = ValidationContext( repo=pctx.repo, commit_sha=pctx.course_commit_sha) from course.content import get_flow_desc flow_desc = get_flow_desc(pctx.repo, pctx.course, flow_id, pctx.course_commit_sha) tags = None if hasattr(flow_desc, "rules"): tags = getattr(flow_desc.rules, "tags", None) # {{{ put together access rule new_access_rule = {"permissions": permissions} if (form.cleaned_data["restrict_to_same_tag"] and session.access_rules_tag is not None): new_access_rule["if_has_tag"] = session.access_rules_tag validate_session_access_rule(vctx, "newly created exception", dict_to_struct(new_access_rule), tags) fre_access = FlowRuleException( flow_id=flow_id, participation=participation, expiration=form.cleaned_data["access_expires"], creator=pctx.request.user, comment=form.cleaned_data["comment"], kind=flow_rule_kind.access, rule=new_access_rule) fre_access.save() # }}} # {{{ put together grading rule due = form.cleaned_data["due"] if form.cleaned_data["due_same_as_access_expiration"]: due = form.cleaned_data["access_expires"] descr = "Granted excecption" if form.cleaned_data["credit_percent"] is not None: descr += " (%.1f%% credit)" % form.cleaned_data["credit_percent"] from relate.utils import as_local_time new_grading_rule = { "credit_percent": form.cleaned_data["credit_percent"], "due": as_local_time(due).replace(tzinfo=None), "if_completed_before": as_local_time(due).replace(tzinfo=None), "description": descr, } if (form.cleaned_data["restrict_to_same_tag"] and session.access_rules_tag is not None): new_grading_rule["if_has_tag"] = session.access_rules_tag if hasattr(grading_rule, "grade_identifier"): new_grading_rule["grade_identifier"] = \ grading_rule.grade_identifier if hasattr(grading_rule, "grade_aggregation_strategy"): new_grading_rule["grade_aggregation_strategy"] = \ grading_rule.grade_aggregation_strategy validate_session_grading_rule(vctx, "newly created exception", dict_to_struct(new_grading_rule), tags) fre_grading = FlowRuleException( flow_id=flow_id, participation=participation, creator=pctx.request.user, comment=form.cleaned_data["comment"], kind=flow_rule_kind.grading, rule=new_grading_rule) fre_grading.save() # }}} messages.add_message(pctx.request, messages.SUCCESS, "Exception granted to '%s' for '%s'." % (participation, flow_id)) return redirect( "course.views.grant_exception", pctx.course.identifier) else: data = { "restrict_to_same_tag": session.access_rules_tag is not None, "credit_percent": grading_rule.credit_percent, #"due_same_as_access_expiration": True, "due": grading_rule.due, } for perm in access_rule.permissions: data[perm] = True form = ExceptionStage3Form(data, session.access_rules_tag) return render_course_page(pctx, "course/generic-course-form.html", { "form": form, "form_description": "Grant Exception", "form_text": "<div class='well'>Granting exception to '%s' for '%s'.</div>" % (participation, flow_id), })