Repository: apluslms/a-plus

Issue:
Bypass (or improve) the slow-loading DL deviations page
For me, loading O1 2022’s "List deadline deviations" page takes about 105 seconds: 65 before anything visible happens and another 40 before the page has fully loaded. It’s not a nice page to navigate to, but (at least in principle) one has to do that pretty often in order to access "Add deadline deviations". 

The ideal fix would be to improve loading times a lot. Failing that (or even if load times are improved), it would be nice to have a faster route to adding deviations; there’s generally no need to browse the current list first.

-------------------------------------------------------------------------

Another issue #1302 focuses on improving the performance of the deviations list view.


Discussion:
I realize it’s possible to use bookmarks and custom A+ menu items to circumvent this.

Closely related: #1123  
We already tried to improve the loading times in the past. The loading times did improve, but they can still be too slow. In this issue, we could simply add the requested shortcut links that help the teacher avoid the slow-loading deviation list page.

In the left-side course menu, we could add a subheading "Deviations" and move all the deviation links under that section. We already have two menu links for the deadline and submission attempt deviation lists. We could add two new links that point directly to the "add deviation" forms.

HTML template for the course menu:
https://github.com/apluslms/a-plus/blob/7f21d5ad5b20d7dc4d02c2bb7601720527d07c77/course/templates/course/_course_menu.html#L190-L200
The links on the points page in #1123 can trivially be also added to the course page as well. Would that solve the issue or would it clutter the UI too much?
> The links on the points page in #1123 can trivially be also added to the course page as well. Would that solve the issue or would it clutter the UI too much?

@lainets I don't understand your question. This issue #1122 and #1123 are related to different parts of the user interface. Both are useful.
As far as I understand, this issue is about improving or bypassing the list deadline deviations page and the exact location of the button(s) for bypassing it hasn't been specified. My question was whether it would be enough to add buttons to the main course page, just like #1123 adds them to the points page. The points page and main course page use the same template (except if the mode is changed by the course but the buttons can be added to the other templates as well), so adding the buttons to the course page as well is trivial (in fact, I had to specifically omit them from the course page in the proposed solution to #1123).
@jsorva should comment on that. 

1. Do we need assignment-specific shortcut links to the "add deviations" form in the course front page and/or "Your points" page? (There is no student pre-selected in these cases.)

2. Do we also need shortcut links to the "add deviations" form in the left-side course menu (navigation panel)? (No assignments nor students pre-selected in this case.)
It’s not hundred-percent certain to me what "main course page" refers so, but anyway: 
In practice, AFAIC, solving #1123 in the way @lainets suggests solves 90%+ of the problem in practice anyway, so this almost becomes a non-issue. 

As for @Mankro’s questions, I’d say: 1) No. 2) If I understand you correctly, you mean the main menu where we now have "Deadline Deviations", among other things. If so, I’d say: no, not necessarily, if #1123 is fixed as suggested. 
Based on jsorva's comment, #1123 is important and then this issue #1122 becomes unnecessary.
@Mankro I would consider reopening this issue after all (and despite my own comments above). 

It’s true that #1123 bypasses the main problem, which is great. That update has been massively useful.

However, parts of the problem remain:
* The deviations list page still loads extremely slowly and more often than not crashes with a server error. (The list is not needed often, but when it is, it’s painful to try to access it.)
* When adding a deadline deviation, one is automatically taken to the deviations list. This basically renders one’s browser tab unusable (for the above reason). 
    * I don’t personally care much where I’m taken after adding a deviation. The deviations list page would be OK if it loaded faster. But if it doesn’t then I’d rather be taken somewhere else.
* When adding a deadline deviation, one is automatically taken to the deviations list. Given the poor performance of that page, this unnecessarily strains the server (I presume). Moreover, it strains the server at a bad time (since deadline deviations are typically added near the deadline, when the server is busy).
OK, we can re-open this issue and consider adding new shortcut links as has been previously discussed in this issue. Though, your concerns are really about the slow performance of the deviation list page and that is a different problem than adding shortcut links.

There could also be a new "deviations home page" that does not list any deviations from the database, but includes links to the deviation features like adding new. This home page would be a sensible redirect location after adding new deviations if we want to not redirect to the list view. Otherwise, the home page would be rather pointless so it's not the greatest of ideas. Maybe the user could be redirected to the "add new deviation" form again after submitting the same form.

One approach to the deviation list performance problem is to use pagination so that only 100 records are loaded per page. Then, a search feature would be needed to find students without browsing through all the deviation list pages.
I made a new issue #1302 that focuses on performance issues in the deviations list. This issue #1122 can focus on bypassing the list view.

