Skip to content

Commit bebd0fb

Browse files
committed
feat: implement sorting functionality for user retrieval in API views
1 parent edca9e8 commit bebd0fb

4 files changed

Lines changed: 71 additions & 16 deletions

File tree

openedx_authz/rest_api/enums.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
"""Enums for the Open edX AuthZ REST API."""
2+
3+
from enum import Enum
4+
5+
6+
class BaseEnum(str, Enum):
7+
"""Base enum class."""
8+
9+
@classmethod
10+
def values(cls):
11+
"""List the values of the enum."""
12+
return [e.value for e in cls]
13+
14+
15+
class SortField(BaseEnum):
16+
"""Enum for the fields to sort by."""
17+
18+
USERNAME = "username"
19+
FULL_NAME = "full_name"
20+
EMAIL = "email"
21+
22+
23+
class SortOrder(BaseEnum):
24+
"""Enum for the order to sort by."""
25+
26+
ASC = "asc"
27+
DESC = "desc"

openedx_authz/rest_api/utils.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
from edx_rest_framework_extensions.auth.session.authentication import SessionAuthenticationAllowInactiveUser
77
from rest_framework.permissions import IsAuthenticated
88

9+
from openedx_authz.rest_api.enums import SortField, SortOrder
10+
911
User = get_user_model()
1012

1113

@@ -48,3 +50,31 @@ def get_user_by_username_or_email(username_or_email: str) -> User:
4850
if hasattr(user, "userretirementrequest"):
4951
raise User.DoesNotExist
5052
return user
53+
54+
55+
def sort_users(
56+
users: list[dict], sort_by: SortField = SortField.USERNAME, order: SortOrder = SortOrder.ASC
57+
) -> list[dict]:
58+
"""
59+
Sort users by a given field and order.
60+
61+
Args:
62+
users (list[dict]): The users to sort.
63+
sort_by (SortField, optional): The field to sort by. Defaults to SortField.USERNAME.
64+
order (SortOrder, optional): The order to sort by. Defaults to SortOrder.ASC.
65+
66+
Raises:
67+
ValueError: If the sort field is invalid.
68+
ValueError: If the sort order is invalid.
69+
70+
Returns:
71+
list[dict]: The sorted users.
72+
"""
73+
if sort_by not in SortField.values():
74+
raise ValueError(f"Invalid field: '{sort_by}'. Must be one of {SortField.values()}")
75+
76+
if order not in SortOrder.values():
77+
raise ValueError(f"Invalid order: '{order}'. Must be one of {SortOrder.values()}")
78+
79+
sorted_users = sorted(users, key=lambda user: (user[sort_by] or "").lower(), reverse=order == SortOrder.DESC)
80+
return sorted_users

openedx_authz/rest_api/v1/serializers.py

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,10 @@
22

33
from rest_framework import serializers
44

5+
from openedx_authz.rest_api.enums import SortField, SortOrder
56
from openedx_authz.rest_api.utils import get_user_by_username_or_email
67
from openedx_authz.rest_api.v1.fields import CommaSeparatedListField
78

8-
__all__ = [
9-
"PermissionValidationSerializer",
10-
"PermissionValidationResponseSerializer",
11-
"AddUserToRoleWithScopeSerializer",
12-
"RemoveUserFromRoleWithScopeSerializer",
13-
"ListUsersInRoleWithScopeSerializer",
14-
"ListRolesWithScopeSerializer",
15-
"ListUsersInRoleWithScopeResponseSerializer",
16-
"ListRolesWithScopeResponseSerializer",
17-
"RoleAssignmentSerializer",
18-
]
19-
209

2110
class ScopeMixin(serializers.Serializer): # pylint: disable=abstract-method
2211
"""Mixin providing scope field functionality."""
@@ -62,6 +51,8 @@ class ListUsersInRoleWithScopeSerializer(ScopeMixin): # pylint: disable=abstrac
6251
"""Serializer for listing users in a role with a scope."""
6352

6453
roles = CommaSeparatedListField(required=False)
54+
sort_by = serializers.ChoiceField(required=False, choices=[(e.value, e.name) for e in SortField])
55+
order = serializers.ChoiceField(required=False, choices=[(e.value, e.name) for e in SortOrder])
6556

6657

6758
class ListRolesWithScopeSerializer(ScopeMixin): # pylint: disable=abstract-method

openedx_authz/rest_api/v1/views.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
from rest_framework.views import APIView
1616

1717
from openedx_authz import api
18-
from openedx_authz.rest_api.utils import get_user_by_username_or_email, view_auth_classes
18+
from openedx_authz.rest_api.enums import SortField, SortOrder
19+
from openedx_authz.rest_api.utils import get_user_by_username_or_email, sort_users, view_auth_classes
1920
from openedx_authz.rest_api.v1.paginators import AuthZAPIViewPagination
2021
from openedx_authz.rest_api.v1.serializers import (
2122
AddUserToRoleWithScopeSerializer,
@@ -92,6 +93,8 @@ class RoleUserAPIView(APIView):
9293
apidocs.query_parameter("scope", str, description="The authorization scope for the role"),
9394
apidocs.query_parameter("page", int, description="Page number for pagination"),
9495
apidocs.query_parameter("page_size", int, description="Number of items per page"),
96+
apidocs.query_parameter("sort_by", str, description="The field to sort by"),
97+
apidocs.query_parameter("order", str, description="The order to sort by"),
9598
],
9699
responses={
97100
status.HTTP_200_OK: "The users were retrieved successfully",
@@ -101,16 +104,20 @@ class RoleUserAPIView(APIView):
101104
)
102105
def get(self, request: HttpRequest) -> Response:
103106
"""Retrieve all users with role assignments within a specific scope."""
104-
# TODO: Filter by role
105107
serializer = ListUsersInRoleWithScopeSerializer(data=request.query_params)
106108
serializer.is_valid(raise_exception=True)
107109
user_role_assignments = api.get_all_user_role_assignments_in_scope_v2(serializer.validated_data["scope"])
108110

109111
paginator = self.pagination_class()
110112
paginated_assignments = paginator.paginate_queryset(user_role_assignments, request)
111113

112-
serializer = RoleAssignmentSerializer(paginated_assignments, many=True)
113-
return paginator.get_paginated_response(serializer.data)
114+
response_serializer = RoleAssignmentSerializer(paginated_assignments, many=True)
115+
116+
sort_by = serializer.validated_data.get("sort_by", SortField.USERNAME)
117+
order = serializer.validated_data.get("order", SortOrder.ASC)
118+
user_role_assignments = sort_users(response_serializer.data, sort_by, order)
119+
120+
return paginator.get_paginated_response(user_role_assignments)
114121

115122
@apidocs.schema(
116123
body=AddUserToRoleWithScopeSerializer,

0 commit comments

Comments
 (0)