Example #1
0
def flow_analytics(pctx, flow_id):
    if pctx.role not in [
            participation_role.teaching_assistant,
            participation_role.instructor,
            participation_role.observer,
            ]:
        raise PermissionDenied(_("must be at least TA to view analytics"))

    restrict_to_first_attempt = int(
            bool(pctx.request.GET.get("restrict_to_first_attempt") == "1"))

    try:
        stats_list = make_page_answer_stats_list(pctx, flow_id,
                restrict_to_first_attempt)
    except ObjectDoesNotExist:
        messages.add_message(pctx.request, messages.ERROR,
                _("Flow '%s' was not found in the repository, but it exists in "
                    "the database--maybe it was deleted?")
                % flow_id)
        raise http.Http404()

    return render_course_page(pctx, "course/analytics-flow.html", {
        "flow_identifier": flow_id,
        "grade_histogram": make_grade_histogram(pctx, flow_id),
        "page_answer_stats_list": stats_list,
        "time_histogram": make_time_histogram(pctx, flow_id),
        "participant_count": count_participants(pctx, flow_id),
        "restrict_to_first_attempt": restrict_to_first_attempt,
        })
Example #2
0
def test_flow(pctx):
    if not pctx.has_permission(pperm.test_flow):
        raise PermissionDenied()

    from course.content import list_flow_ids
    flow_ids = list_flow_ids(pctx.repo, pctx.course_commit_sha)

    request = pctx.request
    if request.method == "POST":
        form = FlowTestForm(flow_ids, request.POST, request.FILES)
        if "test" not in request.POST:
            raise SuspiciousOperation(_("invalid operation"))

        if form.is_valid():
            return redirect("relate-view_start_flow",
                    pctx.course.identifier,
                    form.cleaned_data["flow_id"])

    else:
        form = FlowTestForm(flow_ids)

    return render_course_page(pctx, "course/generic-course-form.html", {
        "form": form,
        "form_description": _("Test Flow"),
    })
Example #3
0
def grant_exception(pctx):
    if pctx.role not in [
            participation_role.instructor,
            participation_role.teaching_assistant]:
        raise PermissionDenied("must be instructor or TA to grant exceptions")

    from course.content import list_flow_ids
    flow_ids = list_flow_ids(pctx.repo, pctx.course_commit_sha)

    request = pctx.request
    if request.method == "POST":
        form = ExceptionStage1Form(pctx.course, flow_ids, request.POST)

        if form.is_valid():
            return redirect("course.views.grant_exception_stage_2",
                    pctx.course.identifier,
                    form.cleaned_data["participation"].id,
                    form.cleaned_data["flow_id"])

    else:
        form = ExceptionStage1Form(pctx.course, flow_ids)

    return render_course_page(pctx, "course/generic-course-form.html", {
        "form": form,
        "form_description": "Grant Exception",
    })
Example #4
0
def course_page(pctx):
    from course.content import get_processed_course_chunks
    chunks = get_processed_course_chunks(
            pctx.course, pctx.repo, pctx.course_commit_sha, pctx.course_desc,
            pctx.role, get_now_or_fake_time(pctx.request),
            facilities=pctx.request.relate_facilities)

    show_enroll_button = (
            pctx.course.accepts_enrollment
            and pctx.role == participation_role.unenrolled)

    if pctx.request.user.is_authenticated() and Participation.objects.filter(
            user=pctx.request.user,
            course=pctx.course,
            status=participation_status.requested).count():
        show_enroll_button = False

        messages.add_message(pctx.request, messages.INFO,
                _("Your enrollment request is pending. You will be "
                "notified once it has been acted upon."))

    return render_course_page(pctx, "course/course-page.html", {
        "chunks": chunks,
        "show_enroll_button": show_enroll_button,
        })
Example #5
0
def course_page(pctx):
    from course.content import get_processed_course_chunks
    chunks = get_processed_course_chunks(
            pctx.course, pctx.repo, pctx.course_commit_sha, pctx.course_desc,
            pctx.role, get_now_or_fake_time(pctx.request))

    return render_course_page(pctx, "course/course-page.html", {
        "chunks": chunks,
        })
Example #6
0
def create_recurring_events(pctx):
    if not pctx.has_permission(pperm.edit_events):
        raise PermissionDenied(_("may not edit events"))

    request = pctx.request

    if request.method == "POST":
        form = RecurringEventForm(request.POST, request.FILES)
        if form.is_valid():
            if form.cleaned_data["starting_ordinal"] is not None:
                starting_ordinal = form.cleaned_data["starting_ordinal"]
                starting_ordinal_specified = True
            else:
                starting_ordinal = 1
                starting_ordinal_specified = False

            while True:
                try:
                    _create_recurring_events_backend(
                        course=pctx.course,
                        time=form.cleaned_data["time"],
                        kind=form.cleaned_data["kind"],
                        starting_ordinal=starting_ordinal,
                        interval=form.cleaned_data["interval"],
                        count=form.cleaned_data["count"],
                        duration_in_minutes=(form.cleaned_data["duration_in_minutes"]),
                    )
                except EventAlreadyExists as e:
                    if starting_ordinal_specified:
                        messages.add_message(
                            request,
                            messages.ERROR,
                            string_concat("%(err_type)s: %(err_str)s. ", _("No events created."))
                            % {"err_type": type(e).__name__, "err_str": str(e)},
                        )
                    else:
                        starting_ordinal += 10
                        continue

                except Exception as e:
                    messages.add_message(
                        request,
                        messages.ERROR,
                        string_concat("%(err_type)s: %(err_str)s. ", _("No events created."))
                        % {"err_type": type(e).__name__, "err_str": str(e)},
                    )
                else:
                    messages.add_message(request, messages.SUCCESS, _("Events created."))

                break
    else:
        form = RecurringEventForm()

    return render_course_page(
        pctx, "course/generic-course-form.html", {"form": form, "form_description": _("Create recurring events")}
    )
Example #7
0
    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)
Example #8
0
def course_page(pctx):
    # type: (CoursePageContext) -> http.HttpResponse
    from course.content import get_processed_page_chunks, get_course_desc
    page_desc = get_course_desc(pctx.repo, pctx.course, pctx.course_commit_sha)

    chunks = get_processed_page_chunks(
            pctx.course, pctx.repo, pctx.course_commit_sha, page_desc,
            pctx.role_identifiers(), get_now_or_fake_time(pctx.request),
            facilities=pctx.request.relate_facilities)

    show_enroll_button = (
            pctx.course.accepts_enrollment
            and pctx.participation is None)

    if pctx.request.user.is_authenticated and Participation.objects.filter(
            user=pctx.request.user,
            course=pctx.course,
            status=participation_status.requested).count():
        show_enroll_button = False

        messages.add_message(pctx.request, messages.INFO,
                _("Your enrollment request is pending. You will be "
                "notified once it has been acted upon."))

        from course.models import ParticipationPreapproval

        if ParticipationPreapproval.objects.filter(
                course=pctx.course).exclude(institutional_id=None).count():
            if not pctx.request.user.institutional_id:
                from django.urls import reverse
                messages.add_message(pctx.request, messages.WARNING,
                        _("This course uses institutional ID for "
                        "enrollment preapproval, please <a href='%s' "
                        "role='button' class='btn btn-md btn-primary'>"
                        "fill in your institutional ID &nbsp;&raquo;"
                        "</a> in your profile.")
                        % (
                            reverse("relate-user_profile")
                            + "?referer="
                            + pctx.request.path
                            + "&set_inst_id=1"
                            )
                        )
            else:
                if pctx.course.preapproval_require_verified_inst_id:
                    messages.add_message(pctx.request, messages.WARNING,
                            _("Your institutional ID is not verified or "
                            "preapproved. Please contact your course "
                            "staff.")
                            )

    return render_course_page(pctx, "course/course-page.html", {
        "chunks": chunks,
        "show_enroll_button": show_enroll_button,
        })
Example #9
0
def flow_analytics(pctx, flow_identifier):
    if pctx.role not in [
            participation_role.teaching_assistant,
            participation_role.instructor]:
        raise PermissionDenied("must be at least TA to view analytics")

    return render_course_page(pctx, "course/analytics-flow.html", {
        "flow_identifier": flow_identifier,
        "grade_histogram": make_grade_histogram(pctx, flow_identifier),
        "page_answer_stats_list": make_page_answer_stats_list(pctx, flow_identifier),
        "time_histogram": make_time_histogram(pctx, flow_identifier),
        })
Example #10
0
def create_preapprovals(pctx):
    if pctx.role != participation_role.instructor:
        raise PermissionDenied(_("only instructors may do that"))

    request = pctx.request

    if request.method == "POST":
        form = BulkPreapprovalsForm(request.POST)
        if form.is_valid():

            created_count = 0
            exist_count = 0

            role = form.cleaned_data["role"]
            for l in form.cleaned_data["emails"].split("\n"):
                l = l.strip()

                if not l:
                    continue

                try:
                    preapproval = ParticipationPreapproval.objects.get(
                            email__iexact=l,
                            course=pctx.course)
                except ParticipationPreapproval.DoesNotExist:
                    pass
                else:
                    exist_count += 1
                    continue

                preapproval = ParticipationPreapproval()
                preapproval.email = l
                preapproval.course = pctx.course
                preapproval.role = role
                preapproval.creator = request.user
                preapproval.save()

                created_count += 1

            messages.add_message(request, messages.INFO,
                    _("%(n_created)d preapprovals created, "
                    "%(n_exist)d already existed.") % {
                        'n_created': created_count,
                        'n_exist': exist_count})
            return redirect("relate-home")

    else:
        form = BulkPreapprovalsForm()

    return render_course_page(pctx, "course/generic-course-form.html", {
        "form": form,
        "form_description": _("Create Participation Preapprovals"),
    })
Example #11
0
def manage_instant_flow_requests(pctx):
    if pctx.role != participation_role.instructor:
        raise PermissionDenied(
                _("must be instructor to manage instant flow requests"))

    from course.content import list_flow_ids
    flow_ids = list_flow_ids(pctx.repo, pctx.course_commit_sha)

    request = pctx.request
    if request.method == "POST":
        form = InstantFlowRequestForm(flow_ids, request.POST, request.FILES)
        if "add" in request.POST:
            op = "add"
        elif "cancel" in request.POST:
            op = "cancel"
        else:
            raise SuspiciousOperation(_("invalid operation"))

        now_datetime = get_now_or_fake_time(pctx.request)

        if form.is_valid():
            if op == "add":

                from datetime import timedelta
                ifr = InstantFlowRequest()
                ifr.course = pctx.course
                ifr.flow_id = form.cleaned_data["flow_id"]
                ifr.start_time = now_datetime
                ifr.end_time = (
                        now_datetime + timedelta(
                            minutes=form.cleaned_data["duration_in_minutes"]))
                ifr.save()

            elif op == "cancel":
                (InstantFlowRequest.objects
                        .filter(
                            course=pctx.course,
                            start_time__lte=now_datetime,
                            end_time__gte=now_datetime,
                            cancelled=False)
                        .order_by("start_time")
                        .update(cancelled=True))
            else:
                raise SuspiciousOperation(_("invalid operation"))

    else:
        form = InstantFlowRequestForm(flow_ids)

    return render_course_page(pctx, "course/generic-course-form.html", {
        "form": form,
        "form_description": _("Manage Instant Flow Requests"),
    })
Example #12
0
def flow_list(pctx):
    if not pctx.has_permission(pperm.view_analytics):
        raise PermissionDenied(_("may not view analytics"))

    cursor = connection.cursor()

    cursor.execute("select distinct flow_id from course_flowsession "
            "where course_id=%s order by flow_id",
            [pctx.course.id])
    flow_ids = [row[0] for row in cursor.fetchall()]

    return render_course_page(pctx, "course/analytics-flows.html", {
        "flow_ids": flow_ids,
        })
Example #13
0
def view_markup_sandbox(pctx):
    if pctx.role not in [
            participation_role.instructor,
            participation_role.teaching_assistant]:
        raise PermissionDenied(
                ugettext("must be instructor or TA to access sandbox"))

    request = pctx.request
    preview_text = ""

    from course.models import get_user_status
    ustatus = get_user_status(request.user)

    def make_form(data=None):
        help_text = (ugettext("Enter <a href=\"http://documen.tician.de/"
                "relate/content.html#relate-markup\">"
                "RELATE markup</a>."))
        return SandboxForm(
                None, "markdown", ustatus.editor_mode,
                help_text,
                data)

    if request.method == "POST":
        form = make_form(request.POST)

        if form.is_valid():
            from course.content import markup_to_html
            try:
                preview_text = markup_to_html(
                        pctx.course, pctx.repo, pctx.course_commit_sha,
                        form.cleaned_data["content"])
            except:
                import sys
                tp, e, _ = sys.exc_info()

                messages.add_message(pctx.request, messages.ERROR,
                        ugettext("Markup failed to render")
                        + ": "
                        + "%(err_type)s: %(err_str)s" % {
                            "err_type": tp.__name__, "err_str": e})

        form = make_form(request.POST)

    else:
        form = make_form()

    return render_course_page(pctx, "course/sandbox-markup.html", {
        "form": form,
        "preview_text": preview_text,
    })
Example #14
0
def create_recurring_events(pctx):
    if pctx.role != participation_role.instructor:
        raise PermissionDenied("only instructors may do that")

    request = pctx.request

    if request.method == "POST":
        form = RecurringEventForm(request.POST, request.FILES)
        if form.is_valid():

            time = form.cleaned_data["time"]
            ordinal = form.cleaned_data["starting_ordinal"]
            interval = form.cleaned_data["interval"]

            import datetime

            for i in xrange(form.cleaned_data["count"]):
                evt = Event()
                evt.course = pctx.course
                evt.kind = form.cleaned_data["kind"]
                evt.ordinal = ordinal
                evt.time = time

                if form.cleaned_data["duration_in_minutes"]:
                    evt.end_time = evt.time + datetime.timedelta(
                            minutes=form.cleaned_data["duration_in_minutes"])
                evt.save()

                if interval == "weekly":
                    date = time.date()
                    date += datetime.timedelta(weeks=1)
                    time = time.tzinfo.localize(
                            datetime.datetime(date.year, date.month, date.day,
                                time.hour, time.minute, time.second))
                    del date
                else:
                    raise ValueError("unknown interval: %s" % interval)

                ordinal += 1

            messages.add_message(request, messages.SUCCESS,
                    "Events created.")
    else:
        form = RecurringEventForm()

    return render_course_page(pctx, "course/generic-course-form.html", {
        "form": form,
        "form_description": "Create recurring events",
    })
Example #15
0
def import_grades(pctx):
    if pctx.role != participation_role.instructor:
        raise PermissionDenied()

    form_text = ""
    request = pctx.request
    if request.method == "POST":
        form = ImportGradesForm(
                pctx.course, request.POST, request.FILES)

        is_import = "import" in request.POST
        if form.is_valid():
            try:
                grade_changes = csv_to_grade_changes(
                        course=pctx.course,
                        grading_opportunity=form.cleaned_data["grading_opportunity"],
                        attempt_id=form.cleaned_data["attempt_id"],
                        file_contents=request.FILES["file"],
                        id_column=form.cleaned_data["id_column"],
                        points_column=form.cleaned_data["points_column"],
                        feedback_column=form.cleaned_data["feedback_column"],
                        max_points=form.cleaned_data["max_points"],
                        creator=request.user,
                        grade_time=now(),
                        has_header=form.cleaned_data["format"] == "csvhead")
            except Exception as e:
                messages.add_message(pctx.request, messages.ERROR,
                        "Error: %s %s" % (type(e).__name__, str(e)))
            else:
                if is_import:
                    GradeChange.objects.bulk_create(grade_changes)
                    messages.add_message(pctx.request, messages.SUCCESS,
                            "%d grades imported." % len(grade_changes))
                else:
                    from django.template.loader import render_to_string
                    form_text = render_to_string(
                            "course/grade-import-preview.html", {
                                "grade_changes": grade_changes,
                                })

    else:
        form = ImportGradesForm(pctx.course)

    return render_course_page(pctx, "course/generic-course-form.html", {
        "form_description": "Import Grade Data",
        "form": form,
        "form_text": form_text,
        })
Example #16
0
def renumber_events(pctx):
    if pctx.role not in [
            participation_role.instructor,
            participation_role.teaching_assistant]:
        raise PermissionDenied(_("only instructors and TAs may do that"))

    request = pctx.request

    if request.method == "POST":
        form = RenumberEventsForm(request.POST, request.FILES)
        if form.is_valid():
            events = list(Event.objects
                    .filter(course=pctx.course, kind=form.cleaned_data["kind"])
                    .order_by('time'))

            if events:
                queryset = (Event.objects
                    .filter(course=pctx.course, kind=form.cleaned_data["kind"]))

                queryset.delete()

                ordinal = form.cleaned_data["starting_ordinal"]
                for event in events:
                    new_event = Event()
                    new_event.course = pctx.course
                    new_event.kind = form.cleaned_data["kind"]
                    new_event.ordinal = ordinal
                    new_event.time = event.time
                    new_event.end_time = event.end_time
                    new_event.all_day = event.all_day
                    new_event.shown_in_calendar = event.shown_in_calendar
                    new_event.save()

                    ordinal += 1

                messages.add_message(request, messages.SUCCESS,
                        _("Events renumbered."))
            else:
                messages.add_message(request, messages.ERROR,
                        _("No events found."))

    else:
        form = RenumberEventsForm()

    return render_course_page(pctx, "course/generic-course-form.html", {
        "form": form,
        "form_description": _("Renumber events"),
    })
Example #17
0
def view_grading_opportunity_list(pctx):
    if pctx.role not in [
            participation_role.instructor,
            participation_role.teaching_assistant]:
        raise PermissionDenied(_("must be instructor or TA to view grades"))

    grading_opps = list((GradingOpportunity.objects
            .filter(
                course=pctx.course,
                shown_in_grade_book=True,
                )
            .order_by("identifier")))

    return render_course_page(pctx, "course/gradebook-opp-list.html", {
        "grading_opps": grading_opps,
        })
Example #18
0
def flow_list(pctx):
    if pctx.role not in [
            participation_role.teaching_assistant,
            participation_role.instructor]:
        raise PermissionDenied("must be at least TA to view analytics")

    cursor = connection.cursor()

    cursor.execute("select distinct flow_id from course_flowsession "
            "where course_id=%s order by flow_id",
            [pctx.course.id])
    flow_ids = [row[0] for row in cursor.fetchall()]

    return render_course_page(pctx, "course/analytics-flows.html", {
        "flow_ids": flow_ids,
        })
Example #19
0
def view_participant_list(pctx):
    if pctx.role not in [
            participation_role.instructor,
            participation_role.teaching_assistant]:
        raise PermissionDenied(_("must be instructor or TA to view grades"))

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

    return render_course_page(pctx, "course/gradebook-participant-list.html", {
        "participations": participations,
        })
Example #20
0
def static_page(pctx, page_path):
    from course.content import get_staticpage_desc, get_processed_page_chunks
    try:
        page_desc = get_staticpage_desc(pctx.repo, pctx.course,
                pctx.course_commit_sha, "staticpages/"+page_path+".yml")
    except ObjectDoesNotExist:
        raise http.Http404()

    chunks = get_processed_page_chunks(
            pctx.course, pctx.repo, pctx.course_commit_sha, page_desc,
            pctx.role, get_now_or_fake_time(pctx.request),
            facilities=pctx.request.relate_facilities)

    return render_course_page(pctx, "course/static-page.html", {
        "chunks": chunks,
        "show_enroll_button": False,
        })
Example #21
0
def regrade_not_for_credit_flows_view(pctx):
    if pctx.role != participation_role.instructor:
        raise PermissionDenied(_("must be instructor to regrade flows"))

    from course.content import list_flow_ids

    flow_ids = list_flow_ids(pctx.repo, pctx.course_commit_sha)

    request = pctx.request
    if request.method == "POST":
        form = RegradeFlowForm(flow_ids, request.POST, request.FILES)
        if form.is_valid():
            sessions = FlowSession.objects.filter(course=pctx.course, flow_id=form.cleaned_data["flow_id"])
            if form.cleaned_data["access_rules_tag"]:
                sessions = sessions.filter(access_rules_tag=form.cleaned_data["access_rules_tag"])

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

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

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

            messages.add_message(request, messages.SUCCESS, _("%d sessions regraded.") % count)
    else:
        form = RegradeFlowForm(flow_ids)

    return render_course_page(
        pctx,
        "course/generic-course-form.html",
        {
            "form": form,
            "form_text": string_concat(
                "<p>",
                _(
                    "This regrading process is only intended for flows that do"
                    "not show up in the grade book. If you would like to regrade"
                    "for-credit flows, use the corresponding functionality in "
                    "the grade book."
                ),
                "</p>",
            ),
            "form_description": _("Regrade not-for-credit Flow Sessions"),
        },
    )
Example #22
0
def edit_course(pctx):
    if not pctx.has_permission(pperm.edit_course):
        raise PermissionDenied()

    request = pctx.request

    if request.method == 'POST':
        form = EditCourseForm(request.POST, instance=pctx.course)
        if form.is_valid():
            form.save()

    else:
        form = EditCourseForm(instance=pctx.course)

    return render_course_page(pctx, "course/generic-course-form.html", {
        "form_description": _("Edit Course"),
        "form": form
        })
Example #23
0
def view_markup_sandbox(pctx):
    if not pctx.has_permission(pperm.use_markup_sandbox):
        raise PermissionDenied()

    request = pctx.request
    preview_text = ""

    def make_form(data=None):
        help_text = (ugettext("Enter <a href=\"http://documen.tician.de/"
                "relate/content.html#relate-markup\">"
                "RELATE markup</a>."))
        return SandboxForm(
                None, "markdown", request.user.editor_mode,
                help_text,
                data)

    if request.method == "POST" and "preview" in request.POST:
        form = make_form(request.POST)

        if form.is_valid():
            from course.content import markup_to_html
            try:
                preview_text = markup_to_html(
                        pctx.course, pctx.repo, pctx.course_commit_sha,
                        form.cleaned_data["content"])
            except:
                import sys
                tp, e, _ = sys.exc_info()

                messages.add_message(pctx.request, messages.ERROR,
                        ugettext("Markup failed to render")
                        + ": "
                        + "%(err_type)s: %(err_str)s" % {
                            "err_type": tp.__name__, "err_str": e})

        form = make_form(request.POST)

    else:
        form = make_form()

    return render_course_page(pctx, "course/sandbox-markup.html", {
        "form": form,
        "preview_text": preview_text,
    })
