Repository: learningequality/kolibri

Issue:
Order resume content node requests by last interaction
## Observed behavior
The resume fetches for contentnodes are not ordered by the interaction.

## Expected behavior
The most recently interacted content node should be returned first!



Discussion:
I would like to contribute! Do you mind pointing me in a good starting direction?
@rtibbles please assign me this issue. I like to contribute. I already got two pull request merged #8782 and #8720.

Resolving patch:
diff --git a/kolibri/core/content/api.py b/kolibri/core/content/api.py
--- a/kolibri/core/content/api.py
+++ b/kolibri/core/content/api.py
@@ -29,6 +29,7 @@
 from django_filters.rest_framework import UUIDFilter
 from le_utils.constants import content_kinds
 from le_utils.constants import languages
+from rest_framework import filters
 from rest_framework import mixins
 from rest_framework import status
 from rest_framework import viewsets
@@ -75,7 +76,6 @@
 from kolibri.core.utils.pagination import ValuesViewsetLimitOffsetPagination
 from kolibri.core.utils.pagination import ValuesViewsetPageNumberPagination
 
-
 logger = logging.getLogger(__name__)
 
 
@@ -610,6 +610,7 @@ def get_resume_queryset(request, queryset):
         .exclude(progress=1)
         .values_list("content_id", flat=True)
     )
+
     return queryset.filter(content_id__in=content_ids)
 
 
@@ -1382,10 +1383,28 @@ class UserContentNodeViewset(BaseContentNodeMixin, BaseValuesViewset, ListModelM
     A content node viewset for filtering on user specific fields.
     """
 
-    filter_backends = (DjangoFilterBackend,)
+    filter_backends = (DjangoFilterBackend, filters.OrderingFilter)
+    ordering_fields = ["last_interacted"]
+    ordering = ("lft", "id")
     filter_class = UserContentNodeFilter
     pagination_class = OptionalPagination
 
+    def get_queryset(self):
+        user = self.request.user
+
+        queryset = models.ContentNode.objects.filter(available=True)
+        if not user.is_facility_user:
+            user = None
+
+        queryset = queryset.annotate(
+            last_interacted=Subquery(
+                ContentSummaryLog.objects.filter(
+                    content_id=OuterRef("content_id"), user=user
+                ).values_list("end_timestamp")[:1]
+            )
+        )
+        return queryset
+
 
 def mean(data):
     n = 0
@@ -1401,7 +1420,9 @@ def mean(data):
 class ContentNodeProgressViewset(
     TreeQueryMixin, viewsets.GenericViewSet, ListModelMixin
 ):
-    filter_backends = (DjangoFilterBackend,)
+    filter_backends = (DjangoFilterBackend, filters.OrderingFilter)
+    ordering_fields = ["last_interacted"]
+    ordering = ("lft", "id")
     filter_class = UserContentNodeFilter
     # Use same pagination class as ContentNodeViewset so we can
     # return identically paginated responses.
@@ -1412,7 +1433,20 @@ class ContentNodeProgressViewset(
     pagination_class = OptionalPagination
 
     def get_queryset(self):
-        return models.ContentNode.objects.filter(available=True)
+        user = self.request.user
+
+        queryset = models.ContentNode.objects.filter(available=True)
+        if not user.is_facility_user:
+            user = None
+
+        queryset = queryset.annotate(
+            last_interacted=Subquery(
+                ContentSummaryLog.objects.filter(
+                    content_id=OuterRef("content_id"), user=user
+                ).values_list("end_timestamp")[:1]
+            )
+        )
+        return queryset
 
     def generate_response(self, request, queryset):
         if request.user.is_anonymous():
diff --git a/kolibri/core/logger/api.py b/kolibri/core/logger/api.py
--- a/kolibri/core/logger/api.py
+++ b/kolibri/core/logger/api.py
@@ -114,7 +114,6 @@ def validate(self, data):
 # in a Django IntegerField across all backends
 MIN_INTEGER = -2147483648
 
-
 attemptlog_fields = [
     "id",
     "correct",
@@ -359,7 +358,6 @@ def create(self, request):
         )
 
         with transaction.atomic(), dataset_cache:
-
             user = None if request.user.is_anonymous() else request.user
 
             self._precache_dataset_id(user)
diff --git a/kolibri/plugins/learn/viewsets.py b/kolibri/plugins/learn/viewsets.py
--- a/kolibri/plugins/learn/viewsets.py
+++ b/kolibri/plugins/learn/viewsets.py
@@ -226,11 +226,17 @@ def get(self, request, format=None):
             classrooms = learner_classroom_viewset.serialize_list(request)
             if not classrooms or not any(_resumable_resources(classrooms)):
                 resumable_resources = user_contentnode_viewset.serialize_list(
-                    request, {"resume": True, "max_results": 12}
+                    request,
+                    {"resume": True, "max_results": 12, "ordering": "-last_interacted"},
                 )
                 resumable_resources_progress = (
                     contentnode_progress_viewset.serialize_list(
-                        request, {"resume": True, "max_results": 12}
+                        request,
+                        {
+                            "resume": True,
+                            "max_results": 12,
+                            "ordering": "-last_interacted",
+                        },
                     )
                 )
 


Test patch: