Skip to content
flow.py 50.9 KiB
Newer Older
                    pctx.course.identifier, flow_session_id)
            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
Andreas Klöckner's avatar
Andreas Klöckner committed
                    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,
                page_visit.is_submitted_answer = pressed_button == "submit"
                answer_was_graded = page_visit.is_submitted_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,
                            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)
Andreas Klöckner's avatar
Andreas Klöckner committed
                            page_context, page_data.data,
                            page_visit.answer, not may_change_answer)

                    # continue at common flow page generation below

                # }}}

            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
                # 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
        may_change_answer = (
                (not answer_was_graded
                    or (flow_permission.change_answer in permissions))
                # can happen if no answer was ever saved
                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

Andreas Klöckner's avatar
Andreas Klöckner committed
                    page_context, page_data.data,
                    answer_data, not may_change_answer)
            form = None

    # start common flow page generation

    # 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,

    show_correctness = None
    show_answer = 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:
    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)
        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

Andreas Klöckner's avatar
Andreas Klöckner committed
    # {{{ render flow page

        form_html = fpctx.page.form_to_html(
                pctx.request, page_context, form, answer_data)
    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))

Andreas Klöckner's avatar
Andreas Klöckner committed
    args = {
        "flow_identifier": fpctx.flow_id,
Andreas Klöckner's avatar
Andreas Klöckner committed
        "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)),
Andreas Klöckner's avatar
Andreas Klöckner committed
        "title": title, "body": body,
        "form": form,
Andreas Klöckner's avatar
Andreas Klöckner committed
        "form_html": form_html,
        "correct_answer": correct_answer,

Andreas Klöckner's avatar
Andreas Klöckner committed
        "show_correctness": show_correctness,
        "may_change_answer": may_change_answer,
        "may_change_graded_answer": (
            (flow_permission.change_answer
                        in permissions)
        "will_receive_feedback": will_receive_feedback(permissions),
Andreas Klöckner's avatar
Andreas Klöckner committed
        "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():
Andreas Klöckner's avatar
Andreas Klöckner committed
        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)

@course_view
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()


@transaction.atomic
def finish_flow_session_view(pctx, flow_session_id):
    now_datetime = get_now_or_fake_time(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)
                fctx, flow_session, grading_rule,
            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_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)

# {{{ view: regrade flow

class RegradeFlowForm(StyledForm):
    def __init__(self, flow_ids, *args, **kwargs):
        super(RegradeFlowForm, self).__init__(*args, **kwargs)

        self.fields["flow_id"] = forms.ChoiceField(
                choices=[(fid, fid) for fid in flow_ids],
                initial=participation_role.student,
ifaint's avatar
ifaint committed
                required=True,
                label=_("Flow ID"))
        self.fields["access_rules_tag"] = forms.CharField(
                required=False,
                help_text=_("If non-empty, limit the regrading to sessions "
                "started under this access rules tag."),
ifaint's avatar
ifaint committed
                label=_("Access rules tag"))
        self.fields["regraded_session_in_progress"] = forms.ChoiceField(
                choices=(
                    ("any", 
                        _("Regrade in-progress and not-in-progress sessions")),
                    ("yes", 
                        _("Regrade in-progress sessions only")),
                    ("no", 
                        _("Regrade not-in-progress sessions only")),
ifaint's avatar
ifaint committed
                    ),
                label=_("Regraded session in progress"))

        self.helper.add_input(
ifaint's avatar
ifaint committed
                Submit("regrade", _("Regrade"), css_class="col-lg-offset-2"))


@transaction.atomic
def _regrade_sessions(repo, course, sessions):
    count = 0

    from course.flow import regrade_session
    for session in sessions:
        regrade_session(repo, course, session)
        count += 1

    return count


@course_view
def regrade_not_for_credit_flows_view(pctx):
    if pctx.role != participation_role.instructor:
ifaint's avatar
ifaint committed
        raise PermissionDenied(_("must be instructor to 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():
            sessions = (FlowSession.objects
                    .filter(
                        course=pctx.course,
                        flow_id=form.cleaned_data["flow_id"]))
            if form.cleaned_data["access_rules_tag"]:
                sessions = sessions.filter(
                        access_rules_tag=form.cleaned_data["access_rules_tag"])

            inprog_value = {
                    "any": None,
                    "yes": True,
                    "no": False,
                    }[form.cleaned_data["regraded_session_in_progress"]]

            if inprog_value is not None:
                sessions = sessions.filter(
                        in_progress=inprog_value)

            count = _regrade_sessions(pctx.repo, pctx.course, sessions)

            messages.add_message(request, messages.SUCCESS,
                    _("%d sessions regraded.") % count)
    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 "
            "the grade book."), 
            "</p>"),
ifaint's avatar
ifaint committed
        "form_description": _("Regrade not-for-credit Flow Sessions"),
# vim: foldmethod=marker