From c625a8d38759d84978649b1e445d5eea513ec4c2 Mon Sep 17 00:00:00 2001 From: Yusuf Musleh Date: Tue, 2 Jul 2024 17:51:00 +0300 Subject: [PATCH] [FC-0059] feat: Add `order` query param to lib v2 API (#35005) * feat: Add `order` query param to lib v2 API * test: Add tests for lib v2 `order` field --- .../core/djangoapps/content_libraries/api.py | 19 +++++++++++++++-- .../content_libraries/serializers.py | 1 + .../content_libraries/tests/base.py | 7 +++++++ .../tests/test_content_libraries.py | 21 +++++++++++++++++++ .../djangoapps/content_libraries/views.py | 9 ++++++++ 5 files changed, 55 insertions(+), 2 deletions(-) diff --git a/openedx/core/djangoapps/content_libraries/api.py b/openedx/core/djangoapps/content_libraries/api.py index 82d648eaeba1..3800fdb7f4f4 100644 --- a/openedx/core/djangoapps/content_libraries/api.py +++ b/openedx/core/djangoapps/content_libraries/api.py @@ -242,7 +242,7 @@ class LibraryXBlockType: # ============ -def get_libraries_for_user(user, org=None, library_type=None, text_search=None): +def get_libraries_for_user(user, org=None, library_type=None, text_search=None, order=None): """ Return content libraries that the user has permission to view. """ @@ -263,7 +263,22 @@ def get_libraries_for_user(user, org=None, library_type=None, text_search=None): Q(learning_package__description__icontains=text_search) ) - return permissions.perms[permissions.CAN_VIEW_THIS_CONTENT_LIBRARY].filter(user, qs) + filtered = permissions.perms[permissions.CAN_VIEW_THIS_CONTENT_LIBRARY].filter(user, qs) + + if order: + order_query = 'learning_package__' + valid_order_fields = ['title', 'created', 'updated'] + # If order starts with a -, that means order descending (default is ascending) + if order.startswith('-'): + order_query = f"-{order_query}" + order = order[1:] + + if order in valid_order_fields: + return filtered.order_by(f"{order_query}{order}") + else: + log.exception(f"Error ordering libraries by {order}: Invalid order field") + + return filtered def get_metadata(queryset, text_search=None): diff --git a/openedx/core/djangoapps/content_libraries/serializers.py b/openedx/core/djangoapps/content_libraries/serializers.py index ee27e4464365..89c6b611a822 100644 --- a/openedx/core/djangoapps/content_libraries/serializers.py +++ b/openedx/core/djangoapps/content_libraries/serializers.py @@ -115,6 +115,7 @@ class BaseFilterSerializer(serializers.Serializer): """ text_search = serializers.CharField(default=None, required=False) org = serializers.CharField(default=None, required=False) + order = serializers.CharField(default=None, required=False) class ContentLibraryFilterSerializer(BaseFilterSerializer): diff --git a/openedx/core/djangoapps/content_libraries/tests/base.py b/openedx/core/djangoapps/content_libraries/tests/base.py index ef6cc180f19a..40fe3ba94956 100644 --- a/openedx/core/djangoapps/content_libraries/tests/base.py +++ b/openedx/core/djangoapps/content_libraries/tests/base.py @@ -89,6 +89,13 @@ def assertDictContainsEntries(self, big_dict, subset_dict): """ assert big_dict.items() >= subset_dict.items() + def assertOrderEqual(self, libraries_list, expected_order): + """ + Assert that the provided list of libraries match the order of expected + list by comparing the slugs. + """ + assert [lib["slug"] for lib in libraries_list] == expected_order + # API helpers def _api(self, method, url, data, expect_response): diff --git a/openedx/core/djangoapps/content_libraries/tests/test_content_libraries.py b/openedx/core/djangoapps/content_libraries/tests/test_content_libraries.py index 06588c13f99d..576d506f9d89 100644 --- a/openedx/core/djangoapps/content_libraries/tests/test_content_libraries.py +++ b/openedx/core/djangoapps/content_libraries/tests/test_content_libraries.py @@ -230,6 +230,27 @@ def test_library_filters(self): 'text_search': 'library-title-4'})) == 1 assert len(self._list_libraries({'type': VIDEO})) == 3 + self.assertOrderEqual( + self._list_libraries({'order': 'title'}), + ["test-lib-filter-1", "test-lib-filter-2", "l3", "l4", "l5"], + ) + self.assertOrderEqual( + self._list_libraries({'order': '-title'}), + ["l5", "l4", "l3", "test-lib-filter-2", "test-lib-filter-1"], + ) + self.assertOrderEqual( + self._list_libraries({'order': 'created'}), + ["test-lib-filter-1", "test-lib-filter-2", "l3", "l4", "l5"], + ) + self.assertOrderEqual( + self._list_libraries({'order': '-created'}), + ["l5", "l4", "l3", "test-lib-filter-2", "test-lib-filter-1"], + ) + # An invalid order doesn't apply any specific ordering to the result, so just + # check if successfully returned libraries + assert len(self._list_libraries({'order': 'invalid'})) == 5 + assert len(self._list_libraries({'order': '-invalid'})) == 5 + # General Content Library XBlock tests: def test_library_blocks(self): diff --git a/openedx/core/djangoapps/content_libraries/views.py b/openedx/core/djangoapps/content_libraries/views.py index fba8d42689cb..810c99f223db 100644 --- a/openedx/core/djangoapps/content_libraries/views.py +++ b/openedx/core/djangoapps/content_libraries/views.py @@ -196,6 +196,13 @@ class LibraryRootView(GenericAPIView): str, description="The string used to filter libraries by searching in title, id, org, or description", ), + apidocs.query_parameter( + 'order', + str, + description=( + "Name of the content library field to sort the results by. Prefix with a '-' to sort descending." + ), + ), ], ) def get(self, request): @@ -207,12 +214,14 @@ def get(self, request): org = serializer.validated_data['org'] library_type = serializer.validated_data['type'] text_search = serializer.validated_data['text_search'] + order = serializer.validated_data['order'] queryset = api.get_libraries_for_user( request.user, org=org, library_type=library_type, text_search=text_search, + order=order, ) paginated_qs = self.paginate_queryset(queryset) result = api.get_metadata(paginated_qs)