Repository: apluslms/a-plus

Issue:
Counter for manually assessed students in the submitters list view
In the assignment submitters list/table, there is a column "Assessed manually Yes/No". **Add a counter (to the top) that shows how many students have been manually assessed thus far.** For example, "5 students out of 20 have been manually assessed thus far" or in a compact form, "5 / 20 (25%)". 

How to find the assignment submitters table? When you open the submissions list of an assignment, there is a button "Group by submitter" in the top.

Where exactly should the counter be rendered? Ideas:

* Above the table
* In the table column header cell that contains the text "Assessed manually"
* In the inspect submission view since the manual assessment is conducted there in practice.
* elsewhere?

The counter is useful when assignments are manually assessed, for example, in exams.


Discussion:
The counter could also be added to the **inspect submission view**. It would then easily reveal how many students are still left "in the queue" while the course staff are working on the assessments.

(The inspect submission view contains the manual assessment form.)
@PasiSa did you say that hit PR didn't work when you tested it?
I think we would like to see the counter in the following locations:
* inspect submission page
* submitters list for an assignment
* all submissions list for an assignment (the counter still counts the submitters, not individual submissions)

Think of a good location within the pages where to put the counter.

Resolving patch:
diff --git a/exercise/staff_views.py b/exercise/staff_views.py
--- a/exercise/staff_views.py
+++ b/exercise/staff_views.py
@@ -75,13 +75,31 @@ def get_common_objects(self) -> None:
                 Prefetch('submitters', UserProfile.objects.prefetch_tags(self.instance)),
             )
         )
+
+        submitters = (
+            UserProfile.objects
+            .filter(submissions__exercise=self.exercise)
+            .annotate(
+                count_assessed=Count(
+                    'submissions__id',
+                    filter=(Q(submissions__grader__isnull=False)),
+                ),
+            )
+        )
+
+        total_submitters = submitters.count()
+        graded_submitters = submitters.filter(count_assessed__gt=0).count()
+
         for submission in qs:
             format_submission(submission, self.pseudonymize)
         self.all = self.request.GET.get('all', None)
         self.all_url = self.exercise.get_submission_list_url() + "?all=yes"
         self.submissions = qs if self.all else qs[:self.default_limit]
         self.count = len(self.submissions)
-        self.note("all", "all_url", "submissions", "default_limit", "count")
+        self.percentage_graded = (
+            f"{graded_submitters} / {total_submitters} ({int(graded_submitters / total_submitters * 100)}%)"
+        )
+        self.note("all", "all_url", "submissions", "default_limit", "count", "percentage_graded")
 
 
 class SubmissionsSummaryView(ExerciseBaseView):
@@ -353,6 +371,7 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
                 earliest_submission=Min('submissions__submission_time'),
             )
             .order_by('earliest_submission'))
+        total_submitters = submitters.count()
         previous_user_id = request.GET.get('prev')
         if previous_user_id:
             # get the previous time
@@ -363,13 +382,17 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
             submitters = submitters.filter(count_assessed=0)
 
             # Find specifically the submitter who's submission was submitted right after this one
-            submitters_after = submitters.filter(earliest_submission__gt=previous_time)
-            submitter = submitters_after.first()
+            submitters = submitters.filter(earliest_submission__gt=previous_time)
+            submitter = submitters.first()
 
         if not submitter:
             submitters = submitters.filter(count_assessed=0)
             submitter = submitters.first()
 
+        # the number of submitters that we have after filtering
+        submitters_after = submitters.count()
+        filtered_submitters = total_submitters - submitters_after + 1
+
         if not submitter:
             # There are no more unassessed submitters.
             messages.success(request, _('ALL_SUBMITTERS_HAVE_BEEN_ASSESSED'))
@@ -380,6 +403,10 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
         ids = cache.submission_ids(exercise_id=self.exercise.id, best=True, fallback_to_last=True)
         if not ids:
             raise Http404()
+        percentage = f"{int(filtered_submitters / total_submitters * 100)}%"
+        self.request.session['manually_assessed_counter'] = (
+            f"{filtered_submitters} / {total_submitters} ({percentage})"
+        )
         url = reverse(
             'submission-inspect',
             kwargs={'submission_id': ids[0], **kwargs},


Test patch: