Пример #1
0
def finish_in_progress_sessions(self, course_id, flow_id, rule_tag, now_datetime,
        past_due_only):
    course = Course.objects.get(id=course_id)
    repo = get_course_repo(course)

    sessions = (FlowSession.objects
            .filter(
                course=course,
                flow_id=flow_id,
                participation__isnull=False,
                access_rules_tag=rule_tag,
                in_progress=True,
                ))

    count = 0
    nsessions = sessions.count()

    from course.flow import finish_flow_session_standalone
    for i, session in enumerate(sessions):
        from course.flow import adjust_flow_session_page_data
        adjust_flow_session_page_data(repo, session, course.identifier,
                respect_preview=False)

        if finish_flow_session_standalone(repo, course, session,
                now_datetime=now_datetime, past_due_only=past_due_only):
            count += 1

        self.update_state(
                state='PROGRESS',
                meta={'current': i, 'total': nsessions})

    repo.close()

    return {"message": _("%d sessions ended.") % count}
Пример #2
0
def finish_in_progress_sessions(self, course_id, flow_id, rule_tag,
                                now_datetime, past_due_only):
    course = Course.objects.get(id=course_id)
    repo = get_course_repo(course)

    sessions = (FlowSession.objects.filter(
        course=course,
        flow_id=flow_id,
        participation__isnull=False,
        access_rules_tag=rule_tag,
        in_progress=True,
    ))

    count = 0
    nsessions = sessions.count()

    from course.flow import finish_flow_session_standalone
    for i, session in enumerate(sessions):
        from course.flow import adjust_flow_session_page_data
        adjust_flow_session_page_data(repo,
                                      session,
                                      course.identifier,
                                      respect_preview=False)

        if finish_flow_session_standalone(repo,
                                          course,
                                          session,
                                          now_datetime=now_datetime,
                                          past_due_only=past_due_only):
            count += 1

        self.update_state(state='PROGRESS',
                          meta={
                              'current': i,
                              'total': nsessions
                          })

    repo.close()

    return {"message": _("%d sessions ended.") % count}
Пример #3
0
def get_flow_session_content(api_ctx, course_identifier):
    # type: (APIContext, Text) -> http.HttpResponse

    if not api_ctx.has_permission(pperm.view_gradebook):
        raise PermissionDenied("token role does not have required permissions")

    try:
        session_id_str = api_ctx.request.GET["flow_session_id"]
    except KeyError:
        raise APIError("must specify flow_id GET parameter")

    session_id = int(session_id_str)

    flow_session = get_object_or_404(FlowSession, id=session_id)

    if flow_session.course != api_ctx.course:
        raise PermissionDenied(
                "session's course does not match auth context")

    from course.content import get_course_repo
    from course.flow import adjust_flow_session_page_data, assemble_answer_visits

    with get_course_repo(api_ctx.course) as repo:
        from course.utils import FlowContext, instantiate_flow_page_with_ctx
        fctx = FlowContext(repo, api_ctx.course, flow_session.flow_id)

        adjust_flow_session_page_data(repo, flow_session, api_ctx.course.identifier,
                fctx.flow_desc)

        from course.flow import get_all_page_data
        all_page_data = get_all_page_data(flow_session)
        answer_visits = assemble_answer_visits(flow_session)

        pages = []
        for i, page_data in enumerate(all_page_data):
            page = instantiate_flow_page_with_ctx(fctx, page_data)

            assert i == page_data.page_ordinal

            page_data_json = dict(
                    ordinal=i,
                    page_type=page_data.page_type,
                    group_id=page_data.group_id,
                    page_id=page_data.page_id,
                    page_data=page_data.data,
                    title=page_data.title,
                    bookmarked=page_data.bookmarked,
                    )
            answer_json = None
            grade_json = None

            visit = answer_visits[i]
            if visit is not None:
                from course.page.base import PageContext
                pctx = PageContext(api_ctx.course, repo, fctx.course_commit_sha,
                        flow_session)
                norm_bytes_answer_tup = page.normalized_bytes_answer(
                        pctx, page_data.data, visit.answer)

                # norm_answer needs to be JSON-encodable
                norm_answer = None  # type: Any

                if norm_bytes_answer_tup is not None:
                    answer_file_ext, norm_bytes_answer = norm_bytes_answer_tup

                    if answer_file_ext in [".txt", ".py"]:
                        norm_answer = norm_bytes_answer.decode("utf-8")
                    elif answer_file_ext == ".json":
                        import json
                        norm_answer = json.loads(norm_bytes_answer)
                    else:
                        from base64 import b64encode
                        norm_answer = [answer_file_ext,
                                       b64encode(norm_bytes_answer).decode("utf-8")]

                answer_json = dict(
                    visit_time=visit.visit_time.isoformat(),
                    remote_address=repr(visit.remote_address),
                    user=visit.user.username if visit.user is not None else None,
                    impersonated_by=(visit.impersonated_by.username
                        if visit.impersonated_by is not None else None),
                    is_synthetic_visit=visit.is_synthetic,
                    answer_data=visit.answer,
                    answer=norm_answer,
                    )

                grade = visit.get_most_recent_grade()
                if grade is not None:
                    grade_json = dict(
                        grader=(grade.grader.username
                                if grade.grader is not None else None),
                        grade_time=grade.grade_time.isoformat(),
                        graded_at_git_commit_sha=grade.graded_at_git_commit_sha,
                        max_points=grade.max_points,
                        correctness=grade.correctness,
                        feedback=grade.feedback)

            pages.append({
                "page": page_data_json,
                "answer": answer_json,
                "grade": grade_json,
                })

    result = {
        "session": flow_session_to_json(flow_session),
        "pages": pages,
        }

    return http.JsonResponse(result, safe=False)
