Пример #1
0
def list_available_exams(request):
    now_datetime = get_now_or_fake_time(request)

    if request.user.is_authenticated():
        participations = (
                Participation.objects.filter(
                    user=request.user,
                    status=participation_status.active))
    else:
        participations = []

    from django.db.models import Q
    exams = (
            Exam.objects
            .filter(
                course__in=[p.course for p in participations],
                active=True,
                no_exams_before__lt=now_datetime)
            .filter(
                Q(no_exams_after__isnull=True)
                |
                Q(no_exams_after__gt=now_datetime))
            .order_by("no_exams_before", "course__number"))

    return render(request, "course/list-exams.html", {
        "exams": exams
        })
Пример #2
0
def render_course_page(pctx, template_name, args,
        allow_instant_flow_requests=True):
    # type: (CoursePageContext, Text, Dict[Text, Any], bool) -> http.HttpResponse

    args = args.copy()

    from course.views import get_now_or_fake_time
    now_datetime = get_now_or_fake_time(pctx.request)

    if allow_instant_flow_requests:
        from course.models import InstantFlowRequest
        instant_flow_requests = list((InstantFlowRequest.objects
                .filter(
                    course=pctx.course,
                    start_time__lte=now_datetime,
                    end_time__gte=now_datetime,
                    cancelled=False)
                .order_by("start_time")))
    else:
        instant_flow_requests = []

    args.update({
        "course": pctx.course,
        "pperm": ParticipationPermissionWrapper(pctx),
        "participation": pctx.participation,
        "num_instant_flow_requests": len(instant_flow_requests),
        "instant_flow_requests":
        [(i+1, r) for i, r in enumerate(instant_flow_requests)],
        })

    return render(pctx.request, template_name, args)
Пример #3
0
def render_course_page(pctx, template_name, args,
        allow_instant_flow_requests=True):
    args = args.copy()

    from course.views import get_now_or_fake_time
    now_datetime = get_now_or_fake_time(pctx.request)

    if allow_instant_flow_requests:
        from course.models import InstantFlowRequest
        instant_flow_requests = list((InstantFlowRequest.objects
                .filter(
                    course=pctx.course,
                    start_time__lte=now_datetime,
                    end_time__gte=now_datetime,
                    cancelled=False)
                .order_by("start_time")))
    else:
        instant_flow_requests = []

    args.update({
        "course": pctx.course,
        "participation": pctx.participation,
        "role": pctx.role,
        "participation_role": participation_role,
        "num_instant_flow_requests": len(instant_flow_requests),
        "instant_flow_requests":
        [(i+1, r) for i, r in enumerate(instant_flow_requests)],
        })

    return render(pctx.request, template_name, args)
Пример #4
0
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()
Пример #5
0
def issue_exam_ticket(request):
    now_datetime = get_now_or_fake_time(request)

    if request.method == "POST":
        form = IssueTicketForm(now_datetime, request.POST)

        if form.is_valid():
            exam = form.cleaned_data["exam"]
            try:
                participation = Participation.objects.get(
                                course=exam.course,
                                user=form.cleaned_data["user"],
                                status=participation_status.active,
                                )

            except ObjectDoesNotExist:
                messages.add_message(request, messages.ERROR,
                        _("User is not enrolled in course."))
                participation = None

            if participation is not None:
                if form.cleaned_data["revoke_prior"]:
                    ExamTicket.objects.filter(
                            exam=exam,
                            participation=participation,
                            state__in=(
                                exam_ticket_states.valid,
                                exam_ticket_states.used,
                                )
                            ).update(state=exam_ticket_states.revoked)

                ticket = ExamTicket()
                ticket.exam = exam
                ticket.participation = participation
                ticket.creator = request.user
                ticket.state = exam_ticket_states.valid
                ticket.code = gen_ticket_code()
                ticket.save()

                messages.add_message(request, messages.SUCCESS,
                        _(
                            "Ticket issued for <b>%(participation)s</b>. "
                            "The ticket code is <b>%(ticket_code)s</b>."
                            ) % {"participation": participation,
                                 "ticket_code": ticket.code})

                form = IssueTicketForm(now_datetime, initial_exam=exam)

    else:
        form = IssueTicketForm(now_datetime)

    return render(request, "generic-form.html", {
        "form_description":
            _("Issue Exam Ticket"),
        "form": form,
        })
Пример #6
0
def check_in_for_exam(request):
    now_datetime = get_now_or_fake_time(request)

    if request.method == "POST":
        form = ExamCheckInForm(request.POST)
        if form.is_valid():
            username = form.cleaned_data["username"]
            code = form.cleaned_data["code"]

            pretend_facilities = request.session.get(
                    "relate_pretend_facilities", None)

            is_valid, msg = check_exam_ticket(username, code, now_datetime)
            if not is_valid:
                messages.add_message(request, messages.ERROR, msg)
            else:
                from django.contrib.auth import authenticate, login
                user = authenticate(username=username, code=code,
                        now_datetime=now_datetime)

                assert user is not None

                login(request, user)

                ticket = ExamTicket.objects.get(
                        participation__user=user,
                        code=code,
                        state__in=(
                            exam_ticket_states.valid,
                            exam_ticket_states.used,
                            )
                        )
                if ticket.state == exam_ticket_states.valid:
                    ticket.state = exam_ticket_states.used
                    ticket.usage_time = now_datetime
                    ticket.save()

                if pretend_facilities:
                    # Make pretend-facilities survive exam login.
                    request.session["relate_pretend_facilities"] = pretend_facilities

                request.session["relate_exam_ticket_pk_used_for_login"] = ticket.pk

                return redirect("relate-view_start_flow",
                        ticket.exam.course.identifier,
                        ticket.exam.flow_id)

    else:
        form = ExamCheckInForm()

    return render(request, "course/exam-check-in.html", {
        "form_description":
            _("Check in for Exam"),
        "form": form
        })
Пример #7
0
    def __init__(self, request, course_identifier, flow_identifier,
            flow_session=None):
        CoursePageContext.__init__(self, request, course_identifier)

        self.flow_session = flow_session
        self.flow_identifier = flow_identifier

        from course.content import get_flow_commit_sha
        from django.core.exceptions import ObjectDoesNotExist

        # Fetch 'current' version of the flow to compute permissions
        # and versioning rules.
        # Fall back to 'old' version if current git version does not
        # contain this flow any more.

        try:
            current_flow_desc_sha = self.course_commit_sha
            current_flow_desc = get_flow_desc(self.repo, self.course,
                    flow_identifier, current_flow_desc_sha)
        except ObjectDoesNotExist:
            if self.flow_session is None:
                raise http.Http404()

            current_flow_desc_sha = self.flow_session.active_git_commit_sha.encode()
            current_flow_desc = get_flow_desc(self.repo, self.course,
                    flow_identifier, current_flow_desc_sha)

        self.flow_commit_sha = get_flow_commit_sha(
                self.course, self.participation,
                current_flow_desc, self.flow_session)
        if self.flow_commit_sha == current_flow_desc_sha:
            self.flow_desc = current_flow_desc
        else:
            self.flow_desc = get_flow_desc(self.repo, self.course,
                flow_identifier, self.flow_commit_sha)

        # {{{ figure out permissions

        from course.views import get_now_or_fake_time
        self.permissions, self.stipulations = get_flow_permissions(
                self.course, self.participation, self.role,
                flow_identifier, current_flow_desc,
                get_now_or_fake_time(request))
Пример #8
0
def get_facilities_config(request=None):
    from django.conf import settings

    # This is called during offline validation, where Django isn't really set up.
    # The getattr makes this usable.
    facilities = getattr(settings, "RELATE_FACILITIES", None)
    if facilities is None:
        # Only happens during offline validation. Suppresses errors there.
        return None

    if callable(facilities):
        from course.views import get_now_or_fake_time
        now_datetime = get_now_or_fake_time(request)

        result = facilities(now_datetime)
        if not isinstance(result, dict):
            raise RuntimeError("RELATE_FACILITIES must return a dictionary")
        return result
    else:
        return facilities