Example #24
0
def view_markup_sandbox(pctx):
    request = pctx.request
    preview_text = ""

    def make_form(data=None):
        help_text = ("Enter <a href=\"http://documen.tician.de/"
                "courseflow/content.html#courseflow-markup\">"
                "CourseFlow markup</a>.")
        return SandboxForm(
                None, "markdown", vim_mode,
                help_text,
                data)

    vim_mode = pctx.request.session.get(CF_SANDBOX_VIM_MODE, False)

    if request.method == "POST":
        form = make_form(request.POST)

        if form.is_valid():
            pctx.request.session[CF_SANDBOX_VIM_MODE] = \
                    vim_mode = form.cleaned_data["vim_mode"]

            from course.content import markup_to_html
            try:
                preview_text = markup_to_html(
                        pctx.course, pctx.repo, pctx.course_commit_sha,
                        form.cleaned_data["content"])
            except:
                import sys
                tp, e, _ = sys.exc_info()

                messages.add_message(pctx.request, messages.ERROR,
                        "Markup failed to render: "
                        "%s: %s" % (tp.__name__, e))

        form = make_form(request.POST)

    else:
        form = make_form()

    return render_course_page(pctx, "course/sandbox-markup.html", {
        "form": form,
        "preview_text": preview_text,
    })
Example #25
0
def renumber_events(pctx):
    if pctx.role != participation_role.instructor:
        raise PermissionDenied("only instructors may do that")

    request = pctx.request

    if request.method == "POST":
        form = RenumberEventsForm(request.POST, request.FILES)
        if form.is_valid():
            labels = list(Event.objects
                    .filter(course=pctx.course, kind=form.cleaned_data["kind"])
                    .order_by('time'))

            if labels:
                queryset = (Event.objects
                    .filter(course=pctx.course, kind=form.cleaned_data["kind"]))

                queryset.delete()

                ordinal = form.cleaned_data["starting_ordinal"]
                for label in labels:
                    new_label = Event()
                    new_label.course = pctx.course
                    new_label.kind = form.cleaned_data["kind"]
                    new_label.ordinal = ordinal
                    new_label.time = label.time
                    new_label.save()

                    ordinal += 1

                messages.add_message(request, messages.SUCCESS,
                        "Events renumbered.")
            else:
                messages.add_message(request, messages.ERROR,
                        "No events found.")

    else:
        form = RenumberEventsForm()

    return render_course_page(pctx, "course/generic-course-form.html", {
        "form": form,
        "form_description": "Renumber events",
    })
Example #26
0
def grant_exception_stage_2(pctx, participation_id, flow_id):
    if pctx.role not in [
            participation_role.instructor,
            participation_role.teaching_assistant]:
        raise PermissionDenied("must be instructor or TA to grant exceptions")

    participation = get_object_or_404(Participation, id=participation_id)

    from course.content import get_flow_desc
    try:
        flow_desc = get_flow_desc(pctx.repo, pctx.course, flow_id,
                pctx.course_commit_sha)
    except ObjectDoesNotExist:
        raise http.Http404()

    if not hasattr(flow_desc, "access_rules"):
        messages.add_message(pctx.request, messages.ERROR,
                "Flow '%s' does not declare access rules."
                % flow_id)
        return redirect("course.views.grant_exception",
                pctx.course.identifier)

    base_ruleset_choices = [rule.id for rule in flow_desc.access_rules]

    request = pctx.request
    if request.method == "POST":
        form = ExceptionStage2Form(base_ruleset_choices, request.POST)

        if form.is_valid():
            return redirect(
                    "course.views.grant_exception_stage_3",
                    pctx.course.identifier,
                    participation.id,
                    flow_id,
                    form.cleaned_data["base_ruleset"])

    else:
        form = ExceptionStage2Form(base_ruleset_choices)

    return render_course_page(pctx, "course/generic-course-form.html", {
        "form": form,
        "form_description": "Grant Exception",
    })
Example #27
0
def view_gradebook(pctx):
    if pctx.role not in [
            participation_role.instructor,
            participation_role.teaching_assistant]:
        raise PermissionDenied(_("must be instructor or TA to view grades"))

    participations, grading_opps, grade_table = get_grade_table(pctx.course)

    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.html", {
        "grade_table": grade_table,
        "grading_opportunities": grading_opps,
        "participations": participations,
        "grade_state_change_types": grade_state_change_types,
        })
Example #28
0
def flow_analytics(pctx, flow_id):
    if pctx.role not in [
            participation_role.teaching_assistant,
            participation_role.instructor,
            participation_role.observer,
            ]:
        raise PermissionDenied(_("must be at least TA to view analytics"))

    restrict_to_first_attempt = int(
            bool(pctx.request.GET.get("restrict_to_first_attempt") == "1"))

    return render_course_page(pctx, "course/analytics-flow.html", {
        "flow_identifier": flow_id,
        "grade_histogram": make_grade_histogram(pctx, flow_id),
        "page_answer_stats_list": make_page_answer_stats_list(pctx, flow_id,
            restrict_to_first_attempt),
        "time_histogram": make_time_histogram(pctx, flow_id),
        "participant_count": count_participants(pctx, flow_id),
        "restrict_to_first_attempt": restrict_to_first_attempt,
        })
Example #29
0
def renumber_events(pctx):
    if not pctx.has_permission(pperm.edit_events):
        raise PermissionDenied(_("may not edit events"))

    request = pctx.request

    if request.method == "POST":
        form = RenumberEventsForm(request.POST, request.FILES)
        if form.is_valid():
            events = list(Event.objects.filter(course=pctx.course, kind=form.cleaned_data["kind"]).order_by("time"))

            if events:
                queryset = Event.objects.filter(course=pctx.course, kind=form.cleaned_data["kind"])

                queryset.delete()

                ordinal = form.cleaned_data["starting_ordinal"]
                for event in events:
                    new_event = Event()
                    new_event.course = pctx.course
                    new_event.kind = form.cleaned_data["kind"]
                    new_event.ordinal = ordinal
                    new_event.time = event.time
                    new_event.end_time = event.end_time
                    new_event.all_day = event.all_day
                    new_event.shown_in_calendar = event.shown_in_calendar
                    new_event.save()

                    ordinal += 1

                messages.add_message(request, messages.SUCCESS, _("Events renumbered."))
            else:
                messages.add_message(request, messages.ERROR, _("No events found."))

    else:
        form = RenumberEventsForm()

    return render_course_page(
        pctx, "course/generic-course-form.html", {"form": form, "form_description": _("Renumber events")}
    )
Example #30
0
def check_events(pctx):
    if pctx.role != participation_role.instructor:
        raise PermissionDenied("only instructors may do that")

    invalid_datespecs = {}

    from course.content import InvalidDatespec, parse_date_spec

    def datespec_callback(location, datespec):
        try:
            parse_date_spec(pctx.course, datespec, return_now_on_error=False)
        except InvalidDatespec as e:
            invalid_datespecs.setdefault(e.datespec, []).append(location)

    from course.validation import validate_course_content
    validate_course_content(
            pctx.repo, pctx.course.course_file, pctx.course.events_file,
            pctx.course_commit_sha, datespec_callback=datespec_callback)

    return render_course_page(pctx, "course/invalid-datespec-list.html", {
        "invalid_datespecs": sorted(invalid_datespecs.iteritems()),
        })
Example #31
0
def static_page(pctx, page_path):
    # type: (CoursePageContext, Text) -> http.HttpResponse
    from course.content import get_staticpage_desc, get_processed_page_chunks
    try:
        page_desc = get_staticpage_desc(pctx.repo, pctx.course,
                                        pctx.course_commit_sha,
                                        "staticpages/" + page_path + ".yml")
    except ObjectDoesNotExist:
        raise http.Http404()

    chunks = get_processed_page_chunks(
        pctx.course,
        pctx.repo,
        pctx.course_commit_sha,
        page_desc,
        pctx.role_identifiers(),
        get_now_or_fake_time(pctx.request),
        facilities=pctx.request.relate_facilities)

    return render_course_page(pctx, "course/static-page.html", {
        "chunks": chunks,
        "show_enroll_button": False
    })
Example #32
0
def view_gradebook(pctx):
    if pctx.role not in [
            participation_role.instructor,
            participation_role.teaching_assistant
    ]:
        raise PermissionDenied(_("must be instructor or TA to view grades"))

    participations, grading_opps, grade_table = get_grade_table(pctx.course)

    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.html", {
            "grade_table": grade_table,
            "grading_opportunities": grading_opps,
            "participations": participations,
            "grade_state_change_types": grade_state_change_types,
        })
Example #33
0
def test_flow(pctx):
    if not pctx.has_permission(pperm.test_flow):
        raise PermissionDenied()

    from course.content import list_flow_ids
    flow_ids = list_flow_ids(pctx.repo, pctx.course_commit_sha)

    request = pctx.request
    if request.method == "POST":
        form = FlowTestForm(flow_ids, request.POST, request.FILES)
        if "test" not in request.POST:
            raise SuspiciousOperation(_("invalid operation"))

        if form.is_valid():
            return redirect("relate-view_start_flow", pctx.course.identifier,
                            form.cleaned_data["flow_id"])

    else:
        form = FlowTestForm(flow_ids)

    return render_course_page(pctx, "course/generic-course-form.html", {
        "form": form,
        "form_description": _("Test Flow"),
    })
Example #34
0
def grant_exception(pctx):
    if not pctx.has_permission(pperm.grant_exception):
        raise PermissionDenied(_("may not grant exceptions"))

    from course.content import list_flow_ids
    flow_ids = list_flow_ids(pctx.repo, pctx.course_commit_sha)

    request = pctx.request
    if request.method == "POST":
        form = ExceptionStage1Form(pctx.course, flow_ids, request.POST)

        if form.is_valid():
            return redirect("relate-grant_exception_stage_2",
                            pctx.course.identifier,
                            form.cleaned_data["participation"].id,
                            form.cleaned_data["flow_id"])

    else:
        form = ExceptionStage1Form(pctx.course, flow_ids)

    return render_course_page(pctx, "course/generic-course-form.html", {
        "form": form,
        "form_description": _("Grant Exception"),
    })
Example #35
0
def update_course(pctx):
    if not (pctx.has_permission(pperm.update_content)
            or pctx.has_permission(pperm.preview_content)):
        raise PermissionDenied()

    course = pctx.course
    request = pctx.request
    content_repo = pctx.repo

    from course.content import SubdirRepoWrapper
    if isinstance(content_repo, SubdirRepoWrapper):
        repo = content_repo.repo
    else:
        repo = content_repo

    participation = pctx.participation

    previewing = bool(participation is not None
                      and participation.preview_git_commit_sha)

    may_update = pctx.has_permission(pperm.update_content)

    response_form = None
    form = None
    if request.method == "POST":
        form = GitUpdateForm(may_update, previewing, repo, request.POST,
                             request.FILES)

        command = None
        for cmd in ALLOWED_COURSE_REVISIOIN_COMMANDS:
            if cmd in form.data:
                command = cmd
                break

        if command is None:
            raise SuspiciousOperation(_("invalid command"))

        if form.is_valid():
            new_sha = form.cleaned_data["new_sha"].encode()

            try:
                run_course_update_command(
                    request,
                    repo,
                    content_repo,
                    pctx,
                    command,
                    new_sha,
                    may_update,
                    prevent_discarding_revisions=form.
                    cleaned_data["prevent_discarding_revisions"])
            except Exception as e:
                import traceback
                traceback.print_exc()

                messages.add_message(
                    pctx.request, messages.ERROR,
                    string_concat(
                        pgettext("Starting of Error message", "Error"),
                        ": %(err_type)s %(err_str)s") % {
                            "err_type": type(e).__name__,
                            "err_str": str(e)
                        })
        else:
            response_form = form

    if response_form is None:
        previewing = bool(participation is not None
                          and participation.preview_git_commit_sha)

        form = GitUpdateForm(
            may_update, previewing, repo, {
                "new_sha": repo.head().decode(),
                "prevent_discarding_revisions": True,
            })

    from django.template.loader import render_to_string
    form_text = render_to_string(
        "course/git-sha-table.html", {
            "participation":
            participation,
            "is_previewing":
            previewing,
            "course":
            course,
            "repo":
            repo,
            "current_git_head":
            repo.head().decode(),
            "git_url":
            request.build_absolute_uri(
                reverse("relate-git_endpoint", args=(course.identifier, ""))),
            "token_url":
            reverse("relate-manage_authentication_tokens",
                    args=(course.identifier, )),
        })

    assert form is not None

    return render_course_page(
        pctx, "course/generic-course-form.html", {
            "form": form,
            "form_text": form_text,
            "form_description": gettext("Update Course Revision"),
        })