Resolving patch:
diff --git a/deviations/viewbase.py b/deviations/viewbase.py
--- a/deviations/viewbase.py
+++ b/deviations/viewbase.py
@@ -77,7 +77,8 @@ def form_valid(self, form: forms.BaseForm) -> HttpResponse:
             self.success_url = self.deviation_model.get_override_url(self.instance)
             self.request.session[self.session_key] = self.serialize_session_data(form.cleaned_data)
         else:
-            self.success_url = self.deviation_model.get_list_url(self.instance)
+            self.success_url = self.get_success_no_override_url()
+
             for exercise in exercises:
                 for submitter in submitters:
                     new_deviation = self.deviation_model(
@@ -87,7 +88,7 @@ def form_valid(self, form: forms.BaseForm) -> HttpResponse:
                     )
                     new_deviation.update_by_form(form.cleaned_data)
                     new_deviation.save()
-
+            messages.success(self.request, _("SUCCESS_ADDING_DEVIATIONS"))
         return super().form_valid(form)
 
     def serialize_session_data(self, form_data: Dict[str, Any]) -> Dict[str, Any]:
@@ -110,9 +111,6 @@ class OverrideDeviationsView(CourseInstanceMixin, BaseFormView):
     deviation_model: Type[SubmissionRuleDeviation]
     session_key: str
 
-    def get_success_url(self) -> str:
-        return self.deviation_model.get_list_url(self.instance)
-
     def get_common_objects(self) -> None:
         super().get_common_objects()
         self.session_data = self.deserialize_session_data(self.request.session[self.session_key])
@@ -162,6 +160,7 @@ def form_valid(self, form: forms.BaseForm) -> HttpResponse:
                     new_deviation.save()
 
         del self.request.session[self.session_key]
+        messages.success(self.request, _("SUCCESS_OVERRIDING_DEVIATIONS"))
         return super().form_valid(form)
 
     def deserialize_session_data(self, session_data: Dict[str, Any]) -> Dict[str, Any]:
@@ -203,7 +202,7 @@ def get_form_kwargs(self) -> Dict[str, Any]:
         return kwargs
 
     def get_success_url(self) -> str:
-        return self.deviation_model.get_list_url(self.instance)
+        return self.instance.get_url('deviations-remove-dl')
 
     def form_valid(self, form: forms.BaseForm) -> HttpResponse:
         number_of_removed = 0
diff --git a/deviations/views.py b/deviations/views.py
--- a/deviations/views.py
+++ b/deviations/views.py
@@ -28,6 +28,9 @@ class AddDeadlinesView(AddDeviationsView):
     deviation_model = DeadlineRuleDeviation
     session_key = 'add-deviations-data-dl'
 
+    def get_success_no_override_url(self) -> str:
+        return self.instance.get_url('deviations-add-dl')
+
     def get_initial_get_param_spec(self) -> Dict[str, Optional[Callable[[str], Any]]]:
         spec = super().get_initial_get_param_spec()
         spec.update({
@@ -52,6 +55,9 @@ class OverrideDeadlinesView(OverrideDeviationsView):
     deviation_model = DeadlineRuleDeviation
     session_key = 'add-deviations-data-dl'
 
+    def get_success_url(self) -> str:
+        return self.instance.get_url('deviations-add-dl')
+
     def deserialize_session_data(self, session_data: Dict[str, Any]) -> Dict[str, Any]:
         result = super().deserialize_session_data(session_data)
         result.update({
@@ -83,6 +89,9 @@ class AddSubmissionsView(AddDeviationsView):
     deviation_model = MaxSubmissionsRuleDeviation
     session_key = 'add-deviations-data-submissions'
 
+    def get_success_no_override_url(self) -> str:
+        return self.instance.get_url('deviations-add-submissions')
+
     def serialize_session_data(self, form_data: Dict[str, Any]) -> Dict[str, Any]:
         result = super().serialize_session_data(form_data)
         result['extra_submissions'] = form_data['extra_submissions']
@@ -94,6 +103,9 @@ class OverrideSubmissionsView(OverrideDeviationsView):
     deviation_model = MaxSubmissionsRuleDeviation
     session_key = 'add-deviations-data-submissions'
 
+    def get_success_url(self) -> str:
+        return self.instance.get_url('deviations-add-submissions')
+
     def deserialize_session_data(self, session_data: Dict[str, Any]) -> Dict[str, Any]:
         result = super().deserialize_session_data(session_data)
         result['extra_submissions'] = session_data['extra_submissions']


Test patch:
diff --git a/deviations/tests.py b/deviations/tests.py
--- a/deviations/tests.py
+++ b/deviations/tests.py
@@ -369,7 +369,6 @@ def test_get_deviation_groups(self):
 
     def test_add_deadline_deviations(self):
         self.client.login(username="staff", password="staffPassword")
-        list_deadline_deviations_url = self.course_instance.get_url("deviations-list-dl")
         add_deadline_deviations_url = self.course_instance.get_url("deviations-add-dl")
 
         # Module and user tag provided
@@ -382,7 +381,7 @@ def test_add_deadline_deviations(self):
             }
         )
         self.assertEqual(response.status_code, 302)
-        self.assertEqual(response.url, list_deadline_deviations_url)
+        self.assertEqual(response.url, add_deadline_deviations_url)
 
         deviation = DeadlineRuleDeviation.objects.get(
             exercise=self.module_2_exercise_1,
@@ -418,7 +417,7 @@ def test_add_deadline_deviations(self):
             }
         )
         self.assertEqual(response.status_code, 302)
-        self.assertEqual(response.url, list_deadline_deviations_url)
+        self.assertEqual(response.url, add_deadline_deviations_url)
 
         deviation = DeadlineRuleDeviation.objects.get(
             exercise=self.module_2_exercise_1,
@@ -456,7 +455,7 @@ def test_add_deadline_deviations(self):
             }
         )
         self.assertEqual(response.status_code, 302)
