Exemple #1
0
    def test_language_override_course_has_force_lang(self):
        self.course.force_lang = "zh-hans"
        self.course.save()

        with LanguageOverride(course=self.course):
            self.assertEqual(translation.get_language(), "zh-hans")

        self.assertEqual(translation.get_language(), "ko")
Exemple #2
0
    def test_language_override_deactivate(self):
        self.course.force_lang = "zh-hans"
        self.course.save()

        with LanguageOverride(course=self.course, deactivate=True):
            self.assertEqual(translation.get_language(), "zh-hans")
            self.assertEqual(translation.ugettext("user"), u"用户")

        self.assertEqual(translation.get_language(), "en-us")
Exemple #3
0
    def test_language_override_no_course_force_lang_no_admin_lang(self):
        if self.course.force_lang:
            self.course.force_lang = ""
            self.course.save()

        with LanguageOverride(course=self.course):
            self.assertEqual(translation.get_language(), None)
            self.assertEqual(translation.ugettext("whatever"), "whatever")

        self.assertEqual(translation.get_language(), "en-us")
Exemple #4
0
    def test_language_override_no_course_force_lang(self):
        if self.course.force_lang:
            self.course.force_lang = ""
            self.course.save()
        with LanguageOverride(course=self.course):
            self.assertEqual(translation.get_language(), "de")
            self.assertEqual(translation.ugettext("user"), u"Benutzer")

        self.assertEqual(translation.get_language(), "ko")
        self.assertEqual(translation.ugettext("user"), u"사용자")
Exemple #5
0
    def test_language_override_no_course_force_lang_no_langcode(self):
        if self.course.force_lang:
            self.course.force_lang = ""
            self.course.save()

        translation.deactivate_all()
        with LanguageOverride(course=self.course):
            self.assertEqual(translation.get_language(), "de")
            self.assertEqual(translation.ugettext("user"), u"Benutzer")

        self.assertEqual(translation.get_language(), None)
        self.assertEqual(translation.ugettext("whatever"), "whatever")
Exemple #6
0
def send_enrollment_decision(participation, approved, request=None):
    # type: (Participation, bool, http.HttpRequest) -> None

    course = participation.course
    with LanguageOverride(course=course):
        if request:
            course_uri = request.build_absolute_uri(
                    reverse("relate-course_page",
                        args=(course.identifier,)))
        else:
            # This will happen when this method is triggered by
            # a model signal which doesn't contain a request object.
            from urllib.parse import urljoin
            course_uri = urljoin(getattr(settings, "RELATE_BASE_URL"),
                                 course.get_absolute_url())

        from relate.utils import render_email_template
        message = render_email_template("course/enrollment-decision-email.txt", {
            "user": participation.user,
            "approved": approved,
            "course": course,
            "course_uri": course_uri
            })

        from django.core.mail import EmailMessage
        email_kwargs = {}
        if settings.RELATE_EMAIL_SMTP_ALLOW_NONAUTHORIZED_SENDER:
            from_email = course.get_from_email()
        else:
            from_email = getattr(settings, "ENROLLMENT_EMAIL_FROM",
                                 settings.ROBOT_EMAIL_FROM)
            from relate.utils import get_outbound_mail_connection
            email_kwargs.update(
                {"connection": (
                    get_outbound_mail_connection("enroll")
                    if hasattr(settings, "ENROLLMENT_EMAIL_FROM")
                    else get_outbound_mail_connection("robot"))})

        msg = EmailMessage(
                string_concat("[%s] ", _("Your enrollment request"))
                % course.identifier,
                message,
                from_email,
                [participation.user.email],
                **email_kwargs)
        msg.bcc = [course.notify_email]
        msg.send()