Example #36
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':
            '%}',
        })
Example #37
0
def query_participations(pctx):
    if (not pctx.has_permission(pperm.query_participation)
            or pctx.has_permission(pperm.view_participant_masked_profile)):
        raise PermissionDenied(_("may not query participations"))

    request = pctx.request

    result = None

    if request.method == "POST":
        form = ParticipationQueryForm(request.POST)
        if form.is_valid():
            parsed_query = None
            try:
                for lineno, q in enumerate(
                        form.cleaned_data["queries"].split("\n")):
                    q = q.strip()

                    if not q:
                        continue

                    parsed_subquery = parse_query(pctx.course, q)
                    if parsed_query is None:
                        parsed_query = parsed_subquery
                    else:
                        parsed_query = parsed_query | parsed_subquery

            except Exception as e:
                messages.add_message(
                    request, messages.ERROR,
                    _("Error in line %(lineno)d: %(error_type)s: %(error)s") %
                    {
                        "lineno": lineno + 1,
                        "error_type": type(e).__name__,
                        "error": str(e),
                    })

                parsed_query = None

            if parsed_query is not None:
                result = list(
                    Participation.objects.filter(course=pctx.course).filter(
                        parsed_query).order_by("user__username").
                    select_related("user").prefetch_related("tags"))

                if "apply" in request.POST:

                    if form.cleaned_data["op"] == "apply_tag":
                        ptag, __ = ParticipationTag.objects.get_or_create(
                            course=pctx.course, name=form.cleaned_data["tag"])
                        for p in result:
                            p.tags.add(ptag)
                    elif form.cleaned_data["op"] == "remove_tag":
                        ptag, __ = ParticipationTag.objects.get_or_create(
                            course=pctx.course, name=form.cleaned_data["tag"])
                        for p in result:
                            p.tags.remove(ptag)
                    elif form.cleaned_data["op"] == "drop":
                        for p in result:
                            p.status = participation_status.dropped
                            p.save()
                    else:
                        raise RuntimeError("unexpected operation")

                    messages.add_message(
                        request, messages.INFO,
                        "Operation successful on %d participations." %
                        len(result))

    else:
        form = ParticipationQueryForm()

    return render_course_page(pctx, "course/query-participations.html", {
        "form": form,
        "result": result,
    })
Example #38
0
def send_instant_message(pctx):
    if not pctx.has_permission(pperm.send_instant_message):
        raise PermissionDenied(_("may not batch-download submissions"))

    request = pctx.request
    course = pctx.course

    if not course.course_xmpp_id:
        messages.add_message(
            request, messages.ERROR,
            _("Instant messaging is not enabled for this course."))

        return redirect("relate-course_page", pctx.course_identifier)

    xmpp = get_xmpp_connection(pctx.course)
    if xmpp.is_recipient_online():
        form_text = _("Recipient is <span class='label label-success'>"
                      "Online</span>.")
    else:
        form_text = _("Recipient is <span class='label label-danger'>"
                      "Offline</span>.")
    form_text = "<div class='well'>%s</div>" % form_text

    if request.method == "POST":
        form = InstantMessageForm(request.POST, request.FILES)
        if form.is_valid():
            msg = InstantMessage()
            msg.participation = pctx.participation
            msg.text = form.cleaned_data["message"]
            msg.save()

            try:
                if not course.recipient_xmpp_id:
                    raise RuntimeError(_("no recipient XMPP ID"))

                if not course.course_xmpp_password:
                    raise RuntimeError(_("no XMPP password"))

                xmpp.send_message(mto=course.recipient_xmpp_id,
                                  mbody=form.cleaned_data["message"],
                                  mtype='chat')

            except Exception:
                from traceback import print_exc
                print_exc()

                messages.add_message(
                    request, messages.ERROR,
                    _("An error occurred while sending the message. "
                      "Sorry."))
            else:
                messages.add_message(request, messages.SUCCESS,
                                     _("Message sent."))
                form = InstantMessageForm()

    else:
        form = InstantMessageForm()

    return render_course_page(
        pctx, "course/generic-course-form.html", {
            "form": form,
            "form_text": form_text,
            "form_description": _("Send instant message"),
        })
Example #39
0
def show_grader_statistics(pctx, flow_id):
    if not pctx.has_permission(pperm.view_grader_stats):
        raise PermissionDenied(_("may not view grader stats"))

    grades = (
        FlowPageVisitGrade.objects.filter(
            visit__flow_session__course=pctx.course,
            visit__flow_session__flow_id=flow_id,

            # There are just way too many autograder grades, which makes this
            # report super slow.
            grader__isnull=False).order_by(
                "visit__id",
                "grade_time").select_related("visit").select_related(
                    "grader").select_related("visit__page_data"))

    graders = set()

    # tuples: (page_ordinal, id)
    pages = set()

    counts = {}
    grader_counts = {}
    page_counts = {}

    def commit_grade_info(grade):
        grader = grade.grader
        page = (grade.visit.page_data.page_ordinal,
                grade.visit.page_data.group_id + "/" +
                grade.visit.page_data.page_id)

        graders.add(grader)
        pages.add(page)

        key = (page, grade.grader)
        counts[key] = counts.get(key, 0) + 1

        grader_counts[grader] = grader_counts.get(grader, 0) + 1
        page_counts[page] = page_counts.get(page, 0) + 1

    last_grade = None

    for grade in grades.iterator():
        if last_grade is not None and last_grade.visit != grade.visit:
            commit_grade_info(last_grade)

        last_grade = grade

    if last_grade is not None:
        commit_grade_info(last_grade)

    graders = sorted(graders,
                     key=lambda grader: grader.last_name
                     if grader is not None else None)
    pages = sorted(pages)

    stats_table = [[counts.get((page, grader), 0) for grader in graders]
                   for page in pages]
    page_counts = [page_counts.get(page, 0) for page in pages]
    grader_counts = [grader_counts.get(grader, 0) for grader in graders]

    return render_course_page(
        pctx, "course/grading-statistics.html", {
            "flow_id": flow_id,
            "pages": pages,
            "graders": graders,
            "pages_stats_counts": list(zip(pages, stats_table, page_counts)),
            "grader_counts": grader_counts,
        })
Example #40
0
def page_analytics(pctx, flow_id, group_id, page_id):
    if pctx.role not in [
            participation_role.teaching_assistant,
            participation_role.instructor,
            participation_role.observer,
            ]:
        raise PermissionDenied(_("must be at least TA to view analytics"))

    flow_desc = get_flow_desc(pctx.repo, pctx.course, flow_id,
            pctx.course_commit_sha)

    restrict_to_first_attempt = int(
            bool(pctx.request.GET.get("restrict_to_first_attempt") == "1"))

    is_multiple_submit = is_flow_multiple_submit(flow_desc)

    page_cache = PageInstanceCache(pctx.repo, pctx.course, flow_id)

    visits = (FlowPageVisit.objects
            .filter(
                flow_session__course=pctx.course,
                flow_session__flow_id=flow_id,
                page_data__group_id=group_id,
                page_data__page_id=page_id,
                is_submitted_answer=True,
                ))

    if connection.features.can_distinct_on_fields:
        if restrict_to_first_attempt:
            visits = (visits
                    .distinct("flow_session__participation__id")
                    .order_by("flow_session__participation__id", "visit_time"))
        elif is_multiple_submit:
            visits = (visits
                    .distinct("page_data__id")
                    .order_by("page_data__id", "-visit_time"))

    visits = (visits
            .select_related("flow_session")
            .select_related("page_data"))

    normalized_answer_and_correctness_to_count = {}

    title = None
    body = None
    total_count = 0
    graded_count = 0

    for visit in visits:
        page = page_cache.get_page(group_id, page_id, pctx.course_commit_sha)

        from course.page import PageContext
        grading_page_context = PageContext(
                course=pctx.course,
                repo=pctx.repo,
                commit_sha=pctx.course_commit_sha,
                flow_session=visit.flow_session)

        title = page.title(grading_page_context, visit.page_data.data)
        body = page.body(grading_page_context, visit.page_data.data)
        normalized_answer = page.normalized_answer(
                grading_page_context, visit.page_data.data, visit.answer)

        answer_feedback = visit.get_most_recent_feedback()

        if answer_feedback is not None:
            key = (normalized_answer, answer_feedback.correctness)
            normalized_answer_and_correctness_to_count[key] = \
                    normalized_answer_and_correctness_to_count.get(key, 0) + 1
            graded_count += 1
        else:
            key = (normalized_answer, None)
            normalized_answer_and_correctness_to_count[key] = \
                    normalized_answer_and_correctness_to_count.get(key, 0) + 1

        total_count += 1

    answer_stats = []
    for (normalized_answer, correctness), count in \
            normalized_answer_and_correctness_to_count.iteritems():
        answer_stats.append(
                AnswerStats(
                    normalized_answer=normalized_answer,
                    correctness=correctness,
                    count=count,
                    percentage=safe_div(100 * count, total_count)))

    answer_stats = sorted(
            answer_stats,
            key=lambda astats: astats.percentage,
            reverse=True)

    return render_course_page(pctx, "course/analytics-page.html", {
        "flow_identifier": flow_id,
        "group_id": group_id,
        "page_id": page_id,
        "title": title,
        "body": body,
        "answer_stats_list": answer_stats,
        "restrict_to_first_attempt": restrict_to_first_attempt,
        })
Example #41
0
def send_instant_message(pctx):
    if pctx.role not in [
            participation_role.student, participation_role.teaching_assistant,
            participation_role.instructor
    ]:
        raise PermissionDenied("only enrolled folks may do that")

    request = pctx.request
    course = pctx.course

    if not course.course_xmpp_id:
        messages.add_message(
            request, messages.ERROR,
            "Instant messaging is not enabled for this course.")

        return redirect("course.views.course_page", pctx.course_identifier)

    xmpp = get_xmpp_connection(pctx.course)
    if xmpp.is_recipient_online():
        form_text = "Recipient is <span class=\"label label-success\">Online</span>."
    else:
        form_text = "Recipient is <span class=\"label label-danger\">Offline</span>."
    form_text = "<div class=\"well\">%s</div>" % form_text

    if request.method == "POST":
        form = InstantMessageForm(request.POST, request.FILES)
        if form.is_valid():
            msg = InstantMessage()
            msg.participation = pctx.participation
            msg.text = form.cleaned_data["message"]
            msg.save()

            try:
                if not course.recipient_xmpp_id:
                    raise RuntimeError("no recipient XMPP ID")

                if not course.course_xmpp_password:
                    raise RuntimeError("no XMPP password")

                xmpp.send_message(mto=course.recipient_xmpp_id,
                                  mbody=form.cleaned_data["message"],
                                  mtype='chat')

            except Exception:
                from traceback import print_exc
                print_exc()

                messages.add_message(
                    request, messages.ERROR,
                    "An error occurred while sending the message. Sorry.")
            else:
                messages.add_message(request, messages.SUCCESS,
                                     "Message sent.")
                form = InstantMessageForm()

    else:
        form = InstantMessageForm()

    return render_course_page(
        pctx, "course/generic-course-form.html", {
            "form": form,
            "form_text": form_text,
            "form_description": "Send instant message",
        })
Example #42
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,
                            "Error: %s %s" % (type(e).__name__, 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,
        })
Example #43
0
def renumber_events(pctx):
    if not pctx.has_permission(pperm.edit_events):
        raise PermissionDenied(_("may not edit events"))

    request = pctx.request

    message = None
    message_level = None

    if request.method == "POST":
        form = RenumberEventsForm(
            pctx.course.identifier, request.POST, request.FILES)
        if form.is_valid():
            kind = form.cleaned_data["kind"]
            order_field = "time"
            if form.cleaned_data["preserve_ordinal_order"]:
                order_field = "ordinal"
            events = list(
                Event.objects.filter(
                    course=pctx.course, kind=kind,

                    # there might be event with the same kind but no ordinal,
                    # we don't renumber that
                    ordinal__isnull=False)
                .order_by(order_field))

            assert events
            queryset = (Event.objects.filter(
                course=pctx.course, kind=kind,

                # there might be event with the same kind but no ordinal,
                # we don't renumber that
                ordinal__isnull=False))

            queryset.delete()

            ordinal = form.cleaned_data["starting_ordinal"]
            for event in events:
                new_event = Event()
                new_event.course = pctx.course
                new_event.kind = kind
                new_event.ordinal = ordinal
                new_event.time = event.time
                new_event.end_time = event.end_time
                new_event.all_day = event.all_day
                new_event.shown_in_calendar = event.shown_in_calendar
                new_event.save()

                ordinal += 1

            message = _("Events renumbered.")
            message_level = messages.SUCCESS

    else:
        form = RenumberEventsForm(pctx.course.identifier)

    if messages and message_level:
        messages.add_message(request, message_level, message)
    return render_course_page(pctx, "course/generic-course-form.html", {
        "form": form,
        "form_description": _("Renumber events"),
    })
Example #44
0
def create_recurring_events(pctx):
    if not pctx.has_permission(pperm.edit_events):
        raise PermissionDenied(_("may not edit events"))

    request = pctx.request
    message = None
    message_level = None

    if request.method == "POST":
        form = RecurringEventForm(
            pctx.course.identifier, request.POST, request.FILES)
        if form.is_valid():
            if form.cleaned_data["starting_ordinal"] is not None:
                starting_ordinal = form.cleaned_data["starting_ordinal"]
                starting_ordinal_specified = True
            else:
                starting_ordinal = 1
                starting_ordinal_specified = False

            while True:
                try:
                    _create_recurring_events_backend(
                            course=pctx.course,
                            time=form.cleaned_data["time"],
                            kind=form.cleaned_data["kind"],
                            starting_ordinal=starting_ordinal,
                            interval=form.cleaned_data["interval"],
                            count=form.cleaned_data["count"],
                            duration_in_minutes=(
                                form.cleaned_data["duration_in_minutes"]),
                            all_day=form.cleaned_data["all_day"],
                            shown_in_calendar=(
                                form.cleaned_data["shown_in_calendar"])
                            )
                    message = _("Events created.")
                    message_level = messages.SUCCESS
                except EventAlreadyExists as e:
                    if starting_ordinal_specified:
                        message = (
                                string_concat(
                                    "%(err_type)s: %(err_str)s. ",
                                    _("No events created."))
                                % {
                                    "err_type": type(e).__name__,
                                    "err_str": str(e)})
                        message_level = messages.ERROR
                    else:
                        starting_ordinal += 10
                        continue

                except Exception as e:
                    if isinstance(e, ValidationError):
                        for field, error in e.error_dict.items():
                            try:
                                form.add_error(field, error)
                            except ValueError:
                                # This happens when ValidationError were
                                # raised for fields which don't exist in
                                # RecurringEventForm
                                form.add_error(
                                    "__all__", "'%s': %s" % (field, error))
                    else:
                        message = (
                                string_concat(
                                    "%(err_type)s: %(err_str)s. ",
                                    _("No events created."))
                                % {
                                    "err_type": type(e).__name__,
                                    "err_str": str(e)})
                        message_level = messages.ERROR
                break
    else:
        form = RecurringEventForm(pctx.course.identifier)

    if message and message_level:
        messages.add_message(request, message_level, message)
    return render_course_page(pctx, "course/generic-course-form.html", {
        "form": form,
        "form_description": _("Create recurring events"),
    })
Example #45
0
def batch_issue_exam_tickets(pctx):
    if not pctx.has_permission(pperm.batch_issue_exam_ticket):
        raise PermissionDenied(_("may not batch-issue tickets"))

    form_text = ""

    request = pctx.request
    if request.method == "POST":
        form = BatchIssueTicketsForm(pctx.course, request.user.editor_mode,
                                     request.POST)

        if form.is_valid():
            exam = form.cleaned_data["exam"]

            from jinja2 import TemplateSyntaxError
            from course.content import markup_to_html
            try:
                with transaction.atomic():
                    if form.cleaned_data["revoke_prior"]:
                        ExamTicket.objects.filter(
                            exam=exam,
                            state__in=(
                                exam_ticket_states.valid,
                                exam_ticket_states.used,
                            )).update(state=exam_ticket_states.revoked)

                    tickets = []
                    for participation in (Participation.objects.filter(
                            course=pctx.course,
                            status=participation_status.active).order_by(
                                "user__last_name")):
                        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()

                        tickets.append(ticket)

                    checkin_uri = pctx.request.build_absolute_uri(
                        reverse("relate-check_in_for_exam"))
                    form_text = markup_to_html(pctx.course,
                                               pctx.repo,
                                               pctx.course_commit_sha,
                                               form.cleaned_data["format"],
                                               jinja_env={
                                                   "tickets": tickets,
                                                   "checkin_uri": checkin_uri,
                                               })
            except TemplateSyntaxError as e:
                messages.add_message(
                    request, messages.ERROR,
                    string_concat(_("Template rendering failed"),
                                  ": line %(lineno)d: %(err_str)s") % {
                                      "lineno": e.lineno,
                                      "err_str": e.message.decode("utf-8")
                                  })
            except Exception as e:
                messages.add_message(
                    request, messages.ERROR,
                    string_concat(_("Template rendering failed"),
                                  ": %(err_type)s: %(err_str)s") % {
                                      "err_type": type(e).__name__,
                                      "err_str": str(e)
                                  })
            else:
                messages.add_message(request, messages.SUCCESS,
                                     _("%d tickets issued.") % len(tickets))

    else:
        form = BatchIssueTicketsForm(pctx.course, request.user.editor_mode)

    return render_course_page(
        pctx, "course/batch-exam-tickets-form.html", {
            "form": form,
            "form_text": form_text,
            "form_description": gettext("Batch-Issue Exam Tickets")
        })
Example #46
0
def view_page_sandbox(pctx):
    from relate.utils import dict_to_struct
    import yaml

    PAGE_SESSION_KEY = "cf_validated_sandbox_page:" + pctx.course.identifier
    ANSWER_DATA_SESSION_KEY = "cf_page_sandbox_answer_data:" + pctx.course.identifier

    request = pctx.request
    page_source = pctx.request.session.get(PAGE_SESSION_KEY)

    page_errors = None

    is_preview_post = (request.method == "POST" and "preview" in request.POST)

    def make_form(data=None):
        return SandboxForm(
                page_source, "yaml", vim_mode,
                "Enter YAML markup for a flow page.",
                data)

    vim_mode = pctx.request.session.get(CF_SANDBOX_VIM_MODE, False)

    if is_preview_post:
        edit_form = make_form(pctx.request.POST)

        if edit_form.is_valid():
            pctx.request.session[CF_SANDBOX_VIM_MODE] = \
                    vim_mode = edit_form.cleaned_data["vim_mode"]

            try:
                new_page_source = edit_form.cleaned_data["content"]
                page_desc = dict_to_struct(yaml.load(new_page_source))

                from course.validation import validate_flow_page, ValidationContext
                vctx = ValidationContext(
                        repo=pctx.repo,
                        commit_sha=pctx.course_commit_sha)

                validate_flow_page(vctx, "sandbox", page_desc)

            except:
                import sys
                tp, e, _ = sys.exc_info()

                page_errors = (
                        "Page failed to load/validate: "
                        "%s: %s" % (tp.__name__, e))

            else:
                # Yay, it did validate.
                request.session[PAGE_SESSION_KEY] = page_source = new_page_source

            del new_page_source

        edit_form = make_form(pctx.request.POST)

    else:
        edit_form = make_form()

    have_valid_page = page_source is not None
    if have_valid_page:
        page_desc = dict_to_struct(yaml.load(page_source))

        from course.content import instantiate_flow_page
        page = instantiate_flow_page("sandbox", pctx.repo, page_desc,
                pctx.course_commit_sha)

        page_data = page.make_page_data()

        from course.page import PageContext
        page_context = PageContext(
                course=pctx.course,
                repo=pctx.repo,
                commit_sha=pctx.course_commit_sha,
                flow_session=None)

        title = page.title(page_context, page_data)
        body = page.body(page_context, page_data)

        # {{{ try to recover answer_data

        answer_data = None

        stored_answer_data_tuple = \
                pctx.request.session.get(ANSWER_DATA_SESSION_KEY)

        # Session storage uses JSON and may turn tuples into lists.
        if (isinstance(stored_answer_data_tuple, (list, tuple))
                and len(stored_answer_data_tuple) == 2):
            stored_answer_data_page_id, stored_answer_data = \
                    stored_answer_data_tuple

            if stored_answer_data_page_id == page_desc.id:
                answer_data = stored_answer_data

        # }}}

        feedback = None
        page_form_html = None

        if page.expects_answer():
            if request.method == "POST" and not is_preview_post:
                page_form = page.post_form(page_context, page_data,
                        request.POST, request.FILES)

                if page_form.is_valid():

                    answer_data = page.answer_data(page_context, page_data,
                            page_form, request.FILES)

                    feedback = page.grade(page_context, page_data, answer_data,
                            grade_data=None)

                    pctx.request.session[ANSWER_DATA_SESSION_KEY] = (
                            page_desc.id, answer_data)

            else:
                page_form = page.make_form(page_context, page_data,
                        answer_data, answer_is_final=False)

            if page_form is not None:
                page_form.helper.add_input(
                        Submit("submit", "Submit answer", accesskey="g",
                            css_class="col-lg-offset-2"))
                page_form_html = page.form_to_html(
                        pctx.request, page_context, page_form, answer_data)

        correct_answer = page.correct_answer(
                page_context, page_data, answer_data,
                grade_data=None)

        return render_course_page(pctx, "course/sandbox-page.html", {
            "edit_form": edit_form,
            "page_errors": page_errors,
            "form": edit_form,  # to placate form.media
            "have_valid_page": True,
            "title": title,
            "body": body,
            "page_form_html": page_form_html,
            "feedback": feedback,
            "correct_answer": correct_answer,
        })

    else:

        return render_course_page(pctx, "course/sandbox-page.html", {
            "edit_form": edit_form,
            "form": edit_form,  # to placate form.media
            "have_valid_page": False,
            "page_errors": page_errors,
        })