Пример #9
0
def view_calendar(pctx):
    if not pctx.has_permission(pperm.view_calendar):
        raise PermissionDenied(_("may not view calendar"))

    from course.views import get_now_or_fake_time
    now = get_now_or_fake_time(pctx.request)

    events_json = []

    from course.content import (get_raw_yaml_from_repo, markup_to_html,
                                parse_date_spec)
    try:
        event_descr = get_raw_yaml_from_repo(pctx.repo,
                                             pctx.course.events_file,
                                             pctx.course_commit_sha)
    except ObjectDoesNotExist:
        event_descr = {}

    event_kinds_desc = event_descr.get("event_kinds", {})
    event_info_desc = event_descr.get("events", {})

    event_info_list = []

    events = sorted(Event.objects.filter(course=pctx.course,
                                         shown_in_calendar=True),
                    key=lambda evt:
                    (-evt.time.year, -evt.time.month, -evt.time.day, evt.time.
                     hour, evt.time.minute, evt.time.second))

    for event in events:
        kind_desc = event_kinds_desc.get(event.kind)

        human_title = six.text_type(event)

        event_json = {
            "id": event.id,
            "start": event.time.isoformat(),
            "allDay": event.all_day,
        }
        if event.end_time is not None:
            event_json["end"] = event.end_time.isoformat()

        if kind_desc is not None:
            if "color" in kind_desc:
                event_json["color"] = kind_desc["color"]
            if "title" in kind_desc:
                if event.ordinal is not None:
                    human_title = kind_desc["title"].format(nr=event.ordinal)
                else:
                    human_title = kind_desc["title"].rstrip("{nr}").strip()

        description = None
        show_description = True
        event_desc = event_info_desc.get(six.text_type(event))
        if event_desc is not None:
            if "description" in event_desc:
                description = markup_to_html(pctx.course, pctx.repo,
                                             pctx.course_commit_sha,
                                             event_desc["description"])

            if "title" in event_desc:
                human_title = event_desc["title"]

            if "color" in event_desc:
                event_json["color"] = event_desc["color"]

            if "show_description_from" in event_desc:
                ds = parse_date_spec(pctx.course,
                                     event_desc["show_description_from"])
                if now < ds:
                    show_description = False

            if "show_description_until" in event_desc:
                ds = parse_date_spec(pctx.course,
                                     event_desc["show_description_until"])
                if now > ds:
                    show_description = False

        event_json["title"] = human_title

        if show_description and description:
            event_json["url"] = "#event-%d" % event.id

            start_time = event.time
            end_time = event.end_time

            if event.all_day:
                start_time = start_time.date()
                if end_time is not None:
                    local_end_time = as_local_time(end_time)
                    end_midnight = datetime.time(tzinfo=local_end_time.tzinfo)
                    if local_end_time.time() == end_midnight:
                        end_time = (end_time -
                                    datetime.timedelta(days=1)).date()
                    else:
                        end_time = end_time.date()

            event_info_list.append(
                EventInfo(id=event.id,
                          human_title=human_title,
                          start_time=start_time,
                          end_time=end_time,
                          description=description))

        events_json.append(event_json)

    default_date = now.date()
    if pctx.course.end_date is not None and default_date > pctx.course.end_date:
        default_date = pctx.course.end_date

    from json import dumps
    return render_course_page(
        pctx, "course/calendar.html", {
            "events_json": dumps(events_json),
            "event_info_list": event_info_list,
            "default_date": default_date.isoformat(),
        })
Пример #10
0
def view_grades_by_opportunity(pctx, opp_id):
    from course.views import get_now_or_fake_time
    now_datetime = get_now_or_fake_time(pctx.request)

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

    opportunity = get_object_or_404(GradingOpportunity, id=int(opp_id))

    if pctx.course != opportunity.course:
        raise SuspiciousOperation("opportunity from wrong course")

    # {{{ batch sessions form

    batch_session_ops_form = None
    if pctx.role == participation_role.instructor and opportunity.flow_id:
        cursor = connection.cursor()
        cursor.execute("select distinct access_rules_id from course_flowsession "
                "where course_id = %s and flow_id = %s "
                "order by access_rules_id", (pctx.course.id, opportunity.flow_id))
        rule_ids = [mangle_rule_id(row[0]) for row in cursor.fetchall()]

        request = pctx.request
        if request.method == "POST":
            batch_session_ops_form = ModifySessionsForm(
                    rule_ids, request.POST, request.FILES)
            if "expire" in request.POST:
                op = "expire"
            elif "end" in request.POST:
                op = "end"
            elif "regrade" in request.POST:
                op = "regrade"
            else:
                raise SuspiciousOperation("invalid operation")

            if batch_session_ops_form.is_valid():
                rule_id = batch_session_ops_form.cleaned_data["rule_id"]
                past_end_only = batch_session_ops_form.cleaned_data["past_end_only"]

                if rule_id == RULE_ID_NONE_STRING:
                    rule_id = None
                try:
                    if op == "expire":
                        count = expire_in_progress_sessions(
                                pctx.repo, pctx.course, opportunity.flow_id,
                                rule_id, now_datetime,
                                past_end_only=past_end_only)

                        messages.add_message(pctx.request, messages.SUCCESS,
                                "%d session(s) expired." % count)
                    elif op == "end":
                        count = finish_in_progress_sessions(
                                pctx.repo, pctx.course, opportunity.flow_id,
                                rule_id, now_datetime,
                                past_end_only=past_end_only)

                        messages.add_message(pctx.request, messages.SUCCESS,
                                "%d session(s) ended." % count)
                    elif op == "regrade":
                        count = regrade_ended_sessions(
                                pctx.repo, pctx.course, opportunity.flow_id,
                                rule_id)

                        messages.add_message(pctx.request, messages.SUCCESS,
                                "%d session(s) regraded." % count)
                    else:
                        raise SuspiciousOperation("invalid operation")
                except Exception as e:
                    messages.add_message(pctx.request, messages.ERROR,
                            "Error: %s %s" % (type(e).__name__, str(e)))

        else:
            batch_session_ops_form = ModifySessionsForm(rule_ids)

    # }}}

    # NOTE: It's important that these queries are sorted consistently,
    # also consistently with the code below.

    participations = list(Participation.objects
            .filter(
                course=pctx.course,
                status=participation_status.active)
            .order_by("id")
            .prefetch_related("user"))

    grade_changes = list(GradeChange.objects
            .filter(opportunity=opportunity)
            .order_by(
                "participation__id",
                "grade_time")
            .prefetch_related("participation")
            .prefetch_related("participation__user")
            .prefetch_related("opportunity"))

    idx = 0

    grade_table = []
    for participation in participations:
        while (
                idx < len(grade_changes)
                and grade_changes[idx].participation.id < participation.id):
            idx += 1

        my_grade_changes = []
        while (
                idx < len(grade_changes)
                and grade_changes[idx].participation.pk == participation.pk):
            my_grade_changes.append(grade_changes[idx])
            idx += 1

        state_machine = GradeStateMachine()
        state_machine.consume(my_grade_changes)

        if opportunity.flow_id:
            flow_sessions = (FlowSession.objects
                    .filter(
                        participation=participation,
                        flow_id=opportunity.flow_id,
                        )
                    .order_by("start_time"))
        else:
            flow_sessions = None

        grade_table.append(
                OpportunityGradeInfo(
                    grade_state_machine=state_machine,
                    flow_sessions=flow_sessions))

    grade_table = sorted(zip(participations, grade_table),
            key=lambda (participation, grades):
                (participation.user.last_name.lower(),
                    participation.user.first_name.lower()))

    return render_course_page(pctx, "course/gradebook-by-opp.html", {
        "opportunity": opportunity,
        "participations": participations,
        "grade_state_change_types": grade_state_change_types,
        "grade_table": grade_table,
        "batch_session_ops_form": batch_session_ops_form,
        })