Пример #4
0
def grade_flow_page(pctx, flow_session_id, page_ordinal):
    # type: (CoursePageContext, int, int) -> http.HttpResponse
    now_datetime = get_now_or_fake_time(pctx.request)

    page_ordinal = int(page_ordinal)

    viewing_prev_grade = False
    prev_grade_id = pctx.request.GET.get("grade_id")
    if prev_grade_id is not None:
        try:
            prev_grade_id = int(prev_grade_id)
            viewing_prev_grade = True
        except ValueError:
            raise SuspiciousOperation("non-integer passed for 'grade_id'")

    if not pctx.has_permission(pperm.view_gradebook):
        raise PermissionDenied(_("may not view grade book"))

    flow_session = get_object_or_404(FlowSession, id=int(flow_session_id))

    if flow_session.course.pk != pctx.course.pk:
        raise SuspiciousOperation(
            _("Flow session not part of specified course"))
    if flow_session.participation is None:
        raise SuspiciousOperation(_("Cannot grade anonymous session"))

    from course.flow import adjust_flow_session_page_data
    adjust_flow_session_page_data(pctx.repo,
                                  flow_session,
                                  pctx.course.identifier,
                                  respect_preview=False)

    fpctx = FlowPageContext(pctx.repo,
                            pctx.course,
                            flow_session.flow_id,
                            page_ordinal,
                            participation=flow_session.participation,
                            flow_session=flow_session,
                            request=pctx.request)

    if fpctx.page_desc is None:
        raise http.Http404()

    assert fpctx.page is not None
    assert fpctx.page_context is not None

    # {{{ enable flow session zapping

    all_flow_sessions = list(
        FlowSession.objects.filter(course=pctx.course,
                                   flow_id=flow_session.flow_id,
                                   participation__isnull=False,
                                   in_progress=flow_session.in_progress).
        order_by(
            # Datatables will default to sorting the user list
            # by the first column, which happens to be the username.
            # Match that sorting.
            "participation__user__username",
            "start_time"))

    next_flow_session_id = None
    prev_flow_session_id = None
    for i, other_flow_session in enumerate(all_flow_sessions):
        if other_flow_session.pk == flow_session.pk:
            if i > 0:
                prev_flow_session_id = all_flow_sessions[i - 1].id
            if i + 1 < len(all_flow_sessions):
                next_flow_session_id = all_flow_sessions[i + 1].id

    # }}}

    prev_grades = get_prev_visit_grades(pctx.course_identifier,
                                        flow_session_id, page_ordinal)

    # {{{ reproduce student view

    form = None
    feedback = None
    answer_data = None
    grade_data = None
    shown_grade = None

    page_expects_answer = fpctx.page.expects_answer()

    if page_expects_answer:
        if fpctx.prev_answer_visit is not None and prev_grade_id is None:
            answer_data = fpctx.prev_answer_visit.answer

            shown_grade = fpctx.prev_answer_visit.get_most_recent_grade()
            if shown_grade is not None:
                feedback = get_feedback_for_grade(shown_grade)
                grade_data = shown_grade.grade_data
            else:
                feedback = None
                grade_data = None

            if shown_grade is not None:
                prev_grade_id = shown_grade.id

        elif prev_grade_id is not None:
            try:
                shown_grade = prev_grades.filter(id=prev_grade_id).get()
            except ObjectDoesNotExist:
                raise http.Http404()

            feedback = get_feedback_for_grade(shown_grade)
            grade_data = shown_grade.grade_data
            answer_data = shown_grade.visit.answer

        else:
            feedback = None

        from course.page.base import PageBehavior
        page_behavior = PageBehavior(show_correctness=True,
                                     show_answer=False,
                                     may_change_answer=False)

        try:
            form = fpctx.page.make_form(fpctx.page_context,
                                        fpctx.page_data.data, answer_data,
                                        page_behavior)
        except InvalidPageData as e:
            messages.add_message(
                pctx.request, messages.ERROR,
                _("The page data stored in the database was found "
                  "to be invalid for the page as given in the "
                  "course content. Likely the course content was "
                  "changed in an incompatible way (say, by adding "
                  "an option to a choice question) without changing "
                  "the question ID. The precise error encountered "
                  "was the following: ") + str(e))

            return render_course_page(pctx, "course/course-base.html", {})

    if form is not None:
        form_html = fpctx.page.form_to_html(pctx.request, fpctx.page_context,
                                            form, answer_data)
    else:
        form_html = None

    # }}}

    # {{{ grading form

    if (page_expects_answer and fpctx.page.is_answer_gradable()
            and fpctx.prev_answer_visit is not None
            and not flow_session.in_progress and not viewing_prev_grade):
        request = pctx.request
        if pctx.request.method == "POST":
            if not pctx.has_permission(pperm.assign_grade):
                raise PermissionDenied(_("may not assign grades"))

            grading_form = fpctx.page.post_grading_form(
                fpctx.page_context, fpctx.page_data, grade_data, request.POST,
                request.FILES)
            if grading_form.is_valid():
                grade_data = fpctx.page.update_grade_data_from_grading_form_v2(
                    request, fpctx.page_context, fpctx.page_data, grade_data,
                    grading_form, request.FILES)
                from course.utils import LanguageOverride
                with LanguageOverride(pctx.course):
                    feedback = fpctx.page.grade(fpctx.page_context,
                                                fpctx.page_data.data,
                                                answer_data, grade_data)

                if feedback is not None:
                    correctness = feedback.correctness
                else:
                    correctness = None

                feedback_json = None  # type: Optional[Dict[Text, Any]]
                bulk_feedback_json = None  # type: Optional[Dict[Text, Any]]

                if feedback is not None:
                    feedback_json, bulk_feedback_json = feedback.as_json()
                else:
                    feedback_json = bulk_feedback_json = None

                most_recent_grade = FlowPageVisitGrade(
                    visit=fpctx.prev_answer_visit,
                    grader=pctx.request.user,
                    graded_at_git_commit_sha=pctx.course_commit_sha,
                    grade_data=grade_data,
                    max_points=fpctx.page.max_points(fpctx.page_data),
                    correctness=correctness,
                    feedback=feedback_json)

                prev_grade_id = _save_grade(fpctx, flow_session,
                                            most_recent_grade,
                                            bulk_feedback_json, now_datetime)
        else:
            grading_form = fpctx.page.make_grading_form(
                fpctx.page_context, fpctx.page_data, grade_data)

    else:
        grading_form = None

    grading_form_html = None  # type: Optional[Text]

    if grading_form is not None:
        from crispy_forms.layout import Submit
        grading_form.helper.form_class += " relate-grading-form"
        grading_form.helper.add_input(
            Submit("submit",
                   _("Submit"),
                   accesskey="s",
                   css_class="relate-grading-save-button"))

        grading_form_html = fpctx.page.grading_form_to_html(
            pctx.request, fpctx.page_context, grading_form, grade_data)

    # }}}

    # {{{ compute points_awarded

    max_points = None
    points_awarded = None
    if (page_expects_answer and fpctx.page.is_answer_gradable()):
        max_points = fpctx.page.max_points(fpctx.page_data)
        if feedback is not None and feedback.correctness is not None:
            points_awarded = max_points * feedback.correctness

    # }}}

    grading_rule = get_session_grading_rule(flow_session, fpctx.flow_desc,
                                            get_now_or_fake_time(pctx.request))

    if grading_rule.grade_identifier is not None:
        grading_opportunity = get_flow_grading_opportunity(
            pctx.course, flow_session.flow_id, fpctx.flow_desc,
            grading_rule.grade_identifier, grading_rule.
            grade_aggregation_strategy)  # type: Optional[GradingOpportunity]
    else:
        grading_opportunity = None

    return render_course_page(
        pctx,
        "course/grade-flow-page.html",
        {
            "flow_identifier":
            fpctx.flow_id,
            "flow_session":
            flow_session,
            "flow_desc":
            fpctx.flow_desc,
            "page_ordinal":
            fpctx.page_ordinal,
            "page_data":
            fpctx.page_data,
            "body":
            fpctx.page.body(fpctx.page_context, fpctx.page_data.data),
            "form":
            form,
            "form_html":
            form_html,
            "feedback":
            feedback,
            "max_points":
            max_points,
            "points_awarded":
            points_awarded,
            "shown_grade":
            shown_grade,
            "prev_grade_id":
            prev_grade_id,
            "expects_answer":
            page_expects_answer,
            "grading_opportunity":
            grading_opportunity,
            "prev_flow_session_id":
            prev_flow_session_id,
            "next_flow_session_id":
            next_flow_session_id,
            "grading_form":
            grading_form,
            "grading_form_html":
            grading_form_html,
            "correct_answer":
            fpctx.page.correct_answer(fpctx.page_context, fpctx.page_data.data,
                                      answer_data, grade_data),

            # Wrappers used by JavaScript template (tmpl) so as not to
            # conflict with Django template's tag wrapper
            "JQ_OPEN":
            '{%',
            'JQ_CLOSE':
            '%}',
        })
