diff --git a/course/flow.py b/course/flow.py index 3d7476d68b1d65b6aeee056b622e2209a477d6ea..eafab91029e9528e594bd3932b2e02f7eaef83e7 100644 --- a/course/flow.py +++ b/course/flow.py @@ -2063,6 +2063,11 @@ def view_flow_page(pctx, flow_session_id, ordinal): "viewing_prior_version": viewing_prior_version, "prev_answer_visits": prev_answer_visits, "prev_visit_id": prev_visit_id, + + # Wrappers used by JavaScript template (tmpl) so as not to + # conflict with Django template's tag wrapper + "JQ_OPEN": '{%', + 'JQ_CLOSE': '%}', } if fpctx.page.expects_answer() and fpctx.page.is_answer_gradable(): @@ -2080,6 +2085,39 @@ def view_flow_page(pctx, flow_session_id, ordinal): # }}} +@course_view +def get_prev_answer_visits_dropdown_content(pctx, flow_session_id, page_ordinal): + """ + :return: serialized prev_answer_visits items for past-submission-dropdown + """ + request = pctx.request + if not request.is_ajax() or request.method != "GET": + raise PermissionDenied() + + try: + page_ordinal = int(page_ordinal) + flow_session_id = int(flow_session_id) + except ValueError: + raise http.Http404() + + flow_session = get_and_check_flow_session(pctx, int(flow_session_id)) + + page_data = get_object_or_404( + FlowPageData, flow_session=flow_session, ordinal=page_ordinal) + prev_answer_visits = get_prev_answer_visits_qset(page_data) + + def serialize(obj): + return { + "id": obj.id, + "visit_time": ( + format_datetime_local(as_local_time(obj.visit_time))), + "is_submitted_answer": obj.is_submitted_answer, + } + + return http.JsonResponse( + {"result": [serialize(visit) for visit in prev_answer_visits]}) + + def get_pressed_button(form): # type: (StyledForm) -> Text diff --git a/course/grading.py b/course/grading.py index d8e58a771bfb7bdd795e1d7702c909710e8ea2e8..910edc9a7a122892d491b0612817f020c9357369 100644 --- a/course/grading.py +++ b/course/grading.py @@ -28,7 +28,9 @@ THE SOFTWARE. from django.utils.translation import ugettext as _ from django.shortcuts import ( # noqa get_object_or_404, redirect) -from relate.utils import retry_transaction_decorator +from relate.utils import ( + retry_transaction_decorator, + as_local_time, format_datetime_local) from django.contrib import messages from django.core.exceptions import ( # noqa PermissionDenied, SuspiciousOperation, @@ -62,10 +64,64 @@ if False: from course.utils import ( # noqa CoursePageContext) import datetime # noqa + from django.db.models import query # noqa # }}} +def get_prev_visit_grades( + flow_session_id, # type: int + page_ordinal, # type: int + reversed_on_visit_time_and_grade_time=False # type: Optional[bool] + ): + # type: (...) -> query.QuerySet + order_by_args = [] # type: List[Text] + if reversed_on_visit_time_and_grade_time: + order_by_args = ["-visit__visit_time", "-grade_time"] + return (FlowPageVisitGrade.objects + .filter( + visit__flow_session_id=flow_session_id, + visit__page_data__ordinal=page_ordinal, + visit__is_submitted_answer=True) + .order_by(*order_by_args) + .select_related("visit")) + + +@course_view +def get_prev_grades_dropdown_content(pctx, flow_session_id, page_ordinal): + """ + :return: serialized prev_grades items for rendering past-grades-dropdown + """ + request = pctx.request + if not request.is_ajax() or request.method != "GET": + raise PermissionDenied() + + try: + page_ordinal = int(page_ordinal) + flow_session_id = int(flow_session_id) + except ValueError: + raise http.Http404() + + if not pctx.participation: + raise PermissionDenied(_("may not view grade book")) + if not pctx.participation.has_permission(pperm.view_gradebook): + raise PermissionDenied(_("may not view grade book")) + + prev_grades = get_prev_visit_grades(flow_session_id, page_ordinal, True) + + def serialize(obj): + return { + "id": obj.id, + "visit_time": ( + format_datetime_local(as_local_time(obj.visit.visit_time))), + "grade_time": format_datetime_local(as_local_time(obj.grade_time)), + "value": obj.value(), + } + + return http.JsonResponse( + {"result": [serialize(pgrade) for pgrade in prev_grades]}) + + # {{{ grading driver @course_view @@ -136,13 +192,7 @@ def grade_flow_page(pctx, flow_session_id, page_ordinal): # }}} - prev_grades = (FlowPageVisitGrade.objects - .filter( - visit__flow_session=flow_session, - visit__page_data__ordinal=page_ordinal, - visit__is_submitted_answer=True) - .order_by("-visit__visit_time", "-grade_time") - .select_related("visit")) + prev_grades = get_prev_visit_grades(flow_session_id, page_ordinal) # {{{ reproduce student view @@ -261,7 +311,7 @@ def grade_flow_page(pctx, flow_session_id, page_ordinal): correctness=correctness, feedback=feedback_json) - _save_grade(fpctx, flow_session, most_recent_grade, + prev_grade_id = _save_grade(fpctx, flow_session, most_recent_grade, bulk_feedback_json, now_datetime) else: grading_form = fpctx.page.make_grading_form( @@ -329,7 +379,6 @@ def grade_flow_page(pctx, flow_session_id, page_ordinal): "max_points": max_points, "points_awarded": points_awarded, "shown_grade": shown_grade, - "prev_grades": prev_grades, "prev_grade_id": prev_grade_id, "grading_opportunity": grading_opportunity, @@ -342,6 +391,12 @@ def grade_flow_page(pctx, flow_session_id, page_ordinal): "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': '%}', }) @@ -353,8 +408,9 @@ def _save_grade( bulk_feedback_json, # type: Any now_datetime, # type: datetime.datetime ): - # type: (...) -> None + # type: (...) -> int most_recent_grade.save() + most_recent_grade_id = most_recent_grade.id update_bulk_feedback( fpctx.prev_answer_visit.page_data, @@ -367,6 +423,8 @@ def _save_grade( from course.flow import grade_flow_session grade_flow_session(fpctx, flow_session, grading_rule) + return most_recent_grade_id + # }}} diff --git a/course/templates/course/flow-page.html b/course/templates/course/flow-page.html index 6324c3e76b0a726986337ff460e584ed90b0a5f2..5b88808776d6ec4d424d0495a0a7028854ab169a 100644 --- a/course/templates/course/flow-page.html +++ b/course/templates/course/flow-page.html @@ -156,39 +156,21 @@ {# {{{ past submissions #} - {% if prev_answer_visits|length > 1 %} - {% endif %} {# }}} #} @@ -623,4 +605,54 @@ {% endblock %} +{% block page_bottom_javascript_extra %} + + + + + {# https://github.com/blueimp/JavaScript-Templates #} + + {{ block.super }} +{% endblock %} {# vim: set foldmethod=marker: #} diff --git a/course/templates/course/grade-flow-page.html b/course/templates/course/grade-flow-page.html index 46649a3d3e329b209cbac4affacfb5b107866d96..9b1858aa3ecc437537c5df8fdfca7397756a2266 100644 --- a/course/templates/course/grade-flow-page.html +++ b/course/templates/course/grade-flow-page.html @@ -15,36 +15,16 @@ {# {{{ past submissions #} @@ -423,4 +403,58 @@ {% endblock %} +{% block page_bottom_javascript_extra %} + + + + + {# https://github.com/blueimp/JavaScript-Templates #} + + {{ block.super }} +{% endblock %} + {# vim: set foldmethod=marker: #} diff --git a/relate/settings.py b/relate/settings.py index a4c4fe9779adc3fc74689b2bb0dba14be6dae101..88045f2fd26083e9203618da2534365d02495ffc 100644 --- a/relate/settings.py +++ b/relate/settings.py @@ -122,6 +122,7 @@ BOWER_INSTALLED_APPS = ( "jstree#3.2.1", "select2#4.0.1", "select2-bootstrap-css", + "blueimp-tmpl", ) CODEMIRROR_PATH = "codemirror" diff --git a/relate/templates/base.html b/relate/templates/base.html index ded45cd371df1ea05b6dfb51af94e9d4f28e2bc4..425a1a2c283cd7aa4b710d59816f7c1ac292e8dd 100644 --- a/relate/templates/base.html +++ b/relate/templates/base.html @@ -137,6 +137,7 @@ {% endblock %} + {% block page_bottom_javascript_extra %}{% endblock %} diff --git a/relate/urls.py b/relate/urls.py index 390627a930b6e5427fabe184f61921a1b9678ea7..a9cc437f912d309f7dfe3906bcd7b1cf586ca370 100644 --- a/relate/urls.py +++ b/relate/urls.py @@ -230,6 +230,16 @@ urlpatterns = [ course.grading.grade_flow_page, name="relate-grade_flow_page"), + url(r"^course" + "/" + COURSE_ID_REGEX + + "/prev_grades" + "/flow-page" + "/(?P[0-9]+)" + "/(?P[0-9]+)" + "/$", + course.grading.get_prev_grades_dropdown_content, + name="relate-get_prev_grades_dropdown_content"), + url(r"^course" "/" + COURSE_ID_REGEX + "/grading/statistics" @@ -369,6 +379,15 @@ urlpatterns = [ "/$", course.flow.view_flow_page, name="relate-view_flow_page"), + url(r"^course" + "/" + COURSE_ID_REGEX + + "/prev_answers" + "/flow-page" + "/(?P[0-9]+)" + "/(?P[0-9]+)" + "/$", + course.flow.get_prev_answer_visits_dropdown_content, + name="relate-get_prev_answer_visits_dropdown_content"), url(r"^course" "/" + COURSE_ID_REGEX + "/flow-session"