Example #47
0
def grant_exception_stage_2(pctx, participation_id, flow_id):
    # type: (CoursePageContext, Text, Text) -> http.HttpResponse

    if not pctx.has_permission(pperm.grant_exception):
        raise PermissionDenied(_("may not grant exceptions"))

    # {{{ get flow data

    participation = get_object_or_404(Participation, id=participation_id)

    form_text = (string_concat(
        "<div class='well'>",
        ugettext("Granting exception to '%(participation)s' for "
                 "'%(flow_id)s'."), "</div>") % {
                     'participation': participation,
                     'flow_id': flow_id
                 })

    from course.content import get_flow_desc
    try:
        flow_desc = get_flow_desc(pctx.repo, pctx.course, flow_id,
                                  pctx.course_commit_sha)
    except ObjectDoesNotExist:
        raise http.Http404()

    now_datetime = get_now_or_fake_time(pctx.request)

    if hasattr(flow_desc, "rules"):
        access_rules_tags = getattr(flow_desc.rules, "tags", [])
    else:
        access_rules_tags = []

    NONE_SESSION_TAG = string_concat("<<<", _("NONE"), ">>>")  # noqa
    session_tag_choices = [(tag, tag) for tag in access_rules_tags] + [
        (NONE_SESSION_TAG, string_concat("(", _("NONE"), ")"))
    ]

    from course.utils import get_session_start_rule
    session_start_rule = get_session_start_rule(pctx.course, participation,
                                                flow_id, flow_desc,
                                                now_datetime)

    create_session_is_override = False
    if not session_start_rule.may_start_new_session:
        create_session_is_override = True
        form_text += (
            "<div class='alert alert-info'>%s</div>" % (string_concat(
                "<i class='fa fa-info-circle'></i> ",
                _("Creating a new session is (technically) not allowed "
                  "by course rules. Clicking 'Create Session' anyway will "
                  "override this rule."))))

    default_tag = session_start_rule.tag_session
    if default_tag is None:
        default_tag = NONE_SESSION_TAG

    # }}}

    def find_sessions():
        # type: () -> List[FlowSession]

        return list(
            FlowSession.objects.filter(participation=participation,
                                       flow_id=flow_id).order_by("start_time"))

    exception_form = None
    request = pctx.request
    if request.method == "POST":
        exception_form = ExceptionStage2Form(find_sessions(), request.POST)
        create_session_form = CreateSessionForm(session_tag_choices,
                                                default_tag,
                                                create_session_is_override,
                                                request.POST)

        if "create_session" in request.POST or "next" in request.POST:
            pass
        else:
            raise SuspiciousOperation(_("invalid command"))

        if create_session_form.is_valid() and "create_session" in request.POST:
            from course.flow import start_flow

            access_rules_tag = (
                create_session_form.
                cleaned_data["access_rules_tag_for_new_session"])
            if access_rules_tag == NONE_SESSION_TAG:
                access_rules_tag = None

            start_flow(pctx.repo,
                       pctx.course,
                       participation,
                       user=participation.user,
                       flow_id=flow_id,
                       flow_desc=flow_desc,
                       session_start_rule=session_start_rule,
                       now_datetime=now_datetime)

            exception_form = None

        elif exception_form.is_valid(
        ) and "next" in request.POST:  # type: ignore
            return redirect(
                "relate-grant_exception_stage_3", pctx.course.identifier,
                participation.id, flow_id,
                exception_form.cleaned_data["session"])  # type: ignore
    else:
        create_session_form = CreateSessionForm(session_tag_choices,
                                                default_tag,
                                                create_session_is_override)

    if exception_form is None:
        exception_form = ExceptionStage2Form(find_sessions())

    return render_course_page(
        pctx, "course/generic-course-form.html", {
            "forms": [exception_form, create_session_form],
            "form_text": form_text,
            "form_description": _("Grant Exception"),
        })
Example #48
0
def create_recurring_events(pctx):
    if not pctx.has_permission(pperm.edit_events):
        raise PermissionDenied(_("may not edit events"))

    request = pctx.request

    if request.method == "POST":
        form = RecurringEventForm(request.POST, request.FILES)
        if form.is_valid():
            if form.cleaned_data["starting_ordinal"] is not None:
                starting_ordinal = form.cleaned_data["starting_ordinal"]
                starting_ordinal_specified = True
            else:
                starting_ordinal = 1
                starting_ordinal_specified = False

            while True:
                try:
                    _create_recurring_events_backend(
                            course=pctx.course,
                            time=form.cleaned_data["time"],
                            kind=form.cleaned_data["kind"],
                            starting_ordinal=starting_ordinal,
                            interval=form.cleaned_data["interval"],
                            count=form.cleaned_data["count"],
                            duration_in_minutes=(
                                form.cleaned_data["duration_in_minutes"]),
                            all_day=form.cleaned_data["all_day"],
                            shown_in_calendar=(
                                form.cleaned_data["shown_in_calendar"])
                            )
                except EventAlreadyExists as e:
                    if starting_ordinal_specified:
                        messages.add_message(request, messages.ERROR,
                                string_concat(
                                    "%(err_type)s: %(err_str)s. ",
                                    _("No events created."))
                                % {
                                    "err_type": type(e).__name__,
                                    "err_str": str(e)})
                    else:
                        starting_ordinal += 10
                        continue

                except Exception as e:
                    messages.add_message(request, messages.ERROR,
                            string_concat(
                                "%(err_type)s: %(err_str)s. ",
                                _("No events created."))
                            % {
                                "err_type": type(e).__name__,
                                "err_str": str(e)})
                else:
                    messages.add_message(request, messages.SUCCESS,
                            _("Events created."))

                break
    else:
        form = RecurringEventForm()

    return render_course_page(pctx, "course/generic-course-form.html", {
        "form": form,
        "form_description": _("Create recurring events"),
    })
Example #49
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,
    })
Example #50
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 = []

    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"]

        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(),
    })
Example #51
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("^([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 (
                    reopen_session,
                    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 == "reopen":
                    reopen_session(session)
                    messages.add_message(pctx.request, messages.SUCCESS,
                            "Session reopened.")

                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,
                        "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")
            .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)

    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"))

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

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

        flow_desc = get_flow_desc(pctx.repo, pctx.course,
                opportunity.flow_id, pctx.course_commit_sha)

        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

    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_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
            ],

        "flow_grade_aggregation_strategy": flow_grade_aggregation_strategy_text,
        })
Example #52
0
def view_page_sandbox(pctx):
    # type: (CoursePageContext) -> http.HttpResponse

    if not pctx.has_permission(pperm.use_page_sandbox):
        raise PermissionDenied()

    from course.validation import ValidationError
    from relate.utils import dict_to_struct, Struct
    import yaml

    PAGE_SESSION_KEY = make_sandbox_session_key(  # noqa
        PAGE_SESSION_KEY_PREFIX, pctx.course.identifier)
    ANSWER_DATA_SESSION_KEY = make_sandbox_session_key(  # noqa
        ANSWER_DATA_SESSION_KEY_PREFIX, pctx.course.identifier)
    PAGE_DATA_SESSION_KEY = make_sandbox_session_key(  # noqa
        PAGE_DATA_SESSION_KEY_PREFIX, pctx.course.identifier)

    request = pctx.request
    page_source = pctx.request.session.get(PAGE_SESSION_KEY)

    page_errors = None
    page_warnings = None

    is_clear_post = (request.method == "POST" and "clear" in request.POST)
    is_clear_response_post = (request.method == "POST"
                              and "clear_response" in request.POST)
    is_preview_post = (request.method == "POST" and "preview" in request.POST)

    def make_form(data=None):
        # type: (Optional[Text]) -> PageSandboxForm
        return PageSandboxForm(page_source, "yaml", request.user.editor_mode,
                               ugettext("Enter YAML markup for a flow page."),
                               data)

    if is_preview_post:
        edit_form = make_form(pctx.request.POST)
        new_page_source = None

        if edit_form.is_valid():
            try:
                from pytools.py_codegen import remove_common_indentation
                new_page_source = remove_common_indentation(
                    edit_form.cleaned_data["content"],
                    require_leading_newline=False)
                from course.content import expand_yaml_macros
                new_page_source = expand_yaml_macros(pctx.repo,
                                                     pctx.course_commit_sha,
                                                     new_page_source)

                yaml_data = yaml.load(new_page_source)  # type: ignore
                page_desc = dict_to_struct(yaml_data)

                if not isinstance(page_desc, Struct):
                    raise ValidationError(
                        "Provided page source code is not "
                        "a dictionary. Do you need to remove a leading "
                        "list marker ('-') or some stray indentation?")

                from course.validation import validate_flow_page, ValidationContext
                vctx = ValidationContext(repo=pctx.repo,
                                         commit_sha=pctx.course_commit_sha)

                validate_flow_page(vctx, "sandbox", page_desc)

                page_warnings = vctx.warnings

            except Exception:
                import sys
                tp, e, _ = sys.exc_info()

                page_errors = (ugettext("Page failed to load/validate") +
                               ": " + "%(err_type)s: %(err_str)s" % {
                                   "err_type": tp.__name__,
                                   "err_str": e
                               })  # type: ignore

            else:
                # Yay, it did validate.
                request.session[
                    PAGE_SESSION_KEY] = page_source = new_page_source

            del new_page_source

        edit_form = make_form(pctx.request.POST)

    elif is_clear_post:
        page_source = None
        pctx.request.session[PAGE_DATA_SESSION_KEY] = None
        pctx.request.session[ANSWER_DATA_SESSION_KEY] = None
        del pctx.request.session[PAGE_DATA_SESSION_KEY]
        del pctx.request.session[ANSWER_DATA_SESSION_KEY]
        edit_form = make_form()

    elif is_clear_response_post:
        page_source = None
        pctx.request.session[PAGE_DATA_SESSION_KEY] = None
        pctx.request.session[ANSWER_DATA_SESSION_KEY] = None
        del pctx.request.session[PAGE_DATA_SESSION_KEY]
        del pctx.request.session[ANSWER_DATA_SESSION_KEY]
        edit_form = make_form(pctx.request.POST)

    else:
        edit_form = make_form()

    have_valid_page = page_source is not None
    if have_valid_page:
        yaml_data = yaml.load(page_source)  # type: ignore
        page_desc = cast(FlowPageDesc, dict_to_struct(yaml_data))

        from course.content import instantiate_flow_page
        try:
            page = instantiate_flow_page("sandbox", pctx.repo, page_desc,
                                         pctx.course_commit_sha)
        except Exception:
            import sys
            tp, e, _ = sys.exc_info()

            page_errors = (ugettext("Page failed to load/validate") + ": " +
                           "%(err_type)s: %(err_str)s" % {
                               "err_type": tp.__name__,
                               "err_str": e
                           })  # type: ignore
            have_valid_page = False

    if have_valid_page:
        page_desc = cast(FlowPageDesc, page_desc)

        # Try to recover page_data, answer_data
        page_data = get_sandbox_data_for_page(pctx, page_desc,
                                              PAGE_DATA_SESSION_KEY)

        answer_data = get_sandbox_data_for_page(pctx, page_desc,
                                                ANSWER_DATA_SESSION_KEY)

        from course.models import FlowSession
        from course.page import PageContext
        page_context = PageContext(
            course=pctx.course,
            repo=pctx.repo,
            commit_sha=pctx.course_commit_sha,

            # This helps code pages retrieve the editor pref.
            flow_session=FlowSession(course=pctx.course,
                                     participation=pctx.participation),
            in_sandbox=True)

        if page_data is None:
            page_data = page.initialize_page_data(page_context)
            pctx.request.session[PAGE_DATA_SESSION_KEY] = (page_desc.type,
                                                           page_desc.id,
                                                           page_data)

        title = page.title(page_context, page_data)
        body = page.body(page_context, page_data)

        feedback = None
        page_form_html = None

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

            if request.method == "POST" and not is_preview_post:
                page_form = page.process_form_post(page_context, page_data,
                                                   request.POST, request.FILES,
                                                   page_behavior)

                if page_form.is_valid():

                    answer_data = page.answer_data(page_context, page_data,
                                                   page_form, request.FILES)

                    feedback = page.grade(page_context,
                                          page_data,
                                          answer_data,
                                          grade_data=None)

                    pctx.request.session[ANSWER_DATA_SESSION_KEY] = (
                        page_desc.type, page_desc.id, answer_data)

            else:
                try:
                    page_form = page.make_form(page_context, page_data,
                                               answer_data, page_behavior)

                except Exception:
                    import sys
                    tp, e, _ = sys.exc_info()

                    page_errors = (
                        ugettext("Page failed to load/validate "
                                 "(change page ID to clear faults)") + ": " +
                        "%(err_type)s: %(err_str)s" % {
                            "err_type": tp.__name__,
                            "err_str": e
                        })  # type: ignore  # noqa: E501
                    have_valid_page = False

                    page_form = None

            if page_form is not None:
                page_form.helper.add_input(
                    Submit("submit", ugettext("Submit answer"), accesskey="g"))
                page_form_html = page.form_to_html(pctx.request, page_context,
                                                   page_form, answer_data)

        correct_answer = page.correct_answer(page_context,
                                             page_data,
                                             answer_data,
                                             grade_data=None)

        return render_course_page(
            pctx,
            "course/sandbox-page.html",
            {
                "edit_form": edit_form,
                "page_errors": page_errors,
                "page_warnings": page_warnings,
                "form": edit_form,  # to placate form.media
                "have_valid_page": True,
                "title": title,
                "body": body,
                "page_form_html": page_form_html,
                "feedback": feedback,
                "correct_answer": correct_answer,
            })

    else:

        return render_course_page(
            pctx,
            "course/sandbox-page.html",
            {
                "edit_form": edit_form,
                "form": edit_form,  # to placate form.media
                "have_valid_page": False,
                "page_errors": page_errors,
                "page_warnings": page_warnings,
            })
