Skip to content

Commit 1709561

Browse files
committed
squash!: refactor constants
1 parent 2149087 commit 1709561

3 files changed

Lines changed: 56 additions & 13 deletions

File tree

openedx_authz/rest_api/data.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,19 @@ class RoleOperationError(BaseEnum):
5858
USER_DOES_NOT_HAVE_ROLE = "user_does_not_have_role"
5959
ROLE_ASSIGNMENT_ERROR = "role_assignment_error"
6060
ROLE_REMOVAL_ERROR = "role_removal_error"
61+
62+
63+
class ScopesQuerySetFields(BaseEnum):
64+
"""Enum for the annotated fields used in the Scopes query set for the scopes endpoint"""
65+
66+
SCOPE_ID = "scope_id"
67+
DISPLAY_NAME_COL = "display_name_col"
68+
ORG_NAME = "org_name"
69+
SCOPE_TYPE = "scope_type"
70+
71+
72+
class ScopesTypeField(BaseEnum):
73+
"""Enum for the scope_type query field on the scopes endpoint"""
74+
75+
COURSE = "course"
76+
LIBRARY = "library"

openedx_authz/rest_api/v1/serializers.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from openedx_authz import api
99
from openedx_authz.api.data import UserAssignments
10-
from openedx_authz.rest_api.data import AssignmentSortField, SortField, SortOrder
10+
from openedx_authz.rest_api.data import AssignmentSortField, ScopesTypeField, SortField, SortOrder
1111
from openedx_authz.rest_api.utils import get_generic_scope
1212
from openedx_authz.rest_api.v1.fields import (
1313
CaseSensitiveCommaSeparatedListField,
@@ -227,7 +227,9 @@ class ListScopesQuerySerializer(OrgMixin): # pylint: disable=abstract-method
227227
"""Serializer for validating query parameters in ScopesAPIView."""
228228

229229
management_permission_only = serializers.BooleanField(required=False, default=False)
230-
scope_type = serializers.ChoiceField(choices=["course", "library"], required=False, default=None, allow_null=True)
230+
scope_type = serializers.ChoiceField(
231+
choices=[(e.value, e.name) for e in ScopesTypeField], required=False, default=None, allow_null=True
232+
)
231233
search = serializers.CharField(required=False, default="", allow_blank=True)
232234

233235

@@ -375,7 +377,7 @@ class ScopeSerializer(serializers.Serializer): # pylint: disable=abstract-metho
375377

376378
def get_external_key(self, obj: dict) -> str:
377379
"""Get the external key for the given scope."""
378-
if obj["scope_type"] == "library":
380+
if obj["scope_type"] == ScopesTypeField.LIBRARY:
379381
return str(LibraryLocatorV2(org=obj["org_name"], slug=obj["scope_id"]))
380382
return obj["scope_id"]
381383

openedx_authz/rest_api/v1/views.py

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
from openedx_authz.api.utils import get_user_map
4040
from openedx_authz.constants import permissions
4141
from openedx_authz.models.scopes import get_content_library_model, get_course_overview_model
42-
from openedx_authz.rest_api.data import RoleOperationError, RoleOperationStatus
42+
from openedx_authz.rest_api.data import RoleOperationError, RoleOperationStatus, ScopesQuerySetFields, ScopesTypeField
4343
from openedx_authz.rest_api.decorators import authz_permissions, view_auth_classes
4444
from openedx_authz.rest_api.utils import (
4545
filter_users,
@@ -675,6 +675,14 @@ class ScopesAPIView(generics.ListAPIView):
675675
pagination_class = AuthZAPIViewPagination
676676
permission_classes = [AnyScopePermission]
677677

678+
# Priority for fields used for stable sorting (first has more priority)
679+
ordering_priority = (
680+
ScopesQuerySetFields.ORG_NAME,
681+
ScopesQuerySetFields.SCOPE_TYPE,
682+
ScopesQuerySetFields.DISPLAY_NAME_COL,
683+
ScopesQuerySetFields.SCOPE_ID,
684+
)
685+
678686
def get_serializer_context(self):
679687
context = super().get_serializer_context()
680688
context["org_map"] = Organization.objects.filter(active=True).in_bulk(field_name="short_name")
@@ -711,7 +719,12 @@ def _get_courses_queryset(
711719
display_name_col=Cast("display_name", output_field=CharField(db_collation="utf8mb4_unicode_ci")),
712720
org_name=Cast("org", output_field=CharField(db_collation="utf8mb4_unicode_ci")),
713721
scope_type=Value("course", output_field=CharField(db_collation="utf8mb4_unicode_ci")),
714-
).values("scope_id", "display_name_col", "org_name", "scope_type")
722+
).values(
723+
ScopesQuerySetFields.SCOPE_ID,
724+
ScopesQuerySetFields.DISPLAY_NAME_COL,
725+
ScopesQuerySetFields.ORG_NAME,
726+
ScopesQuerySetFields.SCOPE_TYPE,
727+
)
715728

716729
def _get_libraries_queryset(
717730
self,
@@ -748,19 +761,23 @@ def _get_libraries_queryset(
748761
display_name_col=Cast("learning_package__title", output_field=CharField(db_collation="utf8mb4_unicode_ci")),
749762
org_name=Cast("org__short_name", output_field=CharField(db_collation="utf8mb4_unicode_ci")),
750763
scope_type=Value("library", output_field=CharField(db_collation="utf8mb4_unicode_ci")),
751-
).values("scope_id", "display_name_col", "org_name", "scope_type")
764+
).values(
765+
ScopesQuerySetFields.SCOPE_ID,
766+
ScopesQuerySetFields.DISPLAY_NAME_COL,
767+
ScopesQuerySetFields.ORG_NAME,
768+
ScopesQuerySetFields.SCOPE_TYPE,
769+
)
752770

753771
def _build_queryset(self, courses_qs: QuerySet | None, libraries_qs: QuerySet | None) -> QuerySet:
754772
"""Union the provided querysets and sort deterministically.
755773
756774
Orders by org_name first (satisfying the 'ordered by org' requirement), then by
757775
scope_type, display_name_col, and scope_id as tiebreakers to ensure stable pagination.
758776
"""
759-
ordering = ("org_name", "scope_type", "display_name_col", "scope_id")
760777
if courses_qs is not None and libraries_qs is not None:
761-
return courses_qs.union(libraries_qs).order_by(*ordering)
778+
return courses_qs.union(libraries_qs).order_by(*self.ordering_priority)
762779
qs = courses_qs if courses_qs is not None else libraries_qs
763-
return qs.order_by(*ordering)
780+
return qs.order_by(*self.ordering_priority)
764781

765782
def get_queryset(self) -> QuerySet:
766783
"""Return scopes ordered by org, filtered by the user's permissions."""
@@ -776,8 +793,16 @@ def get_queryset(self) -> QuerySet:
776793
# Staff and superusers can see all scopes, skip permission filtering.
777794
if user.is_staff or user.is_superuser:
778795
return self._build_queryset(
779-
courses_qs=(self._get_courses_queryset(search=search, org=org) if scope_type != "library" else None),
780-
libraries_qs=(self._get_libraries_queryset(search=search, org=org) if scope_type != "course" else None),
796+
courses_qs=(
797+
self._get_courses_queryset(search=search, org=org)
798+
if scope_type != ScopesTypeField.LIBRARY
799+
else None
800+
),
801+
libraries_qs=(
802+
self._get_libraries_queryset(search=search, org=org)
803+
if scope_type != ScopesTypeField.COURSE
804+
else None
805+
),
781806
)
782807

783808
management_only = params_serializer.validated_data["management_permission_only"]
@@ -788,7 +813,7 @@ def get_permission(scope_cls):
788813

789814
# Resolve allowed scopes from Casbin in a single call per scope type.
790815
courses_qs = None
791-
if scope_type != "library":
816+
if scope_type != ScopesTypeField.LIBRARY:
792817
allowed_course_scopes = get_scopes_for_user_and_permission(
793818
user.username, get_permission(CourseOverviewData).identifier
794819
)
@@ -804,7 +829,7 @@ def get_permission(scope_cls):
804829
)
805830

806831
libraries_qs = None
807-
if scope_type != "course":
832+
if scope_type != ScopesTypeField.COURSE:
808833
allowed_library_scopes = get_scopes_for_user_and_permission(
809834
user.username, get_permission(ContentLibraryData).identifier
810835
)

0 commit comments

Comments
 (0)