Пример #5
0
def get_flow_session_content(api_ctx, course_identifier):
    # type: (APIContext, Text) -> http.HttpResponse

    if not api_ctx.has_permission(pperm.view_gradebook):
        raise PermissionDenied("token role does not have required permissions")

    try:
        session_id_str = api_ctx.request.GET["flow_session_id"]
    except KeyError:
        raise APIError("must specify flow_id GET parameter")

    session_id = int(session_id_str)

    flow_session = get_object_or_404(FlowSession, id=session_id)

    if flow_session.course != api_ctx.course:
        raise PermissionDenied(
                "session's course does not match auth context")

    from course.content import get_course_repo
    from course.flow import adjust_flow_session_page_data, assemble_answer_visits

    with get_course_repo(api_ctx.course) as repo:
        from course.utils import FlowContext, instantiate_flow_page_with_ctx
        fctx = FlowContext(repo, api_ctx.course, flow_session.flow_id)

        adjust_flow_session_page_data(repo, flow_session, api_ctx.course.identifier,
                fctx.flow_desc)

        from course.flow import get_all_page_data
        all_page_data = get_all_page_data(flow_session)
        answer_visits = assemble_answer_visits(flow_session)

        pages = []
        for i, page_data in enumerate(all_page_data):
            page = instantiate_flow_page_with_ctx(fctx, page_data)

            assert i == page_data.page_ordinal

            page_data_json = dict(
                    ordinal=i,
                    page_type=page_data.page_type,
                    group_id=page_data.group_id,
                    page_id=page_data.page_id,
                    page_data=page_data.data,
                    title=page_data.title,
                    bookmarked=page_data.bookmarked,
                    )
            answer_json = None
            grade_json = None

            visit = answer_visits[i]
            if visit is not None:
                from course.page.base import PageContext
                pctx = PageContext(api_ctx.course, repo, fctx.course_commit_sha,
                        flow_session)
                norm_bytes_answer_tup = page.normalized_bytes_answer(
                        pctx, page_data.data, visit.answer)

                # norm_answer needs to be JSON-encodable
                norm_answer = None  # type: Any

                if norm_bytes_answer_tup is not None:
                    answer_file_ext, norm_bytes_answer = norm_bytes_answer_tup

                    if answer_file_ext in [".txt", ".py"]:
                        norm_answer = norm_bytes_answer.decode("utf-8")
                    elif answer_file_ext == ".json":
                        import json
                        norm_answer = json.loads(norm_bytes_answer)
                    else:
                        from base64 import b64encode
                        norm_answer = [answer_file_ext, b64encode(norm_bytes_answer)]

                answer_json = dict(
                    visit_time=visit.visit_time.isoformat(),
                    remote_address=repr(visit.remote_address),
                    user=visit.user.username if visit.user is not None else None,
                    impersonated_by=(visit.impersonated_by.username
                        if visit.impersonated_by is not None else None),
                    is_synthetic_visit=visit.is_synthetic,
                    answer_data=visit.answer,
                    answer=norm_answer,
                    )

                grade = visit.get_most_recent_grade()
                if grade is not None:
                    grade_json = dict(
                        grader=grade.grader,
                        grade_time=grade.grade_time.isoformat(),
                        graded_at_git_commit_sha=grade.graded_at_git_commit_sha,
                        max_points=grade.max_points,
                        correctness=grade.correctness,
                        feedback=grade.feedback)

            pages.append({
                "page": page_data_json,
                "answer": answer_json,
                "grade": grade_json,
                })

    result = {
        "session": flow_session_to_json(flow_session),
        "pages": pages,
        }

    return http.JsonResponse(result, safe=False)