Example #53
0
def create_preapprovals(pctx):
    if pctx.role != participation_role.instructor:
        raise PermissionDenied(_("only instructors may do that"))

    request = pctx.request

    if request.method == "POST":
        form = BulkPreapprovalsForm(request.POST)
        if form.is_valid():

            created_count = 0
            exist_count = 0
            pending_approved_count = 0

            role = form.cleaned_data["role"]
            for l in form.cleaned_data["emails"].split("\n"):
                l = l.strip()

                if not l:
                    continue

                try:
                    preapproval = ParticipationPreapproval.objects.get(
                        email__iexact=l, course=pctx.course)
                except ParticipationPreapproval.DoesNotExist:

                    # approve if l is requesting enrollment
                    try:
                        pending_participation = Participation.objects.get(
                            course=pctx.course,
                            status=participation_status.requested,
                            user__email__iexact=l)

                    except Participation.DoesNotExist:
                        pass

                    else:
                        pending_participation.status = participation_status.active
                        pending_participation.save()
                        send_enrollment_decision(pending_participation, True,
                                                 request)
                        pending_approved_count += 1

                else:
                    exist_count += 1
                    continue

                preapproval = ParticipationPreapproval()
                preapproval.email = l
                preapproval.course = pctx.course
                preapproval.role = role
                preapproval.creator = request.user
                preapproval.save()

                created_count += 1

            messages.add_message(
                request, messages.INFO,
                _("%(n_created)d preapprovals created, "
                  "%(n_exist)d already existed, "
                  "%(n_requested_approved)d pending requests approved.") % {
                      'n_created': created_count,
                      'n_exist': exist_count,
                      'n_requested_approved': pending_approved_count
                  })
            return redirect("relate-home")

    else:
        form = BulkPreapprovalsForm()

    return render_course_page(
        pctx, "course/generic-course-form.html", {
            "form": form,
            "form_description": _("Create Participation Preapprovals"),
        })
Example #54
0
def update_course(pctx):
    if not (pctx.has_permission(pperm.update_content)
            or pctx.has_permission(pperm.preview_content)):
        raise PermissionDenied()

    course = pctx.course
    request = pctx.request
    content_repo = pctx.repo

    from course.content import SubdirRepoWrapper
    if isinstance(content_repo, SubdirRepoWrapper):
        repo = content_repo.repo
    else:
        repo = content_repo

    participation = pctx.participation

    previewing = bool(participation is not None
                      and participation.preview_git_commit_sha)

    may_update = pctx.has_permission(pperm.update_content)

    response_form = None
    if request.method == "POST":
        form = GitUpdateForm(may_update, previewing, repo, request.POST,
                             request.FILES)
        commands = [
            "fetch", "fetch_update", "update", "fetch_preview", "preview",
            "end_preview"
        ]

        command = None
        for cmd in commands:
            if cmd in form.data:
                command = cmd
                break

        if command is None:
            raise SuspiciousOperation(_("invalid command"))

        if form.is_valid():
            new_sha = form.cleaned_data["new_sha"].encode()

            try:
                run_course_update_command(
                    request,
                    repo,
                    content_repo,
                    pctx,
                    command,
                    new_sha,
                    may_update,
                    prevent_discarding_revisions=form.
                    cleaned_data["prevent_discarding_revisions"])
            except Exception as e:
                import traceback
                traceback.print_exc()

                messages.add_message(
                    pctx.request, messages.ERROR,
                    string_concat(
                        pgettext("Starting of Error message", "Error"),
                        ": %(err_type)s %(err_str)s") % {
                            "err_type": type(e).__name__,
                            "err_str": str(e)
                        })
        else:
            response_form = form

    if response_form is None:
        previewing = bool(participation is not None
                          and participation.preview_git_commit_sha)

        form = GitUpdateForm(may_update, previewing, repo, {
            "new_sha": repo.head(),
            "prevent_discarding_revisions": True,
        })

    text_lines = [
        "<table class='table'>",
        string_concat("<tr><th>", ugettext("Git Source URL"),
                      "</th><td><tt>%(git_source)s</tt></td></tr>") % {
                          'git_source': pctx.course.git_source
                      },
        string_concat("<tr><th>", ugettext("Public active git SHA"),
                      "</th><td> %(commit)s (%(message)s)</td></tr>") %
        {
            'commit':
            course.active_git_commit_sha,
            'message':
            _get_commit_message_as_html(repo, course.active_git_commit_sha)
        },
        string_concat("<tr><th>", ugettext("Current git HEAD"),
                      "</th><td>%(commit)s (%(message)s)</td></tr>") %
        {
            'commit': repo.head().decode(),
            'message': _get_commit_message_as_html(repo, repo.head())
        },
    ]
    if participation is not None and participation.preview_git_commit_sha:
        text_lines.append(
            string_concat("<tr><th>", ugettext("Current preview git SHA"),
                          "</th><td>%(commit)s (%(message)s)</td></tr>") % {
                              'commit':
                              participation.preview_git_commit_sha,
                              'message':
                              _get_commit_message_as_html(
                                  repo, participation.preview_git_commit_sha),
                          })
    else:
        text_lines.append("".join([
            "<tr><th>",
            ugettext("Current preview git SHA"),
            "</th><td>",
            ugettext("None"),
            "</td></tr>",
        ]))

    text_lines.append("</table>")

    return render_course_page(
        pctx, "course/generic-course-form.html", {
            "form": form,
            "form_text": "".join("<p>%s</p>" % line for line in text_lines),
            "form_description": ugettext("Update Course Revision"),
        })