Exemple #7
0
    def grade(self, page_context, page_data, answer_data, grade_data):
        if answer_data is None:
            return AnswerFeedback(correctness=0,
                                  feedback=_("No answer provided."))

        user_code = answer_data["answer"]

        # {{{ request run

        run_req = {"compile_only": False, "user_code": user_code}

        def transfer_attr(name):
            if hasattr(self.page_desc, name):
                run_req[name] = getattr(self.page_desc, name)

        transfer_attr("setup_code")
        transfer_attr("names_for_user")
        transfer_attr("names_from_user")

        run_req["test_code"] = self.get_test_code()

        if hasattr(self.page_desc, "data_files"):
            run_req["data_files"] = {}

            from course.content import get_repo_blob

            for data_file in self.page_desc.data_files:
                from base64 import b64encode
                run_req["data_files"][data_file] = \
                        b64encode(
                                get_repo_blob(
                                    page_context.repo, data_file,
                                    page_context.commit_sha).data).decode()

        try:
            response_dict = request_run_with_retries(
                run_req,
                run_timeout=self.page_desc.timeout,
                image=self.container_image)
        except Exception:
            from traceback import format_exc
            response_dict = {
                "result": "uncaught_error",
                "message": "Error connecting to container",
                "traceback": "".join(format_exc()),
            }

        # }}}

        feedback_bits = []

        correctness = None

        if "points" in response_dict:
            correctness = response_dict["points"]
            try:
                feedback_bits.append("<p><b>%s</b></p>" %
                                     _(get_auto_feedback(correctness)))
            except Exception as e:
                correctness = None
                response_dict["result"] = "setup_error"
                response_dict["message"] = ("%s: %s" %
                                            (type(e).__name__, str(e)))

        # {{{ send email if the grading code broke

        if response_dict["result"] in [
                "uncaught_error", "setup_compile_error", "setup_error",
                "test_compile_error", "test_error"
        ]:
            error_msg_parts = ["RESULT: %s" % response_dict["result"]]
            for key, val in sorted(response_dict.items()):
                if (key not in ["result", "figures"] and val
                        and isinstance(val, str)):
                    error_msg_parts.append(
                        "-------------------------------------")
                    error_msg_parts.append(key)
                    error_msg_parts.append(
                        "-------------------------------------")
                    error_msg_parts.append(val)
            error_msg_parts.append("-------------------------------------")
            error_msg_parts.append("user code")
            error_msg_parts.append("-------------------------------------")
            error_msg_parts.append(user_code)
            error_msg_parts.append("-------------------------------------")

            error_msg = "\n".join(error_msg_parts)

            from relate.utils import local_now, format_datetime_local
            from course.utils import LanguageOverride
            with LanguageOverride(page_context.course):
                from relate.utils import render_email_template
                message = render_email_template(
                    "course/broken-code-question-email.txt", {
                        "site": getattr(settings, "RELATE_BASE_URL"),
                        "page_id": self.page_desc.id,
                        "course": page_context.course,
                        "error_message": error_msg,
                        "review_uri": page_context.page_uri,
                        "time": format_datetime_local(local_now())
                    })

                if (not page_context.in_sandbox
                        and not is_nuisance_failure(response_dict)):
                    try:
                        from django.core.mail import EmailMessage
                        msg = EmailMessage(
                            "".join([
                                "[%s:%s] ",
                                _("code question execution failed")
                            ]) % (page_context.course.identifier,
                                  page_context.flow_session.flow_id
                                  if page_context.flow_session is not None else
                                  _("<unknown flow>")), message,
                            settings.ROBOT_EMAIL_FROM,
                            [page_context.course.notify_email])

                        from relate.utils import get_outbound_mail_connection
                        msg.connection = get_outbound_mail_connection("robot")
                        msg.send()

                    except Exception:
                        from traceback import format_exc
                        feedback_bits.append(
                            str(
                                string_concat(
                                    "<p>",
                                    _("Both the grading code and the attempt to "
                                      "notify course staff about the issue failed. "
                                      "Please contact the course or site staff and "
                                      "inform them of this issue, mentioning this "
                                      "entire error message:"), "</p>", "<p>",
                                    _("Sending an email to the course staff about the "
                                      "following failure failed with "
                                      "the following error message:"), "<pre>",
                                    "".join(format_exc()), "</pre>",
                                    _("The original failure message follows:"),
                                    "</p>")))

        # }}}

        if hasattr(self.page_desc, "correct_code"):

            def normalize_code(s):
                return (s.replace(" ", "").replace("\r", "").replace(
                    "\n", "").replace("\t", ""))

            if (normalize_code(user_code) == normalize_code(
                    self.page_desc.correct_code)):
                feedback_bits.append(
                    "<p><b>%s</b></p>" %
                    _("It looks like you submitted code that is identical to "
                      "the reference solution. This is not allowed."))

        from relate.utils import dict_to_struct
        response = dict_to_struct(response_dict)

        bulk_feedback_bits = []

        if response.result == "success":
            pass
        elif response.result in [
                "uncaught_error", "setup_compile_error", "setup_error",
                "test_compile_error", "test_error"
        ]:
            feedback_bits.append("".join([
                "<p>",
                _("The grading code failed. Sorry about that. "
                  "The staff has been informed, and if this problem is "
                  "due to an issue with the grading code, "
                  "it will be fixed as soon as possible. "
                  "In the meantime, you'll see a traceback "
                  "below that may help you figure out what went wrong."),
                "</p>"
            ]))
        elif response.result == "timeout":
            feedback_bits.append("".join([
                "<p>",
                _("Your code took too long to execute. The problem "
                  "specifies that your code may take at most %s seconds "
                  "to run. "
                  "It took longer than that and was aborted."), "</p>"
            ]) % self.page_desc.timeout)

            correctness = 0
        elif response.result == "user_compile_error":
            feedback_bits.append("".join([
                "<p>",
                _("Your code failed to compile. An error message is "
                  "below."), "</p>"
            ]))

            correctness = 0
        elif response.result == "user_error":
            feedback_bits.append("".join([
                "<p>",
                _("Your code failed with an exception. "
                  "A traceback is below."), "</p>"
            ]))

            correctness = 0
        else:
            raise RuntimeError("invalid run result: %s" % response.result)

        if hasattr(response, "feedback") and response.feedback:

            def sanitize(s):
                import bleach
                return bleach.clean(s, tags=["p", "pre"])

            feedback_bits.append("".join([
                "<p>",
                _("Here is some feedback on your code"), ":"
                "<ul>%s</ul></p>"
            ]) % "".join("<li>%s</li>" % sanitize(fb_item)
                         for fb_item in response.feedback))
        if hasattr(response, "traceback") and response.traceback:
            feedback_bits.append("".join([
                "<p>",
                _("This is the exception traceback"), ":"
                "<pre>%s</pre></p>"
            ]) % escape(response.traceback))
        if hasattr(response,
                   "exec_host") and response.exec_host != "localhost":
            import socket
            try:
                exec_host_name, dummy, dummy = socket.gethostbyaddr(
                    response.exec_host)
            except socket.error:
                exec_host_name = response.exec_host

            feedback_bits.append("".join(
                ["<p>",
                 _("Your code ran on %s.") % exec_host_name, "</p>"]))

        if hasattr(response, "stdout") and response.stdout:
            bulk_feedback_bits.append("".join([
                "<p>",
                _("Your code printed the following output"), ":"
                "<pre>%s</pre></p>"
            ]) % escape(response.stdout))
        if hasattr(response, "stderr") and response.stderr:
            bulk_feedback_bits.append("".join([
                "<p>",
                _("Your code printed the following error messages"), ":"
                "<pre>%s</pre></p>"
            ]) % escape(response.stderr))
        if hasattr(response, "figures") and response.figures:
            fig_lines = [
                "".join([
                    "<p>",
                    _("Your code produced the following plots"), ":</p>"
                ]),
                '<dl class="result-figure-list">',
            ]

            for nr, mime_type, b64data in response.figures:
                if mime_type in ["image/jpeg", "image/png"]:
                    fig_lines.extend([
                        "".join(["<dt>", _("Figure"), "%d<dt>"]) % nr,
                        '<dd><img alt="Figure %d" src="data:%s;base64,%s"></dd>'
                        % (nr, mime_type, b64data)
                    ])

            fig_lines.append("</dl>")
            bulk_feedback_bits.extend(fig_lines)

        # {{{ html output / santization

        if hasattr(response, "html") and response.html:

            def is_allowed_data_uri(allowed_mimetypes, uri):
                import re
                m = re.match(r"^data:([-a-z0-9]+/[-a-z0-9]+);base64,", uri)
                if not m:
                    return False

                mimetype = m.group(1)
                return mimetype in allowed_mimetypes

            def sanitize(s):
                import bleach

                def filter_audio_attributes(tag, name, value):
                    if name in ["controls"]:
                        return True
                    else:
                        return False

                def filter_source_attributes(tag, name, value):
                    if name in ["type"]:
                        return True
                    elif name == "src":
                        if is_allowed_data_uri([
                                "audio/wav",
                        ], value):
                            return bleach.sanitizer.VALUE_SAFE
                        else:
                            return False
                    else:
                        return False

                def filter_img_attributes(tag, name, value):
                    if name in ["alt", "title"]:
                        return True
                    elif name == "src":
                        return is_allowed_data_uri([
                            "image/png",
                            "image/jpeg",
                        ], value)
                    else:
                        return False

                if not isinstance(s, str):
                    return _("(Non-string in 'HTML' output filtered out)")

                return bleach.clean(s,
                                    tags=bleach.ALLOWED_TAGS +
                                    ["audio", "video", "source"],
                                    attributes={
                                        "audio": filter_audio_attributes,
                                        "source": filter_source_attributes,
                                        "img": filter_img_attributes,
                                    })

            bulk_feedback_bits.extend(
                sanitize(snippet) for snippet in response.html)

        # }}}

        return AnswerFeedback(correctness=correctness,
                              feedback="\n".join(feedback_bits),
                              bulk_feedback="\n".join(bulk_feedback_bits))