Пример #11
0
def grade_flow_page(pctx, flow_session_id, page_ordinal):
    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)

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

    # {{{ 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(
                "participation__user__last_name",
                "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)

                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)

                most_recent_grade.save()

                update_bulk_feedback(
                        fpctx.prev_answer_visit.page_data,
                        most_recent_grade,
                        bulk_feedback_json)

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

                from course.flow import grade_flow_session
                grade_flow_session(fpctx, flow_session, grading_rule)

        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.add_input(
                Submit(
                    "submit", _("Submit"),
                    accesskey="s",
                    css_class="col-lg-offset-2 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,
            })
Пример #12
0
def view_calendar(pctx):
    from course.content import markup_to_html

    events_json = []

    from course.content import get_raw_yaml_from_repo
    try:
        event_descr = get_raw_yaml_from_repo(pctx.repo,
                pctx.course.events_file, pctx.course_commit_sha)
    except ObjectDoesNotExist:
        event_descr = {}

    event_kinds_desc = event_descr.get("event_kinds", {})
    event_info_desc = event_descr.get("events", {})

    event_info_list = []

    for event in (Event.objects
            .filter(
                course=pctx.course,
                shown_in_calendar=True)
            .order_by("-time")):
        kind_desc = event_kinds_desc.get(event.kind)

        human_title = six.text_type(event)

        event_json = {
                "id": event.id,
                "start": event.time.isoformat(),
                "allDay": event.all_day,
                }
        if event.end_time is not None:
            event_json["end"] = event.end_time.isoformat()

        if kind_desc is not None:
            if "color" in kind_desc:
                event_json["color"] = kind_desc["color"]
            if "title" in kind_desc:
                if event.ordinal is not None:
                    human_title = kind_desc["title"].format(nr=event.ordinal)
                else:
                    human_title = kind_desc["title"]

        description = None
        event_desc = event_info_desc.get(six.text_type(event))
        if event_desc is not None:
            if "description" in event_desc:
                description = markup_to_html(
                        pctx.course, pctx.repo, pctx.course_commit_sha,
                        event_desc["description"])

            if "title" in event_desc:
                human_title = event_desc["title"]

            if "color" in event_desc:
                event_json["color"] = event_desc["color"]

        event_json["title"] = human_title

        if description:
            event_json["url"] = "#event-%d" % event.id

            start_time = event.time
            end_time = event.end_time

            if event.all_day:
                start_time = start_time.date()
                end_time = end_time.date()

            event_info_list.append(
                    EventInfo(
                        id=event.id,
                        human_title=human_title,
                        start_time=start_time,
                        end_time=end_time,
                        description=description
                        ))

        events_json.append(event_json)

    from course.views import get_now_or_fake_time
    default_date = get_now_or_fake_time(pctx.request).date()
    if pctx.course.end_date is not None and default_date > pctx.course.end_date:
        default_date = pctx.course.end_date

    from json import dumps
    return render_course_page(pctx, "course/calendar.html", {
        "events_json": dumps(events_json),
        "event_info_list": event_info_list,
        "default_date": default_date.isoformat(),
    })
Пример #13
0
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,
        )
Пример #14
0
def view_calendar(pctx):
    from course.content import markup_to_html, parse_date_spec

    from course.views import get_now_or_fake_time

    now = get_now_or_fake_time(pctx.request)

    if not pctx.has_permission(pperm.view_calendar):
        raise PermissionDenied(_("may not view calendar"))

    events_json = []

    from course.content import get_raw_yaml_from_repo

    try:
        event_descr = get_raw_yaml_from_repo(pctx.repo, pctx.course.events_file, pctx.course_commit_sha)
    except ObjectDoesNotExist:
        event_descr = {}

    event_kinds_desc = event_descr.get("event_kinds", {})
    event_info_desc = event_descr.get("events", {})

    event_info_list = []

    for event in Event.objects.filter(course=pctx.course, shown_in_calendar=True).order_by("-time"):
        kind_desc = event_kinds_desc.get(event.kind)

        human_title = six.text_type(event)

        event_json = {"id": event.id, "start": event.time.isoformat(), "allDay": event.all_day}
        if event.end_time is not None:
            event_json["end"] = event.end_time.isoformat()

        if kind_desc is not None:
            if "color" in kind_desc:
                event_json["color"] = kind_desc["color"]
            if "title" in kind_desc:
                if event.ordinal is not None:
                    human_title = kind_desc["title"].format(nr=event.ordinal)
                else:
                    human_title = kind_desc["title"]

        description = None
        show_description = True
        event_desc = event_info_desc.get(six.text_type(event))
        if event_desc is not None:
            if "description" in event_desc:
                description = markup_to_html(pctx.course, pctx.repo, pctx.course_commit_sha, event_desc["description"])

            if "title" in event_desc:
                human_title = event_desc["title"]

            if "color" in event_desc:
                event_json["color"] = event_desc["color"]

            if "show_description_from" in event_desc:
                ds = parse_date_spec(pctx.course, event_desc["show_description_from"])
                if now < ds:
                    show_description = False

            if "show_description_until" in event_desc:
                ds = parse_date_spec(pctx.course, event_desc["show_description_until"])
                if now > ds:
                    show_description = False

        event_json["title"] = human_title

        if show_description and description:
            event_json["url"] = "#event-%d" % event.id

            start_time = event.time
            end_time = event.end_time

            if event.all_day:
                start_time = start_time.date()
                local_end_time = as_local_time(end_time)
                end_midnight = datetime.time(tzinfo=local_end_time.tzinfo)
                if local_end_time.time() == end_midnight:
                    end_time = (end_time - datetime.timedelta(days=1)).date()
                else:
                    end_time = end_time.date()

            event_info_list.append(
                EventInfo(
                    id=event.id,
                    human_title=human_title,
                    start_time=start_time,
                    end_time=end_time,
                    description=description,
                )
            )

        events_json.append(event_json)

    default_date = now.date()
    if pctx.course.end_date is not None and default_date > pctx.course.end_date:
        default_date = pctx.course.end_date

    from json import dumps

    return render_course_page(
        pctx,
        "course/calendar.html",
        {
            "events_json": dumps(events_json),
            "event_info_list": event_info_list,
            "default_date": default_date.isoformat(),
        },
    )