Example #55
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 = unicode(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(unicode(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:
                human_title = 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 json import dumps
    return render_course_page(pctx, "course/calendar.html", {
        "events_json": dumps(events_json),
        "event_info_list": event_info_list,
    })
Example #56
0
def view_participant_grades(pctx, participation_id=None):
    if pctx.participation is None:
        raise PermissionDenied("must be enrolled to view grades")

    if participation_id is not None:
        grade_participation = Participation.objects.get(id=int(participation_id))
    else:
        grade_participation = pctx.participation

    if pctx.role in [
            participation_role.instructor,
            participation_role.teaching_assistant]:
        is_student_viewing = False
    elif pctx.role == participation_role.student:
        if grade_participation != pctx.participation:
            raise PermissionDenied("may not view other people's grades")

        is_student_viewing = True
    else:
        raise PermissionDenied()

    # NOTE: It's important that these two queries are sorted consistently,
    # also consistently with the code below.
    grading_opps = list((GradingOpportunity.objects
            .filter(
                course=pctx.course,
                shown_in_grade_book=True,
                )
            .order_by("identifier")))

    grade_changes = list(GradeChange.objects
            .filter(
                participation=grade_participation,
                opportunity__course=pctx.course,
                opportunity__shown_in_grade_book=True)
            .order_by(
                "participation__id",
                "opportunity__identifier",
                "grade_time")
            .select_related("participation")
            .select_related("participation__user")
            .select_related("opportunity"))

    idx = 0

    grade_table = []
    for opp in grading_opps:
        if is_student_viewing:
            if not (opp.shown_in_grade_book
                    and opp.shown_in_student_grade_book):
                continue
        else:
            if not opp.shown_in_grade_book:
                continue

        while (
                idx < len(grade_changes)
                and grade_changes[idx].opportunity.identifier < opp.identifier
                ):
            idx += 1

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

        state_machine = GradeStateMachine()
        state_machine.consume(my_grade_changes)

        grade_table.append(
                GradeInfo(
                    opportunity=opp,
                    grade_state_machine=state_machine))

    return render_course_page(pctx, "course/gradebook-participant.html", {
        "grade_table": grade_table,
        "grade_participation": grade_participation,
        "grading_opportunities": grading_opps,
        "grade_state_change_types": grade_state_change_types,
        })
Example #57
0
def create_preapprovals(pctx):
    if not pctx.has_permission(pperm.preapprove_participation):
        raise PermissionDenied(_("may not preapprove participation"))

    request = pctx.request

    if request.method == "POST":
        form = BulkPreapprovalsForm(pctx.course, request.POST)
        if form.is_valid():

            created_count = 0
            exist_count = 0
            pending_approved_count = 0

            roles = form.cleaned_data["roles"]
            for l in form.cleaned_data["preapproval_data"].split("\n"):
                l = l.strip()
                preapp_type = form.cleaned_data["preapproval_type"]

                if not l:
                    continue

                if preapp_type == "email":

                    try:
                        preapproval = ParticipationPreapproval.objects.get(
                            email__iexact=l, course=pctx.course)
                    except ParticipationPreapproval.DoesNotExist:

                        # approve if l is requesting enrollment
                        try:
                            pending = Participation.objects.get(
                                course=pctx.course,
                                status=participation_status.requested,
                                user__email__iexact=l)

                        except Participation.DoesNotExist:
                            pass

                        else:
                            pending.status = \
                                    participation_status.active
                            pending.save()
                            send_enrollment_decision(pending, True, request)
                            pending_approved_count += 1

                    else:
                        exist_count += 1
                        continue

                    preapproval = ParticipationPreapproval()
                    preapproval.email = l
                    preapproval.course = pctx.course
                    preapproval.creator = request.user
                    preapproval.save()
                    preapproval.roles.set(roles)

                    created_count += 1

                elif preapp_type == "institutional_id":

                    try:
                        preapproval = ParticipationPreapproval.objects.get(
                            course=pctx.course, institutional_id__iexact=l)

                    except ParticipationPreapproval.DoesNotExist:

                        # approve if l is requesting enrollment
                        try:
                            pending = Participation.objects.get(
                                course=pctx.course,
                                status=participation_status.requested,
                                user__institutional_id__iexact=l)
                            if (pctx.course.
                                    preapproval_require_verified_inst_id and
                                    not pending.user.institutional_id_verified
                                ):
                                raise Participation.DoesNotExist

                        except Participation.DoesNotExist:
                            pass

                        else:
                            pending.status = participation_status.active
                            pending.save()
                            send_enrollment_decision(pending, True, request)
                            pending_approved_count += 1

                    else:
                        exist_count += 1
                        continue

                    preapproval = ParticipationPreapproval()
                    preapproval.institutional_id = l
                    preapproval.course = pctx.course
                    preapproval.creator = request.user
                    preapproval.save()
                    preapproval.roles.set(roles)

                    created_count += 1

            messages.add_message(
                request, messages.INFO,
                _("%(n_created)d preapprovals created, "
                  "%(n_exist)d already existed, "
                  "%(n_requested_approved)d pending requests approved.") % {
                      'n_created': created_count,
                      'n_exist': exist_count,
                      'n_requested_approved': pending_approved_count
                  })
            return redirect("relate-course_page", pctx.course.identifier)

    else:
        form = BulkPreapprovalsForm(pctx.course)

    return render_course_page(
        pctx, "course/generic-course-form.html", {
            "form": form,
            "form_description": _("Create Participation Preapprovals"),
        })
Example #58
0
def grant_exception_stage_3(pctx, participation_id, flow_id, session_id):
    # type: (CoursePageContext, int, Text, int) -> http.HttpResponse

    if not pctx.has_permission(pperm.grant_exception):
        raise PermissionDenied(_("may not grant exceptions"))

    participation = get_object_or_404(Participation, id=participation_id)

    from course.content import get_flow_desc
    try:
        flow_desc = get_flow_desc(pctx.repo, pctx.course, flow_id,
                                  pctx.course_commit_sha)
    except ObjectDoesNotExist:
        raise http.Http404()

    session = FlowSession.objects.get(id=int(session_id))

    now_datetime = get_now_or_fake_time(pctx.request)
    from course.utils import (get_session_access_rule,
                              get_session_grading_rule)
    access_rule = get_session_access_rule(session, flow_desc, now_datetime)
    grading_rule = get_session_grading_rule(session, flow_desc, now_datetime)

    request = pctx.request
    if request.method == "POST":
        form = ExceptionStage3Form({}, flow_desc, session.access_rules_tag,
                                   request.POST)

        from course.constants import flow_rule_kind

        if form.is_valid():
            permissions = [
                key for key, _ in FLOW_PERMISSION_CHOICES
                if form.cleaned_data[key]
            ]

            from course.validation import (validate_session_access_rule,
                                           validate_session_grading_rule,
                                           ValidationContext)
            from relate.utils import dict_to_struct
            vctx = ValidationContext(repo=pctx.repo,
                                     commit_sha=pctx.course_commit_sha)

            flow_desc = get_flow_desc(pctx.repo, pctx.course, flow_id,
                                      pctx.course_commit_sha)

            tags = []  # type: List[Text]
            if hasattr(flow_desc, "rules"):
                try:
                    from typing import Text  # noqa
                except ImportError:
                    Text = None  # noqa
                tags = cast(List[Text], getattr(flow_desc.rules, "tags",
                                                []))  # type: ignore  # noqa

            # {{{ put together access rule

            if form.cleaned_data["create_access_exception"]:
                new_access_rule = {"permissions": permissions}

                if (form.cleaned_data.get("restrict_to_same_tag")
                        and session.access_rules_tag is not None):
                    new_access_rule["if_has_tag"] = session.access_rules_tag

                validate_session_access_rule(
                    vctx, ugettext("newly created exception"),
                    dict_to_struct(new_access_rule), tags)

                fre_access = FlowRuleException(
                    flow_id=flow_id,
                    participation=participation,
                    expiration=form.cleaned_data["access_expires"],
                    creator=pctx.request.user,
                    comment=form.cleaned_data["comment"],
                    kind=flow_rule_kind.access,
                    rule=new_access_rule)
                fre_access.save()

            # }}}

            new_access_rules_tag = form.cleaned_data.get(
                "set_access_rules_tag")
            if new_access_rules_tag == NONE_SESSION_TAG:
                new_access_rules_tag = None

            if session.access_rules_tag != new_access_rules_tag:
                session.access_rules_tag = new_access_rules_tag
                session.save()

            # {{{ put together grading rule

            if form.cleaned_data["create_grading_exception"]:
                due = form.cleaned_data["due"]
                if form.cleaned_data["due_same_as_access_expiration"]:
                    due = form.cleaned_data["access_expires"]

                descr = ugettext("Granted excecption")
                if form.cleaned_data["credit_percent"] is not None:
                    descr += string_concat(" (%.1f%% ", ugettext('credit'), ")") \
                            % form.cleaned_data["credit_percent"]

                due_local_naive = due
                if due_local_naive is not None:
                    from relate.utils import as_local_time
                    due_local_naive = (as_local_time(due_local_naive).replace(
                        tzinfo=None))

                new_grading_rule = {
                    "description": descr,
                }

                if due_local_naive is not None:
                    new_grading_rule["due"] = due_local_naive
                    new_grading_rule["if_completed_before"] = due_local_naive

                for attr_name in [
                        "credit_percent", "bonus_points", "max_points",
                        "max_points_enforced_cap", "generates_grade"
                ]:
                    if form.cleaned_data[attr_name] is not None:
                        new_grading_rule[attr_name] = form.cleaned_data[
                            attr_name]

                if (form.cleaned_data.get("restrict_to_same_tag")
                        and session.access_rules_tag is not None):
                    new_grading_rule["if_has_tag"] = session.access_rules_tag

                validate_session_grading_rule(
                    vctx, ugettext("newly created exception"),
                    dict_to_struct(new_grading_rule), tags,
                    grading_rule.grade_identifier)

                fre_grading = FlowRuleException(
                    flow_id=flow_id,
                    participation=participation,
                    creator=pctx.request.user,
                    comment=form.cleaned_data["comment"],
                    kind=flow_rule_kind.grading,
                    rule=new_grading_rule)
                fre_grading.save()

            # }}}

            messages.add_message(
                pctx.request, messages.SUCCESS,
                ugettext("Exception granted to '%(participation)s' "
                         "for '%(flow_id)s'.") % {
                             'participation': participation,
                             'flow_id': flow_id
                         })
            return redirect("relate-grant_exception", pctx.course.identifier)

    else:
        data = {
            "restrict_to_same_tag": session.access_rules_tag is not None,
            #"due_same_as_access_expiration": True,
            "due": grading_rule.due,
            "generates_grade": grading_rule.generates_grade,
            "credit_percent": grading_rule.credit_percent,
            "bonus_points": grading_rule.bonus_points,
            "max_points": grading_rule.max_points,
            "max_points_enforced_cap": grading_rule.max_points_enforced_cap,
        }
        for perm in access_rule.permissions:
            data[perm] = True

        form = ExceptionStage3Form(data, flow_desc, session.access_rules_tag)

    return render_course_page(
        pctx, "course/generic-course-form.html", {
            "form": form,
            "form_description": ugettext("Grant Exception"),
            "form_text": string_concat(
                "<div class='well'>",
                ugettext("Granting exception to '%(participation)s' "
                         "for '%(flow_id)s' (session %(session)s)."), "</div>")
            % {
                'participation': participation,
                'flow_id': flow_id,
                'session': strify_session_for_exception(session)
            },
        })
Example #59
0
def edit_participation(pctx, participation_id):
    # type: (CoursePageContext, int) -> http.HttpResponse
    if not pctx.has_permission(pperm.edit_participation):
        raise PermissionDenied()

    request = pctx.request

    num_participation_id = int(participation_id)

    if num_participation_id == -1:
        participation = Participation(course=pctx.course,
                                      status=participation_status.active)
        add_new = True
    else:
        participation = get_object_or_404(Participation,
                                          id=num_participation_id)
        add_new = False

    if participation.course.id != pctx.course.id:
        raise SuspiciousOperation(
            "may not edit participation in different course")

    if request.method == 'POST':
        form = EditParticipationForm(add_new,
                                     pctx,
                                     request.POST,
                                     instance=participation)
        reset_form = False

        try:
            if form.is_valid():
                if "submit" in request.POST:
                    form.save()

                    messages.add_message(request, messages.SUCCESS,
                                         _("Changes saved."))

                elif "approve" in request.POST:
                    send_enrollment_decision(participation, True, pctx.request)

                    # FIXME: Double-saving
                    participation = form.save()
                    participation.status = participation_status.active
                    participation.save()
                    reset_form = True

                    messages.add_message(request, messages.SUCCESS,
                                         _("Successfully enrolled."))

                elif "deny" in request.POST:
                    send_enrollment_decision(participation, False,
                                             pctx.request)

                    # FIXME: Double-saving
                    participation = form.save()
                    participation.status = participation_status.denied
                    participation.save()
                    reset_form = True

                    messages.add_message(request, messages.SUCCESS,
                                         _("Successfully denied."))

                elif "drop" in request.POST:
                    # FIXME: Double-saving
                    participation = form.save()
                    participation.status = participation_status.dropped
                    participation.save()
                    reset_form = True

                    messages.add_message(request, messages.SUCCESS,
                                         _("Successfully dropped."))
        except IntegrityError as e:
            messages.add_message(
                request, messages.ERROR,
                _("A data integrity issue was detected when saving "
                  "this participation. Maybe a participation for "
                  "this user already exists? (%s)") % str(e))

        if reset_form:
            form = EditParticipationForm(add_new, pctx, instance=participation)
    else:
        form = EditParticipationForm(add_new, pctx, instance=participation)

    return render_course_page(pctx, "course/generic-course-form.html", {
        "form_description": _("Edit Participation"),
        "form": form
    })
Example #60
0
def import_grades(pctx):
    if pctx.role != participation_role.instructor:
        raise PermissionDenied()

    form_text = ""

    log_lines = []

    request = pctx.request
    if request.method == "POST":
        form = ImportGradesForm(
                pctx.course, request.POST, request.FILES)

        is_import = "import" in request.POST
        if form.is_valid():
            try:
                total_count, grade_changes = csv_to_grade_changes(
                        log_lines=log_lines,
                        course=pctx.course,
                        grading_opportunity=form.cleaned_data["grading_opportunity"],
                        attempt_id=form.cleaned_data["attempt_id"],
                        file_contents=request.FILES["file"],
                        id_column=form.cleaned_data["id_column"],
                        points_column=form.cleaned_data["points_column"],
                        feedback_column=form.cleaned_data["feedback_column"],
                        max_points=form.cleaned_data["max_points"],
                        creator=request.user,
                        grade_time=now(),
                        has_header=form.cleaned_data["format"] == "csvhead")
            except Exception as e:
                messages.add_message(pctx.request, messages.ERROR,
                        "Error: %s %s" % (type(e).__name__, str(e)))
            else:
                if total_count != len(grade_changes):
                    messages.add_message(pctx.request, messages.INFO,
                            "%d grades found, %d unchanged."
                            % (total_count, total_count - len(grade_changes)))

                from django.template.loader import render_to_string

                if is_import:
                    GradeChange.objects.bulk_create(grade_changes)
                    form_text = render_to_string(
                            "course/grade-import-preview.html", {
                                "show_grade_changes": False,
                                "log_lines": log_lines,
                                })
                    messages.add_message(pctx.request, messages.SUCCESS,
                            "%d grades imported." % len(grade_changes))
                else:
                    form_text = render_to_string(
                            "course/grade-import-preview.html", {
                                "show_grade_changes": True,
                                "grade_changes": grade_changes,
                                "log_lines": log_lines,
                                })

    else:
        form = ImportGradesForm(pctx.course)

    return render_course_page(pctx, "course/generic-course-form.html", {
        "form_description": "Import Grade Data",
        "form": form,
        "form_text": form_text,
        })