Skip to content

Commit f5e5636

Browse files
committed
squash!: Change type to scope_type, add org filtering
1 parent ea7b52c commit f5e5636

4 files changed

Lines changed: 229 additions & 40 deletions

File tree

openedx_authz/rest_api/v1/serializers.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,12 @@ class OrderMixin(serializers.Serializer): # pylint: disable=abstract-method
5151
)
5252

5353

54+
class OrgMixin(serializers.Serializer): # pylint: disable=abstract-method
55+
"""Mixin providing org field functionality."""
56+
57+
org = serializers.CharField(required=False, max_length=255)
58+
59+
5460
class PermissionValidationSerializer(ActionMixin, ScopeMixin): # pylint: disable=abstract-method
5561
"""Serializer for permission validation request."""
5662

@@ -217,11 +223,11 @@ def get_roles(self, obj: api.RoleAssignmentData) -> list[str]:
217223
return [role.external_key for role in obj.roles]
218224

219225

220-
class ListScopesSerializer(serializers.Serializer): # pylint: disable=abstract-method
221-
"""Serializer for validating query parameters in AdminConsoleScopesAPIView."""
226+
class ListScopesQuerySerializer(OrgMixin): # pylint: disable=abstract-method
227+
"""Serializer for validating query parameters in ScopesAPIView."""
222228

223229
management_permission_only = serializers.BooleanField(required=False, default=False)
224-
type = serializers.ChoiceField(choices=["course", "library"], required=False, default=None, allow_null=True)
230+
scope_type = serializers.ChoiceField(choices=["course", "library"], required=False, default=None, allow_null=True)
225231
search = serializers.CharField(required=False, default="", allow_blank=True)
226232

227233

openedx_authz/rest_api/v1/urls.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,5 @@
1818
path(
1919
"users/<str:username>/assignments/", views.TeamMemberAssignmentsAPIView.as_view(), name="user-assignment-list"
2020
),
21-
path("scopes/", views.AdminConsoleScopesAPIView.as_view(), name="scope-list"),
21+
path("scopes/", views.ScopesAPIView.as_view(), name="scope-list"),
2222
]