Пример #15
0
def start_flow(pctx, flow_identifier):
    request = pctx.request

    now_datetime = get_now_or_fake_time(request)
    fctx = FlowContext(pctx.repo, pctx.course, flow_identifier,
            participation=pctx.participation)

    current_access_rule = fctx.get_current_access_rule(
            None, pctx.role, pctx.participation, now_datetime)

    may_view = flow_permission.view in current_access_rule.permissions

    have_in_progress_session = (FlowSession.objects
            .filter(
                participation=pctx.participation,
                flow_id=fctx.flow_identifier,
                in_progress=True,
                participation__isnull=False,
                )).count() > 0
    past_sessions = (FlowSession.objects
            .filter(
                participation=pctx.participation,
                flow_id=fctx.flow_identifier,
                participation__isnull=False)
           .order_by("start_time"))
    past_session_count = past_sessions.count()

    if current_access_rule.allowed_session_count is not None:
        allowed_another_session = (
                past_session_count < current_access_rule.allowed_session_count)
    else:
        allowed_another_session = True

    if request.method == "POST":
        from course.content import set_up_flow_session_page_data

        resume_match = None
        for post_key in request.POST:
            resume_match = RESUME_RE.match(post_key)
            if resume_match is not None:
                break

        if resume_match is not None:
            resume_session_id = int(resume_match.group(1))

            resume_session = get_object_or_404(FlowSession, pk=resume_session_id)

            if resume_session.participation != pctx.participation:
                raise PermissionDenied("not your session")

            if not may_view:
                raise PermissionDenied("may not resume session without "
                        "'view' permission")

            if resume_session.participation is None:
                raise PermissionDenied("can't resume anonymous session")

            if resume_session.flow_id != fctx.flow_identifier:
                raise SuspiciousOperation("flow id mismatch on resume")

            if not (flow_permission.view_past in current_access_rule.permissions
                    or resume_session.in_progress):
                raise PermissionDenied("not allowed to resume session")

            request.session["flow_session_id"] = resume_session_id

            return redirect("course.flow.view_flow_page",
                    pctx.course.identifier,
                    flow_identifier,
                    0)

        elif ("start_no_credit" in request.POST
                or "start_credit" in request.POST):

            if not may_view:
                raise PermissionDenied("may not start session without "
                        "'view' permission")

            if not allowed_another_session:
                raise PermissionDenied("new session would exceed "
                        "allowed session count limit exceed")

            if have_in_progress_session:
                raise PermissionDenied("cannot start flow when other flow "
                        "session is already in progress")

            session = FlowSession()
            session.course = fctx.course
            session.participation = pctx.participation
            session.active_git_commit_sha = fctx.flow_commit_sha.decode()
            session.flow_id = flow_identifier
            session.in_progress = True
            session.for_credit = "start_credit" in request.POST
            session.access_rules_id = current_access_rule.id
            session.save()

            request.session["flow_session_id"] = session.id

            page_count = set_up_flow_session_page_data(fctx.repo, session,
                    pctx.course.identifier, fctx.flow_desc, fctx.flow_commit_sha)
            session.page_count = page_count
            session.save()

            return redirect("course.flow.view_flow_page",
                    pctx.course.identifier,
                    flow_identifier,
                    0)

        else:
            raise SuspiciousOperation("unrecognized POST action")

    else:
        may_start_credit = (
                may_view
                and not have_in_progress_session
                and allowed_another_session
                and flow_permission.start_credit in current_access_rule.permissions)
        may_start_no_credit = (
                may_view
                and not have_in_progress_session
                and allowed_another_session
                and (flow_permission.start_no_credit
                    in current_access_rule.permissions))
        may_review = (
                may_view
                and flow_permission.view_past in current_access_rule.permissions)

        if hasattr(fctx.flow_desc, "grade_aggregation_strategy"):
            from course.models import GRADE_AGGREGATION_STRATEGY_CHOICES
            grade_aggregation_strategy_text = (
                    dict(GRADE_AGGREGATION_STRATEGY_CHOICES)
                    [fctx.flow_desc.grade_aggregation_strategy])
        else:
            grade_aggregation_strategy_text = None

        # {{{ fish out relevant rules

        from course.utils import (
                get_flow_access_rules,
                get_relevant_rules)
        rules = get_flow_access_rules(fctx.course, pctx.participation,
                flow_identifier, fctx.flow_desc)

        # }}}

        return render_course_page(pctx, "course/flow-start.html", {
            "flow_desc": fctx.flow_desc,
            "grade_aggregation_strategy":
            grade_aggregation_strategy_text,
            "flow_identifier": flow_identifier,

            "rules": get_relevant_rules(rules, pctx.role, now_datetime),
            "now": now_datetime,

            "may_start_credit": may_start_credit,
            "may_start_no_credit": may_start_no_credit,
            "may_review": may_review,

            "past_sessions": past_sessions,
            },
            allow_instant_flow_requests=False)
Пример #16
0
def manage_authentication_tokens(pctx):
    # type: (http.HttpRequest) -> http.HttpResponse

    request = pctx.request

    if not request.user.is_authenticated:
        raise PermissionDenied()

    from course.views import get_now_or_fake_time
    now_datetime = get_now_or_fake_time(request)

    if request.method == 'POST':
        form = AuthenticationTokenForm(pctx.participation, request.POST)

        revoke_prefix = "revoke_"
        revoke_post_args = [
            key for key in request.POST if key.startswith("revoke_")
        ]

        if revoke_post_args:
            token_id = int(revoke_post_args[0][len(revoke_prefix):])

            auth_token = get_object_or_404(AuthenticationToken,
                                           id=token_id,
                                           user=request.user)

            auth_token.revocation_time = now_datetime
            auth_token.save()

            form = AuthenticationTokenForm(pctx.participation)

        elif "create" in request.POST:
            if form.is_valid():
                token = make_sign_in_key(request.user)

                from django.contrib.auth.hashers import make_password
                auth_token = AuthenticationToken(
                    user=pctx.request.user,
                    participation=pctx.participation,
                    restrict_to_participation_role=form.
                    cleaned_data["restrict_to_participation_role"],
                    description=form.cleaned_data["description"],
                    valid_until=form.cleaned_data["valid_until"],
                    token_hash=make_password(token))
                auth_token.save()

                user_token = "%d_%s" % (auth_token.id, token)

                messages.add_message(
                    request, messages.SUCCESS,
                    _("A new authentication token has been created: %s. "
                      "Please save this token, as you will not be able "
                      "to retrieve it later.") % user_token)
        else:
            messages.add_message(request, messages.ERROR,
                                 _("Could not find which button was pressed."))

    else:
        form = AuthenticationTokenForm(pctx.participation)

    from django.db.models import Q

    from datetime import timedelta
    tokens = AuthenticationToken.objects.filter(
        user=request.user,
        participation__course=pctx.course,
    ).filter(
        Q(revocation_time=None)
        | Q(revocation_time__gt=now_datetime - timedelta(weeks=1)))

    return render_course_page(pctx, "course/manage-auth-tokens.html", {
        "form": form,
        "new_token_message": "",
        "tokens": tokens,
    })
