-
Notifications
You must be signed in to change notification settings - Fork 81
Add submision conversion feature for individual submission and by module #991
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| from itertools import groupby | ||
| from lib2to3.pytree import convert | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't this import unused? Remove this line. |
||
| from typing import Any, Dict, Iterable, List, Optional, Tuple, Type | ||
|
|
||
| from django.db import models | ||
|
|
@@ -8,6 +9,10 @@ | |
| from django.shortcuts import get_object_or_404 | ||
| from django.utils.text import format_lazy | ||
| from django.utils.translation import ugettext_lazy as _, ngettext | ||
| from django.utils import timezone | ||
| from django.utils.dateparse import parse_datetime | ||
| import datetime | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The correct coding style is to place standard imports (built-in Python modules) before the third-party libraries. You can move |
||
|
|
||
|
|
||
| from course.models import CourseModule, UserTag | ||
| from course.viewbase import CourseInstanceMixin, CourseInstanceBaseView | ||
|
|
@@ -16,6 +21,9 @@ | |
| from authorization.permissions import ACCESS | ||
| from exercise.models import BaseExercise | ||
| from userprofile.models import UserProfile | ||
| from exercise.submission_models import Submission | ||
|
|
||
| from .forms import DeadlineRuleDeviationForm, MaxSubmissionRuleDeviationForm | ||
|
|
||
|
|
||
| class ListDeviationsView(CourseInstanceBaseView): | ||
|
|
@@ -48,7 +56,12 @@ def form_valid(self, form: forms.BaseForm) -> HttpResponse: | |
| exercise__in=exercises, | ||
| submitter__in=submitters, | ||
| ) | ||
| if isinstance(form, DeadlineRuleDeviationForm ) and not form.cleaned_data.get("without_late_submission_approval"): | ||
| approve_late_submissions(submitters, exercises, form.cleaned_data) | ||
|
|
||
| elif isinstance(form, MaxSubmissionRuleDeviationForm ) and not form.cleaned_data.get("without_unofficial_submission_approval"): | ||
| approve_unofficial_submissions(submitters, exercises, form.cleaned_data) | ||
|
|
||
|
Comment on lines
+59
to
+64
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| if existing_deviations: | ||
| # Some deviations already existed. Use OverrideDeviationsView to | ||
| # confirm which ones the user wants to override. Store the form | ||
|
|
@@ -297,3 +310,36 @@ def get_submitters(form_data: Dict[str, Any]) -> models.QuerySet[UserProfile]: | |
| models.Q(id__in=form_data.get('submitter', [])) | ||
| | models.Q(taggings__tag__in=form_data.get('submitter_tag', [])) | ||
| ) | ||
|
|
||
| def approve_late_submissions(submitters, exercises, form_data): | ||
| minutes = form_data.get('minutes') | ||
| new_date = form_data.get('new_date') | ||
| for submitter in submitters: | ||
| for exercise in exercises: | ||
| submissions = exercise.get_submissions_for_student(submitter, exclude_errors=True) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These database queries could be made more efficient. Now, this fetches also normal, graded submissions that couldn't be approved. There could be many such submissions, but they are not needed here. Note that late submissions do not always have any late penalty and thus, it is not enough to check submissions with If the teacher adds deviations to many exercises and students at once, then making database queries separately for each exercise and student pair might be needlessly slow. Making hundreds of database queries for a single HTTP request is never good. It is possible to fetch more data in a single query. |
||
| for submission in submissions: | ||
| new_deadline = None | ||
| if new_date: | ||
| string_date = str(new_date)[:16] | ||
| new_deadline = timezone.make_aware( | ||
| parse_datetime(string_date), | ||
| timezone.get_current_timezone()) | ||
| else: | ||
| new_deadline = submission.submission_time + datetime.timedelta(minutes=minutes) | ||
|
|
||
| if submission.late_penalty_applied is not None and submission.submission_time <= new_deadline: | ||
| submission.convert_penalized_submission() | ||
| submission.save() | ||
|
|
||
| def approve_unofficial_submissions(submitters, exercises, form_data): | ||
| extra_submissions = form_data.get('extra_submissions') | ||
| for submitter in submitters: | ||
| for exercise in exercises: | ||
| converted_counter = extra_submissions | ||
| submissions_unordered = exercise.get_submissions_for_student(submitter, exclude_errors=True) | ||
| submissions = [submission for submission in reversed(submissions_unordered)] #reverse the order to get older submission first | ||
| for submission in submissions: | ||
| if submission.status == Submission.STATUS.UNOFFICIAL and converted_counter > 0: | ||
| submission.convert_penalized_submission() | ||
| submission.save() | ||
| converted_counter -= 1 | ||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -19,12 +19,14 @@ | |||||||||
| from authorization.permissions import ACCESS | ||||||||||
| from course.viewbase import CourseInstanceBaseView, CourseInstanceMixin | ||||||||||
| from course.models import ( | ||||||||||
| CourseModule, | ||||||||||
| Enrollment, | ||||||||||
| USERTAG_EXTERNAL, | ||||||||||
| USERTAG_INTERNAL, | ||||||||||
| ) | ||||||||||
| from deviations.models import MaxSubmissionsRuleDeviation | ||||||||||
| from exercise.cache.points import CachedPoints | ||||||||||
| from exercise.exercise_models import BaseExercise | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||||
| from lib.helpers import settings_text, extract_form_errors | ||||||||||
| from lib.viewbase import BaseRedirectView, BaseFormView, BaseView | ||||||||||
| from notification.models import Notification | ||||||||||
|
|
@@ -530,3 +532,73 @@ def form_valid(self, form): | |||||||||
| def form_invalid(self, form): | ||||||||||
| messages.error(self.request, _('FAILURE_SAVING_CHANGES')) | ||||||||||
| return super().form_invalid(form) | ||||||||||
|
|
||||||||||
| class SubmissionConversionView(SubmissionMixin, BaseRedirectView): | ||||||||||
| """ | ||||||||||
| A POST-only view that updates a student's late or unofficial submission | ||||||||||
| to normal submission. Changed the status and remove the penalty | ||||||||||
| """ | ||||||||||
| def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this missing access_mode = ACCESS.ASSISTANT?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, i'm not sure, but if you think i should add that then i could do that
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, it should be |
||||||||||
| self.submission = self.get_submission_object() | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The super classes already defined |
||||||||||
| self.submission.convert_penalized_submission() | ||||||||||
| self.submission.save() | ||||||||||
| return self.redirect(self.submission.get_inspect_url()) | ||||||||||
|
|
||||||||||
|
|
||||||||||
| class SubmissionConversionByModuleView(CourseInstanceMixin, BaseRedirectView): | ||||||||||
| """ | ||||||||||
| A POST-only view that by module bulks updates a student's late or unofficial submission | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. typo bulks |
||||||||||
| to normal submission. Changed the status and remove the penalty | ||||||||||
| """ | ||||||||||
|
|
||||||||||
| user_kw = 'user_id' | ||||||||||
| module_kw = 'module_id' | ||||||||||
| access_mode = ACCESS.ASSISTANT | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need to also check |
||||||||||
|
|
||||||||||
| def get_course_module_object(self): | ||||||||||
| return get_object_or_404( | ||||||||||
| CourseModule, | ||||||||||
| id=self.kwargs[self.module_kw], | ||||||||||
| course_instance=self.instance | ||||||||||
| ) | ||||||||||
|
Comment on lines
+558
to
+563
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This method isn't used anywhere in this class. Fetching the course module in |
||||||||||
|
|
||||||||||
| def get_resource_objects(self): | ||||||||||
| self.kwargs[self.user_kw] = self.request.POST[self.user_kw], | ||||||||||
| self.kwargs[self.user_kw] = self.kwargs[self.user_kw][0] | ||||||||||
|
Comment on lines
+566
to
+567
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The trailing comma should be removed from the first line and then, the second line is not needed. The comma turns the value into a tuple.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||||
| self.kwargs[self.module_kw] = self.request.POST[self.module_kw] | ||||||||||
| super().get_resource_objects() | ||||||||||
|
|
||||||||||
| #getting module and user by id. | ||||||||||
| self.module = get_object_or_404( | ||||||||||
| CourseModule, | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bad indentation. |
||||||||||
| id=self.kwargs[self.module_kw]) | ||||||||||
|
|
||||||||||
| self.student = get_object_or_404( | ||||||||||
| User, | ||||||||||
| id=self.kwargs[self.user_kw], | ||||||||||
| ) | ||||||||||
|
|
||||||||||
| def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: | ||||||||||
| approve_scope = self.request.POST["approve-scope"] | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||||
| approve_type = self.request.POST["approve-type"] | ||||||||||
| is_scope_exercise = approve_scope == "single-exercise" | ||||||||||
| exercise_id = self.request.POST["exercise_id"] | ||||||||||
| is_late = False if approve_type == "isUnofficial" else True | ||||||||||
| is_unofficial = False if approve_type == "isLate" else True | ||||||||||
|
Comment on lines
+586
to
+587
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
|
||||||||||
| profile = self.student.userprofile | ||||||||||
| exercises = [] | ||||||||||
| if is_scope_exercise: | ||||||||||
| exercises = BaseExercise.objects.filter(id=exercise_id) | ||||||||||
| else: | ||||||||||
| exercises = BaseExercise.objects.filter(course_module=self.module) | ||||||||||
|
|
||||||||||
| for exercise in exercises: | ||||||||||
| submissions = exercise.get_submissions_for_student(self.student.userprofile, exclude_errors=True) | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The database queries can be made more efficient. It is not necessary to iterate over all the normal submissions. Note that if a course module does not allow late submissions, but unofficial submissions are allowed in the exercise category, then late submissions become unofficial without any late penalty. Though, since the user interface has choices "late only" or "unofficial only", then "late only" should imply that it does not pick any unofficial submissions. Note that unofficial submissions may have late penalty set when the module allows late submissions. |
||||||||||
| for submission in submissions: | ||||||||||
| if ((is_unofficial and submission.status == Submission.STATUS.UNOFFICIAL) or (is_late and submission.late_penalty_applied is not None)): | ||||||||||
| submission.convert_penalized_submission() | ||||||||||
| submission.save() | ||||||||||
|
|
||||||||||
| link = reverse('user-results', kwargs={'user_id': profile.id, **self.instance.get_url_kwargs()}) | ||||||||||
| return self.redirect(link) | ||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -406,6 +406,16 @@ def clean_post_parameters(self): | |
| del self._files | ||
| del self._data | ||
|
|
||
| def convert_penalized_submission(self): | ||
| """ | ||
| Removes penalty and Sets the points for this submission object based on original score. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will rephrase this docstring. The official PEP Python styleguides require the imperative mood ("Remove the penalty"). |
||
| Then this will set the status of submission to ready | ||
|
|
||
| The method is used to convert late or unofficial submission to fully graded one. | ||
| """ | ||
| self.set_points(self.service_points, self.service_max_points, no_penalties=True) | ||
| self.set_ready(convert_operation=True) | ||
|
|
||
| def set_points(self, points, max_points, no_penalties=False): | ||
| """ | ||
| Sets the points and maximum points for this submissions. If the given | ||
|
|
@@ -467,9 +477,9 @@ def scale_grade_to(self, percentage): | |
| def set_waiting(self): | ||
| self.status = self.STATUS.WAITING | ||
|
|
||
| def set_ready(self): | ||
| def set_ready(self, convert_operation=False): | ||
| self.grading_time = timezone.now() | ||
| if self.status != self.STATUS.UNOFFICIAL or self.force_exercise_points: | ||
| if (self.status != self.STATUS.UNOFFICIAL or self.force_exercise_points) or convert_operation: | ||
| self.status = self.STATUS.READY | ||
|
|
||
| # Fire set hooks. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -85,6 +85,11 @@ <h3 class="panel-title"> | |
|
|
||
| {% points_progress module %} | ||
| {{ module.introduction|safe }} | ||
|
|
||
| {% if module.late_allowed and module.late_percent > 0 %} | ||
|
|
||
| {% endif %} | ||
|
|
||
|
Comment on lines
+88
to
+92
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this empty if here? |
||
| </div> | ||
| {% if not exercise_accessible and not is_course_staff %} | ||
| <div class="alert alert-warning clearfix site-message"> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,7 +19,24 @@ | |
| {% endif %} | ||
| </div> | ||
| <div> | ||
| <button | ||
| <form method="post" style="display: inline-block"> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This form is unnecessary. You have other form elements inside the modal dialog. |
||
| {% csrf_token %} | ||
| <button | ||
| data-toggle="modal" | ||
| data-target="#submission-approval-modal" | ||
| class="aplus-button--secondary aplus-button--sm aplus-button--left" | ||
| type="button" | ||
| > | ||
| {% translate 'APPROVE_SUBMISSION' %} | ||
| </button><button | ||
| data-toggle="modal" | ||
| data-target="#approval-help-modal" | ||
| class="aplus-button--secondary aplus-button--sm aplus-button--right" | ||
| type="button" | ||
| title="{% translate 'HELP' %}" | ||
| >?</button> | ||
|
Comment on lines
+31
to
+37
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The separate help modal dialog is unnecessary. The help text can be moved to the approval modal. |
||
| </form> | ||
| <button | ||
| data-toggle="modal" | ||
| data-target="#details-modal" | ||
| class="aplus-button--secondary aplus-button--sm" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| {% load i18n %} | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's delete this file since the help text can be moved to the actual approval modal. |
||
| {% load course %} | ||
|
|
||
| <div class="modal" id="approval-help-modal" tabindex="-1" role="dialog" aria-labelledby="approval-help-modal-label"> | ||
| <div class="modal-dialog" role="document"> | ||
| <div class="modal-content"> | ||
| <div class="modal-header"> | ||
| <button type="button" class="close" data-dismiss="modal" aria-label="{% translate "CLOSE" %}"> | ||
| <span aria-hidden="true">×</span> | ||
| </button> | ||
| <h4 class="modal-title" id="approval-help-modal-label"> | ||
| {% translate "SUBMISSION_APPROVAL_HELP" %} | ||
| </h4> | ||
| </div> | ||
|
|
||
| <div class="modal-body"> | ||
| {% translate "SUBMISSION_APPROVAL_DESCRIPTION" %} | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| {% load i18n %} | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Our style guide expects indentation with TAB in HTML files. This file has mixed tab and space indentations. |
||
| {% load course %} | ||
|
|
||
| <div class="modal" id="submission-approval-modal" tabindex="-1" role="dialog" aria-labelledby="resubmit-modal-label"> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| <div class="modal-dialog" role="document"> | ||
| <div class="modal-content"> | ||
| <div class="modal-header"> | ||
| <button type="button" class="close" data-dismiss="modal" aria-label="{% translate "CLOSE" %}"> | ||
| <span aria-hidden="true">×</span> | ||
| </button> | ||
| <h4 class="modal-title" id="resubmit-modal-label"> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This id should match |
||
| {% translate 'LATE_SUBMISSION_APPROVAL_CONFIRMATION_HEADER' %} | ||
| </h4> | ||
| </div> | ||
|
|
||
| <div class="modal-body"> | ||
| <p>{% translate 'LATE_SUBMISSION_APPROVAL_CONFIRMATION_TEXT' %}</p> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sometimes the translated messages have keys like "LATE_SUBMISSION" even though they do not apply to only late submissions. Unofficial submissions are included too. |
||
| <p>{{submission.submitters.all.0.name_with_student_id}}</p> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The inspect view already adds a template variable |
||
| <dl> | ||
| <dt>{% translate 'LATE_SUBMISSION_APPROVAL_CONFIRMATION_TEXT_DETAIL' %}</dt> | ||
| <dd>Module: {{ module.name|parse_localization }}</dd> | ||
| <dd>Exercise: {{ exercise.name|parse_localization }}</dd> | ||
| <dd>Submission ID & Time: {{ submission.id }} - {{ submission.submission_time }}</dd> | ||
| </dl> | ||
| <br> | ||
| <button onclick="OpenMultipleConversionform();" name = "approve-multiple" class="aplus-button--secondary aplus-button--xs">{% translate 'LATE_SUBMISSION_APPROVAL_APPROVE_MULTIPLE_BUTTON' %}</button> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| <br> | ||
| <br> | ||
| <form method="post" id="convert-module-form" action="{% url 'submission-conversion-module' course_slug=course.url instance_slug=instance.url %}" style="display: none"> | ||
| {% csrf_token %} | ||
| <input type="hidden" id="module_id" name="module_id" value="{{ module.id }}"> | ||
| <input type="hidden" id="user_id" name="user_id" value="{{ submission.submitters.all.0.id }}"> | ||
| <input type="hidden" id="exercise_id" name="exercise_id" value="{{ exercise.id }}"> | ||
| <p>{% translate 'LATE_SUBMISSION_APPROVAL_APPROVE_MULTIPLE_SELECT_SCOPE' %}</p> | ||
| <input type="radio" id="single-exercise" name="approve-scope" value="single-exercise"> | ||
| <label for="single-exercise">{% translate 'LATE_SUBMISSION_APPROVAL_APPROVE_MULTIPLE_SCOPE_EXERCISE' %}</label><br> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The label's for attribute is not needed if the input element is wrapped inside the label element. |
||
| <input type="radio" id="whole-module" name="approve-scope" value="whole-module"> | ||
| <label for="whole-module">{% translate 'LATE_SUBMISSION_APPROVAL_APPROVE_MULTIPLE_SCOPE_MODULE' %}</label><br> | ||
|
|
||
| <br><p>{% translate 'LATE_SUBMISSION_APPROVAL_APPROVE_MULTIPLE_SELECT_TYPE' %}</p> | ||
| <input type="radio" id="isLate" name="approve-type" value="isLate"> | ||
| <label for="isLate"> {% translate 'LATE_SUBMISSION_APPROVAL_APPROVE_MULTIPLE_TYPE_LATE' %}</label><br> | ||
| <input type="radio" id="isUnofficial" name="approve-type" value="isUnofficial"> | ||
| <label for="isUnofficial"> {% translate 'LATE_SUBMISSION_APPROVAL_APPROVE_MULTIPLE_TYPE_UNOFFICIAL' %}</label><br> | ||
| <input type="radio" id="isAll" name="approve-type" value="isAll"> | ||
| <label for="isAll"> {% translate 'LATE_SUBMISSION_APPROVAL_APPROVE_MULTIPLE_TYPE_ALL' %}</label><br> | ||
| <button | ||
| class="aplus-button--default aplus-button--sm" | ||
| type="submit" | ||
| > | ||
| {% translate 'APPROVE_MULTIPLE_SUBMISSIONS' %} | ||
| </button> | ||
| </form> | ||
| <form method="post" id="convert-singular-form" action="{{ submission|url:'submission-conversion' }}" style="display: inline-block"> | ||
| {% csrf_token %} | ||
| <button | ||
| class="aplus-button--default aplus-button--sm" | ||
| type="submit" | ||
| > | ||
| {% translate 'APPROVE_SUBMISSION' %} | ||
| </button> | ||
| </form> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| <script> | ||
| function OpenMultipleConversionform(){ | ||
| moduleform = document.getElementById('convert-module-form') | ||
| singularform = document.getElementById('convert-singular-form') | ||
|
Comment on lines
+69
to
+70
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In JavaScript, if you don't declare variables with |
||
| if (moduleform.style.display === "none") { | ||
| document.getElementById('convert-module-form').style.display = 'inline-block'; | ||
| document.getElementById('convert-singular-form').style.display = 'none'; | ||
| } else { | ||
| document.getElementById('convert-module-form').style.display = 'none'; | ||
| document.getElementById('convert-singular-form').style.display = 'inline-block'; | ||
| } | ||
| } | ||
| </script> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -97,6 +97,8 @@ | |
| </div> | ||
| {% include "exercise/staff/_submission_data_modal.html" %} | ||
| {% include "exercise/staff/_resubmit_modal.html" %} | ||
| {% include "exercise/staff/_late_submission_approval_modal.html" %} | ||
| {% include "exercise/staff/_late_submission_approval_help_modal.html" %} | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The separate help modal is deleted since the help text can be moved to the actual approval modal. |
||
| {% endblock %} | ||
|
|
||
| {% block scripts %} | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -571,6 +571,13 @@ def test_submission_late_penalty_applied(self): | |
| deviation.save() | ||
| self.late_late_submission_when_late_allowed.set_points(5, 10) | ||
| self.assertAlmostEqual(self.late_late_submission_when_late_allowed.late_penalty_applied, 0.2) | ||
|
|
||
| def test_submission_late_conversion(self): | ||
| convert_submission_url = self.late_submission.get_url('submission-conversion') | ||
| response = self.client.get(convert_submission_url) | ||
| self.assertEqual(response.status_code, 302) | ||
| self.assertTrue(self.late_submission.late_penalty_applied is None) | ||
|
|
||
|
Comment on lines
+575
to
+580
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would make sense to add more tests than just this. For example, there could be unit tests that directly call the method |
||
|
|
||
| def test_early_submission(self): | ||
| self.course_module_with_late_submissions_allowed.opening_time = self.tomorrow | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would name this field without negation: "approve_late_submissions", initial True. Negation is harder to understand for the users whereas enabling something is clear.