Exemple #8
0
def enroll_view(request, course_identifier):
    # type: (http.HttpRequest, str) -> http.HttpResponse

    course = get_object_or_404(Course, identifier=course_identifier)
    user = request.user
    participations = Participation.objects.filter(course=course, user=user)
    if not participations.count():
        participation = None
    else:
        participation = participations.first()

    if participation is not None:
        if participation.status == participation_status.requested:
            messages.add_message(
                request, messages.ERROR,
                _("You have previously sent the enrollment "
                  "request. Re-sending the request is not "
                  "allowed."))
            return redirect("relate-course_page", course_identifier)
        elif participation.status == participation_status.denied:
            messages.add_message(
                request, messages.ERROR,
                _("Your enrollment request had been denied. "
                  "Enrollment is not allowed."))
            return redirect("relate-course_page", course_identifier)
        elif participation.status == participation_status.dropped:
            messages.add_message(
                request, messages.ERROR,
                _("You had been dropped from the course. "
                  "Re-enrollment is not allowed."))
            return redirect("relate-course_page", course_identifier)
        else:
            assert participation.status == participation_status.active
            messages.add_message(request, messages.ERROR,
                                 _("Already enrolled. Cannot re-enroll."))
        return redirect("relate-course_page", course_identifier)

    if not course.accepts_enrollment:
        messages.add_message(request, messages.ERROR,
                             _("Course is not accepting enrollments."))
        return redirect("relate-course_page", course_identifier)

    if request.method != "POST":
        # This can happen if someone tries to refresh the page, or switches to
        # desktop view on mobile.
        messages.add_message(request, messages.ERROR,
                             _("Can only enroll using POST request"))
        return redirect("relate-course_page", course_identifier)

    if user.status != user_status.active:
        messages.add_message(
            request, messages.ERROR,
            _("Your email address is not yet confirmed. "
              "Confirm your email to continue."))
        return redirect("relate-course_page", course_identifier)

    preapproval = None
    if request.user.email:  # pragma: no branch (user email NOT NULL constraint)
        try:
            preapproval = ParticipationPreapproval.objects.get(
                course=course, email__iexact=request.user.email)
        except ParticipationPreapproval.DoesNotExist:
            pass

    if preapproval is None:
        if user.institutional_id:
            if not (course.preapproval_require_verified_inst_id
                    and not user.institutional_id_verified):
                try:
                    preapproval = ParticipationPreapproval.objects.get(
                        course=course,
                        institutional_id__iexact=user.institutional_id)
                except ParticipationPreapproval.DoesNotExist:
                    pass

    def email_suffix_matches(email, suffix):
        # type: (Text, Text) -> bool
        if suffix.startswith("@"):
            return email.endswith(suffix)
        else:
            return email.endswith("@%s" % suffix) or email.endswith(
                ".%s" % suffix)

    if (preapproval is None and course.enrollment_required_email_suffix
            and not email_suffix_matches(
                user.email, course.enrollment_required_email_suffix)):

        messages.add_message(
            request, messages.ERROR,
            _("Enrollment not allowed. Please use your '%s' email to "
              "enroll.") % course.enrollment_required_email_suffix)
        return redirect("relate-course_page", course_identifier)

    roles = ParticipationRole.objects.filter(
        course=course, is_default_for_new_participants=True)

    if preapproval is not None:
        roles = list(preapproval.roles.all())

    try:
        if course.enrollment_approval_required and preapproval is None:
            participation = handle_enrollment_request(
                course, user, participation_status.requested, roles, request)

            assert participation is not None

            with LanguageOverride(course=course):
                from relate.utils import render_email_template
                message = render_email_template(
                    "course/enrollment-request-email.txt", {
                        "user":
                        user,
                        "course":
                        course,
                        "admin_uri":
                        mark_safe(
                            request.build_absolute_uri(
                                reverse("relate-edit_participation",
                                        args=(course.identifier,
                                              participation.id))))
                    })

                from django.core.mail import EmailMessage
                msg = EmailMessage(
                    string_concat("[%s] ", _("New enrollment request")) %
                    course_identifier, message,
                    getattr(settings, "ENROLLMENT_EMAIL_FROM",
                            settings.ROBOT_EMAIL_FROM), [course.notify_email])

                from relate.utils import get_outbound_mail_connection
                msg.connection = (get_outbound_mail_connection("enroll")
                                  if hasattr(settings, "ENROLLMENT_EMAIL_FROM")
                                  else get_outbound_mail_connection("robot"))

                msg.send()

            messages.add_message(
                request, messages.INFO,
                _("Enrollment request sent. You will receive notifcation "
                  "by email once your request has been acted upon."))
        else:
            handle_enrollment_request(course, user,
                                      participation_status.active, roles,
                                      request)

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

    except IntegrityError:
        messages.add_message(
            request, messages.ERROR,
            _("A participation already exists. Enrollment attempt aborted."))

    return redirect("relate-course_page", course_identifier)