Пример #17
0
def view_grades_by_opportunity(pctx, opp_id):
    from course.views import get_now_or_fake_time
    now_datetime = get_now_or_fake_time(pctx.request)

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

    opportunity = get_object_or_404(GradingOpportunity, id=int(opp_id))

    if pctx.course != opportunity.course:
        raise SuspiciousOperation(_("opportunity from wrong course"))

    # {{{ batch sessions form

    batch_session_ops_form = None
    if pctx.role == participation_role.instructor and opportunity.flow_id:
        cursor = connection.cursor()
        cursor.execute("select distinct access_rules_tag from course_flowsession "
                "where course_id = %s and flow_id = %s "
                "order by access_rules_tag", (pctx.course.id, opportunity.flow_id))
        session_rule_tags = [
                mangle_session_access_rule_tag(row[0]) for row in cursor.fetchall()]

        request = pctx.request
        if request.method == "POST":
            batch_session_ops_form = ModifySessionsForm(
                    session_rule_tags, request.POST, request.FILES)
            if "expire" in request.POST:
                op = "expire"
            elif "end" in request.POST:
                op = "end"
            elif "regrade" in request.POST:
                op = "regrade"
            elif "recalculate" in request.POST:
                op = "recalculate"
            else:
                raise SuspiciousOperation(_("invalid operation"))

            if batch_session_ops_form.is_valid():
                rule_tag = batch_session_ops_form.cleaned_data["rule_tag"]
                past_due_only = batch_session_ops_form.cleaned_data["past_due_only"]

                if rule_tag == RULE_TAG_NONE_STRING:
                    rule_tag = None

                from course.tasks import (
                        expire_in_progress_sessions,
                        finish_in_progress_sessions,
                        regrade_flow_sessions,
                        recalculate_ended_sessions)

                if op == "expire":
                    async_res = expire_in_progress_sessions.delay(
                            pctx.course.id, opportunity.flow_id,
                            rule_tag, now_datetime,
                            past_due_only=past_due_only)

                    return redirect("relate-monitor_task", async_res.id)

                elif op == "end":
                    async_res = finish_in_progress_sessions.delay(
                            pctx.course.id, opportunity.flow_id,
                            rule_tag, now_datetime,
                            past_due_only=past_due_only)

                    return redirect("relate-monitor_task", async_res.id)

                elif op == "regrade":
                    async_res = regrade_flow_sessions.delay(
                            pctx.course.id, opportunity.flow_id,
                            rule_tag, inprog_value=False)

                    return redirect("relate-monitor_task", async_res.id)

                elif op == "recalculate":
                    async_res = recalculate_ended_sessions.delay(
                            pctx.course.id, opportunity.flow_id,
                            rule_tag)

                    return redirect("relate-monitor_task", async_res.id)

                else:
                    raise SuspiciousOperation("invalid operation")

        else:
            batch_session_ops_form = ModifySessionsForm(session_rule_tags)

    # }}}

    # NOTE: It's important that these queries are sorted consistently,
    # also consistently with the code below.

    participations = list(Participation.objects
            .filter(
                course=pctx.course,
                status=participation_status.active)
            .order_by("id")
            .select_related("user"))

    grade_changes = list(GradeChange.objects
            .filter(opportunity=opportunity)
            .order_by(
                "participation__id",
                "grade_time")
            .select_related("participation")
            .select_related("participation__user")
            .select_related("opportunity"))

    idx = 0

    finished_sessions = 0
    total_sessions = 0

    grade_table = []
    for participation in participations:
        while (
                idx < len(grade_changes)
                and grade_changes[idx].participation.id < participation.id):
            idx += 1

        my_grade_changes = []
        while (
                idx < len(grade_changes)
                and grade_changes[idx].participation.pk == participation.pk):
            my_grade_changes.append(grade_changes[idx])
            idx += 1

        state_machine = GradeStateMachine()
        state_machine.consume(my_grade_changes)

        if opportunity.flow_id:
            flow_sessions = (FlowSession.objects
                    .filter(
                        participation=participation,
                        flow_id=opportunity.flow_id,
                        )
                    .order_by("start_time"))

            for fsession in flow_sessions:
                total_sessions += 1
                if not fsession.in_progress:
                    finished_sessions += 1

        else:
            flow_sessions = None

        grade_table.append(
                OpportunityGradeInfo(
                    grade_state_machine=state_machine,
                    flow_sessions=flow_sessions))

    def grade_key(entry):
        (participation, grades) = entry
        return (participation.user.last_name.lower(),
                    participation.user.first_name.lower())

    grade_table = sorted(zip(participations, grade_table),
            key=grade_key)

    return render_course_page(pctx, "course/gradebook-by-opp.html", {
        "opportunity": opportunity,
        "participations": participations,
        "grade_state_change_types": grade_state_change_types,
        "grade_table": grade_table,
        "batch_session_ops_form": batch_session_ops_form,

        "total_sessions": total_sessions,
        "finished_sessions": finished_sessions,
        })
Пример #18
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':
            '%}',
        })
Пример #19
0
def manage_authentication_tokens(pctx):
    # type: (http.HttpRequest) -> http.HttpResponse

    request = pctx.request

    if not request.user.is_authenticated:
        raise PermissionDenied()

    from course.views import get_now_or_fake_time
    now_datetime = get_now_or_fake_time(request)

    if request.method == 'POST':
        form = AuthenticationTokenForm(pctx.participation, request.POST)

        revoke_prefix = "revoke_"
        revoke_post_args = [key for key in request.POST if key.startswith("revoke_")]

        if revoke_post_args:
            token_id = int(revoke_post_args[0][len(revoke_prefix):])

            auth_token = get_object_or_404(AuthenticationToken,
                    id=token_id,
                    user=request.user)

            auth_token.revocation_time = now_datetime
            auth_token.save()

            form = AuthenticationTokenForm(pctx.participation)

        elif "create" in request.POST:
            if form.is_valid():
                token = make_sign_in_key(request.user)

                from django.contrib.auth.hashers import make_password
                auth_token = AuthenticationToken(
                        user=pctx.request.user,
                        participation=pctx.participation,
                        restrict_to_participation_role=form.cleaned_data[
                            "restrict_to_participation_role"],
                        description=form.cleaned_data["description"],
                        valid_until=form.cleaned_data["valid_until"],
                        token_hash=make_password(token))
                auth_token.save()

                user_token = "%d_%s" % (auth_token.id, token)

                messages.add_message(request, messages.SUCCESS,
                        _("A new authentication token has been created: %s. "
                            "Please save this token, as you will not be able "
                            "to retrieve it later.")
                        % user_token)
        else:
            messages.add_message(request, messages.ERROR,
                    _("Could not find which button was pressed."))

    else:
        form = AuthenticationTokenForm(pctx.participation)

    from django.db.models import Q

    from datetime import timedelta
    tokens = AuthenticationToken.objects.filter(
            user=request.user,
            participation__course=pctx.course,
            ).filter(
                Q(revocation_time=None)
                | Q(revocation_time__gt=now_datetime - timedelta(weeks=1)))

    return render_course_page(pctx, "course/manage-auth-tokens.html", {
        "form": form,
        "new_token_message": "",
        "tokens": tokens,
        })
Пример #20
0
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)
Пример #21
0
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,
        )