-        self.assertEqual(response.url, list_deadline_deviations_url)
+        self.assertEqual(response.url, add_deadline_deviations_url)
 
         deviation = DeadlineRuleDeviation.objects.get(
             exercise=self.module_2_exercise_1,
@@ -490,7 +489,6 @@ def test_add_deadline_deviations(self):
 
     def test_override_deadline_deviations(self):
         self.client.login(username="staff", password="staffPassword")
-        list_deadline_deviations_url = self.course_instance.get_url("deviations-list-dl")
         add_deadline_deviations_url = self.course_instance.get_url("deviations-add-dl")
         override_deadline_deviations_url = self.course_instance.get_url("deviations-override-dl")
 
@@ -504,7 +502,7 @@ def test_override_deadline_deviations(self):
             }
         )
         self.assertEqual(response.status_code, 302)
-        self.assertEqual(response.url, list_deadline_deviations_url)
+        self.assertEqual(response.url, add_deadline_deviations_url)
 
         self.assertEqual(
             DeadlineRuleDeviation.objects.get(
@@ -547,7 +545,7 @@ def test_override_deadline_deviations(self):
             }
         )
         self.assertEqual(response.status_code, 302)
-        self.assertEqual(response.url, list_deadline_deviations_url)
+        self.assertEqual(response.url, add_deadline_deviations_url)
 
         self.assertEqual(
             DeadlineRuleDeviation.objects.get(
@@ -583,7 +581,7 @@ def test_override_deadline_deviations(self):
             }
         )
         self.assertEqual(response.status_code, 302)
-        self.assertEqual(response.url, list_deadline_deviations_url)
+        self.assertEqual(response.url, add_deadline_deviations_url)
 
         self.assertEqual(
             DeadlineRuleDeviation.objects.get(
@@ -604,7 +602,6 @@ def test_override_deadline_deviations(self):
 
     def test_remove_deadline_deviations(self):
         self.client.login(username="staff", password="staffPassword")
-        list_deadline_deviations_url = self.course_instance.get_url("deviations-list-dl")
         add_deadline_deviations_url = self.course_instance.get_url("deviations-add-dl")
         remove_deadline_deviations_url = self.course_instance.get_url("deviations-remove-dl")
 
@@ -618,7 +615,7 @@ def test_remove_deadline_deviations(self):
             }
         )
         self.assertEqual(response.status_code, 302)
-        self.assertEqual(response.url, list_deadline_deviations_url)
+        self.assertEqual(response.url, add_deadline_deviations_url)
 
         self.assertEqual(
             DeadlineRuleDeviation.objects.get(
@@ -644,7 +641,7 @@ def test_remove_deadline_deviations(self):
             }
         )
         self.assertEqual(response.status_code, 302)
-        self.assertEqual(response.url, list_deadline_deviations_url)
+        self.assertEqual(response.url, remove_deadline_deviations_url)
 
         with self.assertRaises(DeadlineRuleDeviation.DoesNotExist):
             DeadlineRuleDeviation.objects.get(
@@ -661,7 +658,7 @@ def test_remove_deadline_deviations(self):
             }
         )
         self.assertEqual(response.status_code, 302)
-        self.assertEqual(response.url, list_deadline_deviations_url)
+        self.assertEqual(response.url, remove_deadline_deviations_url)
 
         with self.assertRaises(DeadlineRuleDeviation.DoesNotExist):
             DeadlineRuleDeviation.objects.get(