Exemple #9
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':
            '%}',
        })
Exemple #10
0
    def update_grade_data_from_grading_form_v2(self, request, page_context,
            page_data, grade_data, grading_form, files_data):

        if grade_data is None:
            grade_data = {}
        for k in self.grade_data_attrs:
            if k == "grade_percent":
                grade_data[k] = grading_form.cleaned_percent()
            else:
                grade_data[k] = grading_form.cleaned_data[k]

        if grading_form.cleaned_data["notify"] and page_context.flow_session:
            from course.utils import LanguageOverride
            with LanguageOverride(page_context.course):
                from relate.utils import render_email_template
                from course.utils import will_use_masked_profile_for_email
                staff_email = [page_context.course.notify_email, request.user.email]
                message = render_email_template("course/grade-notify.txt", {
                    "page_title": self.title(page_context, page_data),
                    "course": page_context.course,
                    "participation": page_context.flow_session.participation,
                    "feedback_text": grade_data["feedback_text"],
                    "flow_session": page_context.flow_session,
                    "review_uri": page_context.page_uri,
                    "use_masked_profile":
                        will_use_masked_profile_for_email(staff_email)
                    })

                from django.core.mail import EmailMessage
                msg = EmailMessage(
                        string_concat("[%(identifier)s:%(flow_id)s] ",
                            _("New notification"))
                        % {'identifier': page_context.course.identifier,
                            'flow_id': page_context.flow_session.flow_id},
                        message,
                        getattr(settings, "GRADER_FEEDBACK_EMAIL_FROM",
                                page_context.course.get_from_email()),
                        [page_context.flow_session.participation.user.email])
                msg.bcc = [page_context.course.notify_email]

                if grading_form.cleaned_data["may_reply"]:
                    msg.reply_to = [request.user.email]

                if hasattr(settings, "GRADER_FEEDBACK_EMAIL_FROM"):
                    from relate.utils import get_outbound_mail_connection
                    msg.connection = get_outbound_mail_connection("grader_feedback")
                msg.send()

        if (grading_form.cleaned_data["notes"]
                and grading_form.cleaned_data["notify_instructor"]
                and page_context.flow_session):
            from course.utils import LanguageOverride
            with LanguageOverride(page_context.course):
                from relate.utils import render_email_template
                from course.utils import will_use_masked_profile_for_email
                staff_email = [page_context.course.notify_email, request.user.email]
                use_masked_profile = will_use_masked_profile_for_email(staff_email)
                if use_masked_profile:
                    username = (
                        page_context.flow_session.user.get_masked_profile())
                else:
                    username = (
                        page_context.flow_session.user.get_email_appellation())
                message = render_email_template(
                    "course/grade-internal-notes-notify.txt",
                    {
                        "page_title": self.title(page_context, page_data),
                        "username": username,
                        "course": page_context.course,
                        "participation": page_context.flow_session.participation,
                        "notes_text": grade_data["notes"],
                        "flow_session": page_context.flow_session,
                        "review_uri": page_context.page_uri,
                        "sender": request.user,
                    })

                from django.core.mail import EmailMessage
                msg = EmailMessage(
                        string_concat("[%(identifier)s:%(flow_id)s] ",
                            _("Grading notes from %(ta)s"))
                        % {'identifier': page_context.course.identifier,
                           'flow_id': page_context.flow_session.flow_id,
                           'ta': request.user.get_full_name()
                           },
                        message,
                        getattr(settings, "GRADER_FEEDBACK_EMAIL_FROM",
                                page_context.course.get_from_email()),
                        [page_context.course.notify_email])
                msg.bcc = [request.user.email]
                msg.reply_to = [request.user.email]

                if hasattr(settings, "GRADER_FEEDBACK_EMAIL_FROM"):
                    from relate.utils import get_outbound_mail_connection
                    msg.connection = get_outbound_mail_connection("grader_feedback")
                msg.send()

        return grade_data