Пример #22
0
def view_single_grade(pctx, participation_id, opportunity_id):
    from course.views import get_now_or_fake_time
    now_datetime = get_now_or_fake_time(pctx.request)

    participation = get_object_or_404(Participation,
            id=int(participation_id))

    if participation.course != pctx.course:
        raise SuspiciousOperation("participation does not match course")

    opportunity = get_object_or_404(GradingOpportunity, id=int(opportunity_id))

    if pctx.role in [
            participation_role.instructor,
            participation_role.teaching_assistant]:
        if not opportunity.shown_in_grade_book:
            messages.add_message(pctx.request, messages.INFO,
                    "This grade is not shown in the grade book.")
        if not opportunity.shown_in_student_grade_book:
            messages.add_message(pctx.request, messages.INFO,
                    "This grade is not shown in the student grade book.")

    elif pctx.role == participation_role.student:
        if participation != pctx.participation:
            raise PermissionDenied("may not view other people's grades")
        if not (opportunity.shown_in_grade_book
                and opportunity.shown_in_student_grade_book):
            raise PermissionDenied("grade has not been released")
    else:
        raise PermissionDenied()

    # {{{ modify sessions buttons

    if pctx.role in [
            participation_role.instructor,
            participation_role.teaching_assistant]:
        allow_session_actions = True

        request = pctx.request
        if pctx.request.method == "POST":
            action_re = re.compile("^(expire|end|reopen|regrade)_([0-9]+)$")
            for key in request.POST.keys():
                action_match = action_re.match(key)
                if action_match:
                    break

            if not action_match:
                raise SuspiciousOperation("unknown action")

            session = FlowSession.objects.get(id=int(action_match.group(2)))
            op = action_match.group(1)

            from course.flow import (
                    reopen_session,
                    regrade_session,
                    expire_flow_session_standalone,
                    finish_flow_session_standalone)

            try:
                if op == "expire":
                    expire_flow_session_standalone(
                            pctx.repo, pctx.course, session, now_datetime)
                    messages.add_message(pctx.request, messages.SUCCESS,
                            "Session expired.")

                elif op == "end":
                    finish_flow_session_standalone(
                            pctx.repo, pctx.course, session,
                            now_datetime=now_datetime)
                    messages.add_message(pctx.request, messages.SUCCESS,
                            "Session ended.")

                elif op == "regrade":
                    regrade_session(
                            pctx.repo, pctx.course, session)
                    messages.add_message(pctx.request, messages.SUCCESS,
                            "Session regraded.")

                elif op == "reopen":
                    reopen_session(session)
                    messages.add_message(pctx.request, messages.SUCCESS,
                            "Session reopened.")

                else:
                    raise SuspiciousOperation("invalid session operation")

            except Exception as e:
                messages.add_message(pctx.request, messages.ERROR,
                        "Error: %s %s" % (type(e).__name__, str(e)))
    else:
        allow_session_actions = False

    # }}}

    grade_changes = list(GradeChange.objects
            .filter(
                opportunity=opportunity,
                participation=participation)
            .order_by("grade_time")
            .prefetch_related("participation")
            .prefetch_related("participation__user")
            .prefetch_related("creator")
            .prefetch_related("opportunity"))

    state_machine = GradeStateMachine()
    state_machine.consume(grade_changes, set_is_superseded=True)

    flow_grade_aggregation_strategy_text = None

    if opportunity.flow_id:
        flow_sessions = list(FlowSession.objects
                .filter(
                    participation=participation,
                    flow_id=opportunity.flow_id,
                    )
                .order_by("start_time"))

        # {{{ fish out grade rules

        from course.content import get_flow_desc

        flow_desc = get_flow_desc(
                pctx.repo, pctx.course, opportunity.flow_id,
                pctx.course_commit_sha)
        from course.utils import (
                get_flow_access_rules,
                get_relevant_rules)
        all_flow_rules = get_flow_access_rules(pctx.course,
                participation, opportunity.flow_id, flow_desc)

        relevant_flow_rules = get_relevant_rules(
                all_flow_rules, pctx.participation.role, now())

        if hasattr(flow_desc, "grade_aggregation_strategy"):
            from course.models import GRADE_AGGREGATION_STRATEGY_CHOICES
            flow_grade_aggregation_strategy_text = (
                    dict(GRADE_AGGREGATION_STRATEGY_CHOICES)
                    [flow_desc.grade_aggregation_strategy])

        # }}}

    else:
        flow_sessions = None
        relevant_flow_rules = None

    return render_course_page(pctx, "course/gradebook-single.html", {
        "opportunity": opportunity,
        "grade_participation": participation,
        "grade_state_change_types": grade_state_change_types,
        "grade_changes": grade_changes,
        "state_machine": state_machine,
        "flow_sessions": flow_sessions,
        "allow_session_actions": allow_session_actions,
        "show_page_grades": pctx.role in [
            participation_role.instructor,
            participation_role.teaching_assistant
            ],

        "flow_rules": relevant_flow_rules,
        "flow_grade_aggregation_strategy": flow_grade_aggregation_strategy_text,
        })
Пример #23
0
def view_flow_page(pctx, flow_identifier, ordinal):
    request = pctx.request

    flow_session = find_current_flow_session(
            request, pctx.course, flow_identifier)

    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("course.flow.start_flow",
                pctx.course.identifier,
                flow_identifier)

    fpctx = FlowPageContext(pctx.repo, pctx.course, flow_identifier, ordinal,
            participation=pctx.participation,
            flow_session=flow_session)

    if fpctx.page_desc is None:
        messages.add_message(request, messages.ERROR,
                "Your session does not match the course content and needs "
                "to be reset. Course staff have been notified about this issue. "
                "Please get in touch with them to get help.")

        from django.template.loader import render_to_string
        message = render_to_string("course/session-mismatch.txt", {
            "page_data": fpctx.page_data,
            "course": pctx.course,
            "user": pctx.request.user,
            })

        from django.core.mail import send_mail
        from django.conf import settings
        send_mail("[%s] session mismatch with course content"
                % pctx.course.identifier,
                message,
                settings.ROBOT_EMAIL_FROM,
                recipient_list=[pctx.course.email])

        return redirect("course.flow.start_flow",
                pctx.course.identifier,
                flow_identifier)

    current_access_rule = fpctx.get_current_access_rule(
            flow_session, pctx.role, pctx.participation,
            get_now_or_fake_time(request))
    permissions = fpctx.page.get_modified_permissions_for_page(
            current_access_rule.permissions)

    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("course.flow.finish_flow_session_view",
                    pctx.course.identifier, flow_identifier)
        else:
            # reject answer update if flow is not in-progress
            if not flow_session.in_progress:
                raise PermissionDenied("session is not in progress")

            # reject if previous answer was final
            if (fpctx.prev_answer_visit is not None
                    and fpctx.prev_answer_visit.is_graded_answer
                    and flow_permission.change_answer
                        not in permissions):
                raise PermissionDenied("already have final answer")

            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 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_graded_answer = pressed_button == "submit"
                page_visit.save()

                answer_was_graded = page_visit.is_graded_answer
                may_change_answer = (
                    not answer_was_graded
                    or flow_permission.change_answer
                    in permissions)

                feedback = fpctx.page.grade(
                        page_context, page_data.data, page_visit.answer,
                        grade_data=None)

                if page_visit.is_graded_answer:
                    grade = FlowPageVisitGrade()
                    grade.visit = page_visit
                    grade.max_points = fpctx.page.max_points(page_data.data)
                    grade.graded_at_git_commit_sha = fpctx.flow_commit_sha

                    if feedback is not None:
                        grade.correctness = feedback.correctness
                        grade.feedback = feedback.as_json()

                    grade.save()

                    del grade

                if (pressed_button == "save_and_next"
                        and not will_receive_feedback(permissions)):
                    return redirect("course.flow.view_flow_page",
                            pctx.course.identifier,
                            flow_identifier,
                            fpctx.ordinal + 1)
                elif (pressed_button == "save_and_finish"
                        and not will_receive_feedback(permissions)):
                    return redirect("course.flow.finish_flow_session_view",
                            pctx.course.identifier, flow_identifier)
                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_graded_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)

        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:
                    if most_recent_grade.feedback is not None:
                        from course.page import AnswerFeedback
                        feedback = AnswerFeedback.from_json(
                                most_recent_grade.feedback)
                    else:
                        feedback = None

                    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
                or (
                    (flow_permission.see_correctness_after_completion
                        in permissions)
                    and not flow_session.in_progress))

        show_answer = flow_permission.see_answer 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
        pass
    else:
        show_answer = flow_permission.see_answer 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

    args = {
        "flow_identifier": fpctx.flow_identifier,
        "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,

        "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,
    }

    if fpctx.page.expects_answer():
        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)
Пример #24
0
def issue_exam_ticket(request):
    # must import locally for mock to work
    from course.views import get_now_or_fake_time
    now_datetime = get_now_or_fake_time(request)

    if request.method == "POST":
        form = IssueTicketForm(now_datetime, request.POST)

        if form.is_valid():
            exam = form.cleaned_data["exam"]
            try:
                participation = Participation.objects.get(
                    course=exam.course,
                    user=form.cleaned_data["user"],
                    status=participation_status.active,
                )

            except ObjectDoesNotExist:
                messages.add_message(request, messages.ERROR,
                                     _("User is not enrolled in course."))
                participation = None

            if participation is not None:
                if form.cleaned_data["revoke_prior"]:
                    ExamTicket.objects.filter(
                        exam=exam,
                        participation=participation,
                        state__in=(
                            exam_ticket_states.valid,
                            exam_ticket_states.used,
                        )).update(state=exam_ticket_states.revoked)

                ticket = ExamTicket()
                ticket.exam = exam
                ticket.participation = participation
                ticket.creator = request.user
                ticket.state = exam_ticket_states.valid
                ticket.code = gen_ticket_code()
                ticket.valid_start_time = form.cleaned_data["valid_start_time"]
                ticket.valid_end_time = form.cleaned_data["valid_end_time"]
                ticket.restrict_to_facility = \
                        form.cleaned_data["restrict_to_facility"]
                ticket.save()

                messages.add_message(
                    request, messages.SUCCESS,
                    _("Ticket issued for <b>%(participation)s</b>. "
                      "The ticket code is <b>%(ticket_code)s</b>.") % {
                          "participation": participation,
                          "ticket_code": ticket.code
                      })

                form = IssueTicketForm(now_datetime, initial_exam=exam)

    else:
        form = IssueTicketForm(now_datetime)

    return render(request, "generic-form.html", {
        "form_description": _("Issue Exam Ticket"),
        "form": form,
    })