Пример #6
0
def grade_flow_page(pctx, flow_session_id, page_ordinal):
    now_datetime = get_now_or_fake_time(pctx.request)

    page_ordinal = int(page_ordinal)

    if pctx.role not in [
            participation_role.instructor,
            participation_role.teaching_assistant]:
        raise PermissionDenied(
                _("must be instructor or TA to view grades"))

    flow_session = get_object_or_404(FlowSession, id=int(flow_session_id))

    if flow_session.course.pk != pctx.course.pk:
        raise SuspiciousOperation(
                _("Flow session not part of specified course"))
    if flow_session.participation is None:
        raise SuspiciousOperation(
                _("Cannot grade anonymous session"))

    fpctx = FlowPageContext(pctx.repo, pctx.course, flow_session.flow_id,
            page_ordinal, participation=flow_session.participation,
            flow_session=flow_session, request=pctx.request)

    if fpctx.page_desc is None:
        raise http.Http404()

    from course.flow import adjust_flow_session_page_data
    adjust_flow_session_page_data(pctx.repo, flow_session,
            pctx.course.identifier, fpctx.flow_desc)

    # {{{ enable flow session zapping

    all_flow_sessions = list(FlowSession.objects
            .filter(
                course=pctx.course,
                flow_id=flow_session.flow_id,
                participation__isnull=False,
                in_progress=flow_session.in_progress)
            .order_by(
                # Datatables will default to sorting the user list
                # by the first column, which happens to be the username.
                # Match that sorting.
                "participation__user__username",
                "start_time"))

    next_flow_session_id = None
    prev_flow_session_id = None
    for i, other_flow_session in enumerate(all_flow_sessions):
        if other_flow_session.pk == flow_session.pk:
            if i > 0:
                prev_flow_session_id = all_flow_sessions[i-1].id
            if i + 1 < len(all_flow_sessions):
                next_flow_session_id = all_flow_sessions[i+1].id

    # }}}

    # {{{ reproduce student view

    form = None
    feedback = None
    answer_data = None
    grade_data = None
    most_recent_grade = None

    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

        from course.page.base import PageBehavior
        page_behavior = PageBehavior(
                show_correctness=True,
                show_answer=False,
                may_change_answer=False)

        form = fpctx.page.make_form(
                fpctx.page_context, fpctx.page_data.data,
                answer_data, page_behavior)

    if form is not None:
        form_html = fpctx.page.form_to_html(
                pctx.request, fpctx.page_context, form, answer_data)
    else:
        form_html = None

    # }}}

    # {{{ grading form

    if (fpctx.page.expects_answer()
            and fpctx.page.is_answer_gradable()
            and fpctx.prev_answer_visit is not None
            and not flow_session.in_progress):
        request = pctx.request
        if pctx.request.method == "POST":
            grading_form = fpctx.page.post_grading_form(
                    fpctx.page_context, fpctx.page_data, grade_data,
                    request.POST, request.FILES)
            if grading_form.is_valid():
                grade_data = fpctx.page.update_grade_data_from_grading_form(
                        fpctx.page_context, fpctx.page_data, grade_data,
                        grading_form, request.FILES)

                with translation.override(settings.RELATE_ADMIN_EMAIL_LOCALE):
                    feedback = fpctx.page.grade(
                            fpctx.page_context, fpctx.page_data,
                            answer_data, grade_data)

                if feedback is not None:
                    correctness = feedback.correctness
                else:
                    correctness = None

                if feedback is not None:
                    feedback_json, bulk_feedback_json = feedback.as_json()
                else:
                    feedback_json = bulk_feedback_json = None

                most_recent_grade = FlowPageVisitGrade(
                        visit=fpctx.prev_answer_visit,
                        grader=pctx.request.user,
                        graded_at_git_commit_sha=pctx.course_commit_sha,

                        grade_data=grade_data,

                        max_points=fpctx.page.max_points(fpctx.page_data),
                        correctness=correctness,
                        feedback=feedback_json)

                _save_grade(fpctx, flow_session, most_recent_grade,
                        bulk_feedback_json, now_datetime)
        else:
            grading_form = fpctx.page.make_grading_form(
                    fpctx.page_context, fpctx.page_data, grade_data)

    else:
        grading_form = None

    if grading_form is not None:
        from crispy_forms.layout import Submit
        grading_form.helper.form_class += " relate-grading-form"
        grading_form.helper.add_input(
                Submit(
                    "submit", _("Submit"),
                    accesskey="s",
                    css_class="relate-grading-save-button"))

        grading_form_html = fpctx.page.grading_form_to_html(
                pctx.request, fpctx.page_context, grading_form, grade_data)

    else:
        grading_form_html = None

    # }}}

    # {{{ compute points_awarded

    max_points = None
    points_awarded = None
    if (fpctx.page.expects_answer()
            and fpctx.page.is_answer_gradable()):
        max_points = fpctx.page.max_points(fpctx.page_data)
        if feedback is not None and feedback.correctness is not None:
            points_awarded = max_points * feedback.correctness

    # }}}

    grading_rule = get_session_grading_rule(
            flow_session, flow_session.participation.role,
            fpctx.flow_desc, get_now_or_fake_time(pctx.request))

    if grading_rule.grade_identifier is not None:
        grading_opportunity = get_flow_grading_opportunity(
                pctx.course, flow_session.flow_id, fpctx.flow_desc,
                grading_rule)
    else:
        grading_opportunity = None

    return render_course_page(
            pctx,
            "course/grade-flow-page.html",
            {
                "flow_identifier": fpctx.flow_id,
                "flow_session": flow_session,
                "flow_desc": fpctx.flow_desc,
                "ordinal": fpctx.ordinal,
                "page_data": fpctx.page_data,

                "body": fpctx.page.body(
                    fpctx.page_context, fpctx.page_data.data),
                "form": form,
                "form_html": form_html,
                "feedback": feedback,
                "max_points": max_points,
                "points_awarded": points_awarded,
                "most_recent_grade": most_recent_grade,

                "grading_opportunity": grading_opportunity,

                "prev_flow_session_id": prev_flow_session_id,
                "next_flow_session_id": next_flow_session_id,

                "grading_form": grading_form,
                "grading_form_html": grading_form_html,
                "correct_answer": fpctx.page.correct_answer(
                    fpctx.page_context, fpctx.page_data.data,
                    answer_data, grade_data),
            })