openedx_authz/rest_api/v1/views.py

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
ListRolesWithScopeResponseSerializer,
5959
ListRolesWithScopeSerializer,
6060
ListTeamMemberAssignmentsQuerySerializer,
61-
ListScopesSerializer,
61+
ListScopesQuerySerializer,
6262
ListTeamMembersSerializer,
6363
ListUsersInRoleWithScopeSerializer,
6464
PermissionValidationResponseSerializer,
@@ -587,6 +587,7 @@ def get_queryset(self) -> QuerySet:
587587
"get",
588588
parameters=[
589589
apidocs.query_parameter("search", str, description="Filter scopes by display name"),
590+
apidocs.query_parameter("org", str, description="Filter scopes by org"),
590591
apidocs.query_parameter("page", int, description="Page number for pagination"),
591592
apidocs.query_parameter("page_size", int, description="Number of items per page"),
592593
apidocs.query_parameter(
@@ -598,18 +599,19 @@ def get_queryset(self) -> QuerySet:
598599
),
599600
),
600601
apidocs.query_parameter(
601-
"type",
602+
"scope_type",
602603
str,
603604
description="Filter by scope type. Either 'course' or 'library'. Returns both if not specified.",
604605
),
605606
],
606607
responses={
607608
status.HTTP_200_OK: ScopeSerializer(many=True),
608609
status.HTTP_400_BAD_REQUEST: "The request parameters are invalid",
609-
status.HTTP_401_UNAUTHORIZED: "The user is not authenticated or does not have the required permissions",
610+
status.HTTP_401_UNAUTHORIZED: "The user is not authenticated",
611+
status.HTTP_403_FORBIDDEN: "The user does not have the required permisisons",
610612
},
611613
)
612-
class AdminConsoleScopesAPIView(generics.ListAPIView):
614+
class ScopesAPIView(generics.ListAPIView):
613615
"""
614616
API view for listing scopes
615617
This API is used on the filters and assign roles functionality on the Admin Console.
@@ -621,9 +623,10 @@ class AdminConsoleScopesAPIView(generics.ListAPIView):
621623
**Query Parameters**
622624
623625
- search (Optional): Search term to filter scopes by display name
626+
- org (Optional): Filter scopes by org
624627
- page (Optional): Page number for pagination
625628
- page_size (Optional): Number of items per page
626-
- type (Optional): Filter scopes by type. Supported values are `course` and `library`.
629+
- scope_type (Optional): Filter scopes by type. Supported values are `course` and `library`.
627630
- management_permission_only (Optional): Filter scopes either by only the ones to which the user has "manage team"
628631
permissions (if true), or just "view team" permissions.
629632
@@ -678,12 +681,17 @@ def get_serializer_context(self):
678681
return context
679682

680683
def _get_courses_queryset(
681-
self, allowed_ids: set | None = None, allowed_orgs: set | None = None, search: str = ""
684+
self,
685+
allowed_ids: set | None = None,
686+
allowed_orgs: set | None = None,
687+
search: str = "",
688+
org: str = "",
682689
) -> QuerySet:
683690
"""Return a CourseOverview queryset projected to the unified scope shape.
684691
685692
If allowed_ids and/or allowed_orgs are provided, filter to matching courses.
686693
If search is provided, filter by display_name.
694+
If org is provided, filter by org short_name.
687695
"""
688696
qs = CourseOverview.objects
689697
if allowed_ids is not None or allowed_orgs is not None:
@@ -694,6 +702,8 @@ def _get_courses_queryset(
694702
qs = qs.none()
695703
else:
696704
qs = qs.filter(combined_filter)
705+
if org:
706+
qs = qs.filter(org=org)
697707
if search:
698708
qs = qs.filter(display_name__icontains=search)
699709
return qs.annotate(
@@ -704,12 +714,17 @@ def _get_courses_queryset(
704714
).values("scope_id", "display_name_col", "org_name", "scope_type")
705715

706716
def _get_libraries_queryset(
707-
self, allowed_pairs: set | None = None, allowed_orgs: set | None = None, search: str = ""
717+
self,
718+
allowed_pairs: set | None = None,
719+
allowed_orgs: set | None = None,
720+
search: str = "",
721+
org: str = "",
708722
) -> QuerySet:
709723
"""Return a ContentLibrary queryset projected to the unified scope shape.
710724
711725
If allowed_pairs and/or allowed_orgs are provided, filter to matching libraries.
712726
If search is provided, filter by learning_package__title.
727+
If org is provided, filter by org short_name.
713728
"""
714729
qs = ContentLibrary.objects
715730
if allowed_pairs is not None or allowed_orgs is not None:
@@ -724,6 +739,8 @@ def _get_libraries_queryset(
724739
qs = qs.none()
725740
else:
726741
qs = qs.filter(combined)
742+
if org:
743+
qs = qs.filter(org__short_name=org)
727744
if search:
728745
qs = qs.filter(learning_package__title__icontains=search)
729746
return qs.annotate(
@@ -750,16 +767,17 @@ def get_queryset(self) -> QuerySet:
750767
user = self.request.user
751768

752769
# Validate and parse query parameters.
753-
params_serializer = ListScopesSerializer(data=self.request.query_params)
770+
params_serializer = ListScopesQuerySerializer(data=self.request.query_params)
754771
params_serializer.is_valid(raise_exception=True)
755-
scope_type = params_serializer.validated_data["type"]
772+
scope_type = params_serializer.validated_data["scope_type"]
756773
search = params_serializer.validated_data["search"]
774+
org = params_serializer.validated_data.get("org", "")
757775

758776
# Staff and superusers can see all scopes, skip permission filtering.
759777
if user.is_staff or user.is_superuser:
760778
return self._build_queryset(
761-
courses_qs=self._get_courses_queryset(search=search) if scope_type != "library" else None,
762-
libraries_qs=self._get_libraries_queryset(search=search) if scope_type != "course" else None,
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),
763781
)
764782

765783
management_only = params_serializer.validated_data["management_permission_only"]
@@ -778,7 +796,12 @@ def get_permission(scope_cls):
778796
s.external_key for s in allowed_course_scopes if not isinstance(s, OrgCourseOverviewGlobData)
779797
}
780798
allowed_course_orgs = {s.org for s in allowed_course_scopes if isinstance(s, OrgCourseOverviewGlobData)}
781-
courses_qs = self._get_courses_queryset(allowed_course_ids, allowed_course_orgs, search=search)
799+
courses_qs = self._get_courses_queryset(
800+
allowed_course_ids,
801+
allowed_course_orgs,
802+
search=search,
803+
org=org,
804+
)
782805

783806
libraries_qs = None
784807
if scope_type != "course":
@@ -792,7 +815,12 @@ def get_permission(scope_cls):
792815
if not isinstance(s, OrgContentLibraryGlobData)
793816
}
794817
allowed_library_orgs = {s.org for s in allowed_library_scopes if isinstance(s, OrgContentLibraryGlobData)}
795-
libraries_qs = self._get_libraries_queryset(allowed_library_pairs, allowed_library_orgs, search=search)
818+
libraries_qs = self._get_libraries_queryset(
819+
allowed_library_pairs,
820+
allowed_library_orgs,
821+
search=search,
822+
org=org,
823+
)
796824

797825
# Union the requested querysets and sort by org at the DB level.
798826
return self._build_queryset(courses_qs, libraries_qs)

0 commit comments

Comments
 (0)