Пример #25
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),
            })
Пример #26
0
def finish_flow_session_view(pctx, flow_identifier):
    request = pctx.request

    flow_session = find_current_flow_session(
            request, pctx.course, flow_identifier)

    if flow_session is None:
        messages.add_message(request, messages.WARNING,
                "No session record found for this flow. "
                "Redirected to flow start page.")

        return redirect("course.flow.start_flow",
                pctx.course.identifier,
                flow_identifier)

    fctx = FlowContext(pctx.repo, pctx.course, flow_identifier,
            participation=pctx.participation,
            flow_session=flow_session)

    current_access_rule = fctx.get_current_access_rule(
            flow_session, pctx.role, pctx.participation,
            get_now_or_fake_time(request))

    answer_visits = assemble_answer_visits(flow_session)

    from course.content import markup_to_html
    completion_text = markup_to_html(
            fctx.course, fctx.repo, fctx.flow_commit_sha,
            fctx.flow_desc.completion_text)

    (answered_count, unanswered_count) = count_answered(
            fctx, flow_session, answer_visits)
    is_graded_flow = bool(answered_count + unanswered_count)

    if flow_permission.view not in current_access_rule.permissions:
        raise PermissionDenied()

    def render_finish_response(template, **kwargs):
        render_args = {
            "flow_identifier": fctx.flow_identifier,
            "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")

        # Actually end the flow session
        request.session["flow_session_id"] = None

        grade_info = finish_flow_session(fctx, flow_session, current_access_rule)

        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,
                    completion_text=completion_text)

            # }}}

    if (not is_graded_flow
            and fctx.flow_commit_sha == fctx.course_commit_sha):
        # Not serious--no questions in flow, and no new version available.
        # No need to end the flow visit.

        return render_finish_response(
                "course/flow-completion.html",
                last_page_nr=flow_session.page_count-1,
                completion_text=completion_text)

    elif not flow_session.in_progress:
        # Just reviewing: re-show grades.
        grade_info = gather_grade_info(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,
                answered_count=answered_count,
                unanswered_count=unanswered_count,
                total_count=answered_count+unanswered_count)
Пример #27
0
def view_grades_by_opportunity(pctx, opp_id):
    from course.views import get_now_or_fake_time
    now_datetime = get_now_or_fake_time(pctx.request)

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

    opportunity = get_object_or_404(GradingOpportunity, id=int(opp_id))

    if pctx.course != opportunity.course:
        raise SuspiciousOperation(_("opportunity from wrong course"))

    # {{{ batch sessions form

    batch_session_ops_form = None
    if pctx.role == participation_role.instructor and opportunity.flow_id:
        cursor = connection.cursor()
        cursor.execute(
            "select distinct access_rules_tag from course_flowsession "
            "where course_id = %s and flow_id = %s "
            "order by access_rules_tag", (pctx.course.id, opportunity.flow_id))
        session_rule_tags = [
            mangle_session_access_rule_tag(row[0])
            for row in cursor.fetchall()
        ]

        request = pctx.request
        if request.method == "POST":
            batch_session_ops_form = ModifySessionsForm(
                session_rule_tags, request.POST, request.FILES)
            if "expire" in request.POST:
                op = "expire"
            elif "end" in request.POST:
                op = "end"
            elif "regrade" in request.POST:
                op = "regrade"
            elif "recalculate" in request.POST:
                op = "recalculate"
            else:
                raise SuspiciousOperation(_("invalid operation"))

            if batch_session_ops_form.is_valid():
                rule_tag = batch_session_ops_form.cleaned_data["rule_tag"]
                past_due_only = batch_session_ops_form.cleaned_data[
                    "past_due_only"]

                if rule_tag == RULE_TAG_NONE_STRING:
                    rule_tag = None
                try:
                    if op == "expire":
                        count = expire_in_progress_sessions(
                            pctx.repo,
                            pctx.course,
                            opportunity.flow_id,
                            rule_tag,
                            now_datetime,
                            past_due_only=past_due_only)

                        messages.add_message(
                            pctx.request, messages.SUCCESS,
                            _("%d session(s) expired.") % count)

                    elif op == "end":
                        count = finish_in_progress_sessions(
                            pctx.repo,
                            pctx.course,
                            opportunity.flow_id,
                            rule_tag,
                            now_datetime,
                            past_due_only=past_due_only)

                        messages.add_message(pctx.request, messages.SUCCESS,
                                             _("%d session(s) ended.") % count)

                    elif op == "regrade":
                        count = regrade_ended_sessions(pctx.repo, pctx.course,
                                                       opportunity.flow_id,
                                                       rule_tag)

                        messages.add_message(
                            pctx.request, messages.SUCCESS,
                            _("%d session(s) regraded.") % count)

                    elif op == "recalculate":
                        count = recalculate_ended_sessions(
                            pctx.repo, pctx.course, opportunity.flow_id,
                            rule_tag)

                        messages.add_message(
                            pctx.request, messages.SUCCESS,
                            _("Grade recalculated for %d session(s).") % count)

                    else:
                        raise SuspiciousOperation("invalid operation")
                except Exception as e:
                    messages.add_message(
                        pctx.request, messages.ERROR,
                        string_concat(
                            pgettext_lazy("Starting of Error message",
                                          "Error"),
                            ": %(err_type)s %(err_str)s") % {
                                "err_type": type(e).__name__,
                                "err_str": str(e)
                            })
                    raise

        else:
            batch_session_ops_form = ModifySessionsForm(session_rule_tags)

    # }}}

    # NOTE: It's important that these queries are sorted consistently,
    # also consistently with the code below.

    participations = list(
        Participation.objects.filter(
            course=pctx.course, status=participation_status.active).order_by(
                "id").select_related("user"))

    grade_changes = list(
        GradeChange.objects.filter(opportunity=opportunity).order_by(
            "participation__id",
            "grade_time").select_related("participation").select_related(
                "participation__user").select_related("opportunity"))

    idx = 0

    finished_sessions = 0
    total_sessions = 0

    grade_table = []
    for participation in participations:
        while (idx < len(grade_changes)
               and grade_changes[idx].participation.id < participation.id):
            idx += 1

        my_grade_changes = []
        while (idx < len(grade_changes)
               and grade_changes[idx].participation.pk == participation.pk):
            my_grade_changes.append(grade_changes[idx])
            idx += 1

        state_machine = GradeStateMachine()
        state_machine.consume(my_grade_changes)

        if opportunity.flow_id:
            flow_sessions = (FlowSession.objects.filter(
                participation=participation,
                flow_id=opportunity.flow_id,
            ).order_by("start_time"))

            for fsession in flow_sessions:
                total_sessions += 1
                if not fsession.in_progress:
                    finished_sessions += 1

        else:
            flow_sessions = None

        grade_table.append(
            OpportunityGradeInfo(grade_state_machine=state_machine,
                                 flow_sessions=flow_sessions))

    grade_table = sorted(zip(participations, grade_table),
                         key=lambda (participation, grades):
                         (participation.user.last_name.lower(),
                          participation.user.first_name.lower()))

    return render_course_page(
        pctx, "course/gradebook-by-opp.html", {
            "opportunity": opportunity,
            "participations": participations,
            "grade_state_change_types": grade_state_change_types,
            "grade_table": grade_table,
            "batch_session_ops_form": batch_session_ops_form,
            "total_sessions": total_sessions,
            "finished_sessions": finished_sessions,
        })
