diff --git a/kolibri/core/auth/api.py b/kolibri/core/auth/api.py index 4bef4681f3..4eeb714d42 100644 --- a/kolibri/core/auth/api.py +++ b/kolibri/core/auth/api.py @@ -72,6 +72,7 @@ from kolibri.core import error_constants from kolibri.core.api import ReadOnlyValuesViewset from kolibri.core.api import ValuesViewset +from kolibri.core.api import ValuesViewsetOrderingFilter from kolibri.core.auth.constants.demographics import NOT_SPECIFIED from kolibri.core.auth.permissions.general import _user_is_admin_for_own_facility from kolibri.core.auth.permissions.general import DenyAll @@ -90,7 +91,6 @@ from kolibri.core.utils.pagination import ValuesViewsetPageNumberPagination from kolibri.plugins.app.utils import interface - logger = logging.getLogger(__name__) @@ -394,6 +394,7 @@ class FacilityUserViewSet(ValuesViewset): KolibriAuthPermissionsFilter, DjangoFilterBackend, filters.SearchFilter, + ValuesViewsetOrderingFilter, ) order_by_field = "username" @@ -415,6 +416,16 @@ class FacilityUserViewSet(ValuesViewset): "gender", "birth_year", "extra_demographics", + "date_joined", + ) + + ordering_fields = ( + "id", + "username", + "full_name", + "gender", + "birth_year", + "date_joined", ) field_map = { @@ -424,6 +435,8 @@ class FacilityUserViewSet(ValuesViewset): def consolidate(self, items, queryset): output = [] items = sorted(items, key=lambda x: x["id"]) + ordering_param = self.request.query_params.get("ordering", self.order_by_field) + reverse = False for key, group in groupby(items, lambda x: x["id"]): roles = [] for item in group: @@ -438,7 +451,11 @@ def consolidate(self, items, queryset): roles.append(role) item["roles"] = roles output.append(item) - output = sorted(output, key=lambda x: x[self.order_by_field]) + if ordering_param.startswith("-"): + ordering_param = ordering_param[1:] + reverse = True + + output = sorted(output, key=lambda x: x[ordering_param], reverse=reverse) return output def perform_update(self, serializer): diff --git a/kolibri/core/auth/test/test_api.py b/kolibri/core/auth/test/test_api.py index d7562f9aba..1c77e3d423 100644 --- a/kolibri/core/auth/test/test_api.py +++ b/kolibri/core/auth/test/test_api.py @@ -3,6 +3,7 @@ import time import uuid from datetime import datetime +from datetime import timedelta from importlib import import_module import factory @@ -1083,6 +1084,7 @@ def test_user_list(self): "id_number": self.user.id_number, "gender": self.user.gender, "birth_year": self.user.birth_year, + "date_joined": self.user.date_joined, "is_superuser": False, "roles": [], "extra_demographics": None, @@ -1094,6 +1096,7 @@ def test_user_list(self): "facility": self.superuser.facility_id, "id_number": self.superuser.id_number, "gender": self.superuser.gender, + "date_joined": self.superuser.date_joined, "birth_year": self.superuser.birth_year, "is_superuser": True, "roles": [ @@ -1127,6 +1130,7 @@ def test_user_list_self(self): "id_number": self.user.id_number, "gender": self.user.gender, "birth_year": self.user.birth_year, + "date_joined": self.user.date_joined, "is_superuser": False, "roles": [], "extra_demographics": None, @@ -1170,6 +1174,103 @@ def test_anonymous_no_retrieve_user(self): self.assertEqual(response.status_code, 404) +class FacilityUserOrderingTestCase(APITestCase): + @classmethod + def setUpTestData(cls): + provision_device() + cls.facility = FacilityFactory.create() + cls.superuser = create_superuser(cls.facility) + cls.facility.add_admin(cls.superuser) + + base_time = datetime.now() - timedelta(days=3) + cls.user1 = FacilityUserFactory.create( + facility=cls.facility, username="mario", date_joined=base_time + ) + cls.user2 = FacilityUserFactory.create( + facility=cls.facility, + username="luigi", + date_joined=base_time + timedelta(days=1), + ) + cls.user3 = FacilityUserFactory.create( + facility=cls.facility, + username="batman", + date_joined=base_time + timedelta(days=4), + ) + + def setUp(self): + self.client.login( + username=self.superuser.username, + password=DUMMY_PASSWORD, + facility=self.facility, + ) + + def _sort_by_field(self, data, field, reverse=False): + return sorted(data, key=lambda x: x[field], reverse=reverse) + + def test_default_ordering(self): + response = self.client.get(reverse("kolibri:core:facilityuser-list")) + self.assertEqual(response.status_code, 200) + data = response.data + self.assertEqual(data[0]["username"], "batman") + sorted_data = self._sort_by_field(data, "username") + self.assertEqual(data, sorted_data) + + def test_ordering_by_username(self): + response = self.client.get( + reverse("kolibri:core:facilityuser-list") + "?ordering=username" + ) + self.assertEqual(response.status_code, 200) + data = response.data + sorted_data = self._sort_by_field(data, "username") + self.assertEqual(data, sorted_data) + + def test_ordering_by_username_desc(self): + response = self.client.get( + reverse("kolibri:core:facilityuser-list") + "?ordering=-username" + ) + self.assertEqual(response.status_code, 200) + data = response.data + self.assertEqual(data[0]["username"], "superuser") + sorted_data = self._sort_by_field(data, "username", reverse=True) + self.assertEqual(data, sorted_data) + + def test_ordering_by_date_joined(self): + response = self.client.get( + reverse("kolibri:core:facilityuser-list") + "?ordering=date_joined" + ) + self.assertEqual(response.status_code, 200) + data = response.data + sorted_data = self._sort_by_field(data, "date_joined") + self.assertEqual(data, sorted_data) + + def test_ordering_by_date_joined_desc(self): + response = self.client.get( + reverse("kolibri:core:facilityuser-list") + "?ordering=-date_joined" + ) + self.assertEqual(response.status_code, 200) + data = response.data + sorted_data = self._sort_by_field(data, "date_joined", reverse=True) + self.assertEqual(data, sorted_data) + + def test_ordering_by_full_name(self): + response = self.client.get( + reverse("kolibri:core:facilityuser-list") + "?ordering=full_name" + ) + self.assertEqual(response.status_code, 200) + data = response.data + sorted_data = self._sort_by_field(data, "full_name") + self.assertEqual(data, sorted_data) + + def test_ordering_by_full_name_desc(self): + response = self.client.get( + reverse("kolibri:core:facilityuser-list") + "?ordering=-full_name" + ) + self.assertEqual(response.status_code, 200) + data = response.data + sorted_data = self._sort_by_field(data, "full_name", reverse=True) + self.assertEqual(data, sorted_data) + + class FacilityUserFilterTestCase(APITestCase): @classmethod def setUpTestData(cls):