Пример #28
0
def view_single_grade(pctx, participation_id, opportunity_id):
    now_datetime = get_now_or_fake_time(pctx.request)

    participation = get_object_or_404(Participation,
            id=int(participation_id))

    if participation.course != pctx.course:
        raise SuspiciousOperation(_("participation does not match course"))

    opportunity = get_object_or_404(GradingOpportunity, id=int(opportunity_id))

    if pctx.role in [
            participation_role.instructor,
            participation_role.teaching_assistant]:
        if not opportunity.shown_in_grade_book:
            messages.add_message(pctx.request, messages.INFO,
                    _("This grade is not shown in the grade book."))
        if not opportunity.shown_in_student_grade_book:
            messages.add_message(pctx.request, messages.INFO,
                    _("This grade is not shown in the student grade book."))

    elif pctx.role == participation_role.student:
        if participation != pctx.participation:
            raise PermissionDenied(_("may not view other people's grades"))
        if not (opportunity.shown_in_grade_book
                and opportunity.shown_in_student_grade_book):
            raise PermissionDenied(_("grade has not been released"))
    else:
        raise PermissionDenied()

    # {{{ modify sessions buttons

    if pctx.role in [
            participation_role.instructor,
            participation_role.teaching_assistant]:
        allow_session_actions = True

        request = pctx.request
        if pctx.request.method == "POST":
            action_re = re.compile("^([a-z]+)_([0-9]+)$")
            for key in request.POST.keys():
                action_match = action_re.match(key)
                if action_match:
                    break

            if not action_match:
                raise SuspiciousOperation(_("unknown action"))

            session = FlowSession.objects.get(id=int(action_match.group(2)))
            op = action_match.group(1)

            from course.flow import (
                    regrade_session,
                    recalculate_session_grade,
                    expire_flow_session_standalone,
                    finish_flow_session_standalone)

            try:
                if op == "expire":
                    expire_flow_session_standalone(
                            pctx.repo, pctx.course, session, now_datetime)
                    messages.add_message(pctx.request, messages.SUCCESS,
                            _("Session expired."))

                elif op == "end":
                    finish_flow_session_standalone(
                            pctx.repo, pctx.course, session,
                            now_datetime=now_datetime)
                    messages.add_message(pctx.request, messages.SUCCESS,
                            _("Session ended."))

                elif op == "regrade":
                    regrade_session(
                            pctx.repo, pctx.course, session)
                    messages.add_message(pctx.request, messages.SUCCESS,
                            _("Session regraded."))

                elif op == "recalculate":
                    recalculate_session_grade(
                            pctx.repo, pctx.course, session)
                    messages.add_message(pctx.request, messages.SUCCESS,
                            _("Session grade recalculated."))

                else:
                    raise SuspiciousOperation(_("invalid session operation"))

            except Exception as e:
                messages.add_message(pctx.request, messages.ERROR,
                        string_concat(
                            pgettext_lazy("Starting of Error message",
                                "Error"),
                            ": %(err_type)s %(err_str)s")
                        % {
                            "err_type": type(e).__name__,
                            "err_str": str(e)})
    else:
        allow_session_actions = False

    # }}}

    grade_changes = list(GradeChange.objects
            .filter(
                opportunity=opportunity,
                participation=participation)
            .order_by("grade_time")
            .select_related("participation")
            .select_related("participation__user")
            .select_related("creator")
            .select_related("opportunity"))

    state_machine = GradeStateMachine()
    state_machine.consume(grade_changes, set_is_superseded=True)

    if opportunity.flow_id:
        flow_sessions = list(FlowSession.objects
                .filter(
                    participation=participation,
                    flow_id=opportunity.flow_id,
                    )
                .order_by("start_time"))

        from collections import namedtuple
        SessionProperties = namedtuple(  # noqa
                "SessionProperties",
                ["due", "grade_description"])

        from course.utils import get_session_grading_rule
        from course.content import get_flow_desc

        try:
            flow_desc = get_flow_desc(pctx.repo, pctx.course,
                    opportunity.flow_id, pctx.course_commit_sha)
        except ObjectDoesNotExist:
            flow_sessions_and_session_properties = None
        else:
            flow_sessions_and_session_properties = []
            for session in flow_sessions:
                grading_rule = get_session_grading_rule(
                        session, pctx.role, flow_desc, now_datetime)

                session_properties = SessionProperties(
                        due=grading_rule.due,
                        grade_description=grading_rule.description)
                flow_sessions_and_session_properties.append(
                        (session, session_properties))

    else:
        flow_sessions_and_session_properties = None

    avg_grade_percentage, avg_grade_population = average_grade(opportunity)

    return render_course_page(pctx, "course/gradebook-single.html", {
        "opportunity": opportunity,
        "avg_grade_percentage": avg_grade_percentage,
        "avg_grade_population": avg_grade_population,
        "grade_participation": participation,
        "grade_state_change_types": grade_state_change_types,
        "grade_changes": grade_changes,
        "state_machine": state_machine,
        "flow_sessions_and_session_properties": flow_sessions_and_session_properties,
        "allow_session_actions": allow_session_actions,
        "show_privileged_info": pctx.role in [
            participation_role.instructor,
            participation_role.teaching_assistant
            ],
        })
Пример #29
0
def view_calendar(pctx):
    from course.content import markup_to_html

    events_json = []

    from course.content import get_raw_yaml_from_repo
    try:
        event_descr = get_raw_yaml_from_repo(pctx.repo,
                                             pctx.course.events_file,
                                             pctx.course_commit_sha)
    except ObjectDoesNotExist:
        event_descr = {}

    event_kinds_desc = event_descr.get("event_kinds", {})
    event_info_desc = event_descr.get("events", {})

    event_info_list = []

    for event in (Event.objects.filter(
            course=pctx.course, shown_in_calendar=True).order_by("-time")):
        kind_desc = event_kinds_desc.get(event.kind)

        human_title = six.text_type(event)

        event_json = {
            "id": event.id,
            "start": event.time.isoformat(),
            "allDay": event.all_day,
        }
        if event.end_time is not None:
            event_json["end"] = event.end_time.isoformat()

        if kind_desc is not None:
            if "color" in kind_desc:
                event_json["color"] = kind_desc["color"]
            if "title" in kind_desc:
                if event.ordinal is not None:
                    human_title = kind_desc["title"].format(nr=event.ordinal)
                else:
                    human_title = kind_desc["title"]

        description = None
        event_desc = event_info_desc.get(six.text_type(event))
        if event_desc is not None:
            if "description" in event_desc:
                description = markup_to_html(pctx.course, pctx.repo,
                                             pctx.course_commit_sha,
                                             event_desc["description"])

            if "title" in event_desc:
                human_title = event_desc["title"]

            if "color" in event_desc:
                event_json["color"] = event_desc["color"]

        event_json["title"] = human_title

        if description:
            event_json["url"] = "#event-%d" % event.id

            start_time = event.time
            end_time = event.end_time

            if event.all_day:
                start_time = start_time.date()
                end_time = end_time.date()

            event_info_list.append(
                EventInfo(id=event.id,
                          human_title=human_title,
                          start_time=start_time,
                          end_time=end_time,
                          description=description))

        events_json.append(event_json)

    from course.views import get_now_or_fake_time
    default_date = get_now_or_fake_time(pctx.request).date()
    if pctx.course.end_date is not None and default_date > pctx.course.end_date:
        default_date = pctx.course.end_date

    from json import dumps
    return render_course_page(
        pctx, "course/calendar.html", {
            "events_json": dumps(events_json),
            "event_info_list": event_info_list,
            "default_date": default_date.isoformat(),
        })