Skip to content

Commit 2b01f04

Browse files
committed
squash!: Change type to scope_type, add org filtering
1 parent 3939e46 commit 2b01f04

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
@@ -36,6 +36,12 @@ class ActionMixin(serializers.Serializer): # pylint: disable=abstract-method
3636
action = serializers.CharField(max_length=255)
3737

3838

39+
class OrgMixin(serializers.Serializer): # pylint: disable=abstract-method
40+
"""Mixin providing org field functionality."""
41+
42+
org = serializers.CharField(required=False, max_length=255)
43+
44+
3945
class PermissionValidationSerializer(ActionMixin, ScopeMixin): # pylint: disable=abstract-method
4046
"""Serializer for permission validation request."""
4147

@@ -212,11 +218,11 @@ def get_roles(self, obj: api.RoleAssignmentData) -> list[str]:
212218
return [role.external_key for role in obj.roles]
213219

214220

215-
class ListScopesSerializer(serializers.Serializer): # pylint: disable=abstract-method
216-
"""Serializer for validating query parameters in AdminConsoleScopesAPIView."""
221+
class ListScopesQuerySerializer(OrgMixin): # pylint: disable=abstract-method
222+
"""Serializer for validating query parameters in ScopesAPIView."""
217223

218224
management_permission_only = serializers.BooleanField(required=False, default=False)
219-
type = serializers.ChoiceField(choices=["course", "library"], required=False, default=None, allow_null=True)
225+
scope_type = serializers.ChoiceField(choices=["course", "library"], required=False, default=None, allow_null=True)
220226
search = serializers.CharField(required=False, default="", allow_blank=True)
221227

222228

openedx_authz/rest_api/v1/urls.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@
1515
path("orgs/", views.AdminConsoleOrgsAPIView.as_view(), name="orgs-list"),
1616
path("users/", views.TeamMembersAPIView.as_view(), name="user-list"),
1717
path("users/validate/", views.UserValidationAPIView.as_view(), name="user-validation"),
18-
path("scopes/", views.AdminConsoleScopesAPIView.as_view(), name="scope-list"),
18+
path("scopes/", views.ScopesAPIView.as_view(), name="scope-list"),
1919
]

openedx_authz/rest_api/v1/views.py

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
AddUsersToRoleWithScopeSerializer,
4848
ListRolesWithScopeResponseSerializer,
4949
ListRolesWithScopeSerializer,
50-
ListScopesSerializer,
50+
ListScopesQuerySerializer,
5151
ListTeamMembersSerializer,
5252
ListUsersInRoleWithScopeSerializer,
5353
PermissionValidationResponseSerializer,
@@ -575,6 +575,7 @@ def get_queryset(self) -> QuerySet:
575575
"get",
576576
parameters=[
577577
apidocs.query_parameter("search", str, description="Filter scopes by display name"),
578+
apidocs.query_parameter("org", str, description="Filter scopes by org"),
578579
apidocs.query_parameter("page", int, description="Page number for pagination"),
579580
apidocs.query_parameter("page_size", int, description="Number of items per page"),
580581
apidocs.query_parameter(
@@ -586,18 +587,19 @@ def get_queryset(self) -> QuerySet:
586587
),
587588
),
588589
apidocs.query_parameter(
589-
"type",
590+
"scope_type",
590591
str,
591592
description="Filter by scope type. Either 'course' or 'library'. Returns both if not specified.",
592593
),
593594
],
594595
responses={
595596
status.HTTP_200_OK: ScopeSerializer(many=True),
596597
status.HTTP_400_BAD_REQUEST: "The request parameters are invalid",
597-
status.HTTP_401_UNAUTHORIZED: "The user is not authenticated or does not have the required permissions",
598+
status.HTTP_401_UNAUTHORIZED: "The user is not authenticated",
599+
status.HTTP_403_FORBIDDEN: "The user does not have the required permisisons",
598600
},
599601
)
600-
class AdminConsoleScopesAPIView(generics.ListAPIView):
602+
class ScopesAPIView(generics.ListAPIView):
601603
"""
602604
API view for listing scopes
603605
This API is used on the filters and assign roles functionality on the Admin Console.
@@ -609,9 +611,10 @@ class AdminConsoleScopesAPIView(generics.ListAPIView):
609611
**Query Parameters**
610612
611613
- search (Optional): Search term to filter scopes by display name
614+
- org (Optional): Filter scopes by org
612615
- page (Optional): Page number for pagination
613616
- page_size (Optional): Number of items per page
614-
- type (Optional): Filter scopes by type. Supported values are `course` and `library`.
617+
- scope_type (Optional): Filter scopes by type. Supported values are `course` and `library`.
615618
- management_permission_only (Optional): Filter scopes either by only the ones to which the user has "manage team"
616619
permissions (if true), or just "view team" permissions.
617620
@@ -666,12 +669,17 @@ def get_serializer_context(self):
666669
return context
667670

668671
def _get_courses_queryset(
669-
self, allowed_ids: set | None = None, allowed_orgs: set | None = None, search: str = ""
672+
self,
673+
allowed_ids: set | None = None,
674+
allowed_orgs: set | None = None,
675+
search: str = "",
676+
org: str = "",
670677
) -> QuerySet:
671678
"""Return a CourseOverview queryset projected to the unified scope shape.
672679
673680
If allowed_ids and/or allowed_orgs are provided, filter to matching courses.
674681
If search is provided, filter by display_name.
682+
If org is provided, filter by org short_name.
675683
"""
676684
qs = CourseOverview.objects
677685
if allowed_ids is not None or allowed_orgs is not None:
@@ -682,6 +690,8 @@ def _get_courses_queryset(
682690
qs = qs.none()
683691
else:
684692
qs = qs.filter(combined_filter)
693+
if org:
694+
qs = qs.filter(org=org)
685695
if search:
686696
qs = qs.filter(display_name__icontains=search)
687697
return qs.annotate(
@@ -692,12 +702,17 @@ def _get_courses_queryset(
692702
).values("scope_id", "display_name_col", "org_name", "scope_type")
693703

694704
def _get_libraries_queryset(
695-
self, allowed_pairs: set | None = None, allowed_orgs: set | None = None, search: str = ""
705+
self,
706+
allowed_pairs: set | None = None,
707+
allowed_orgs: set | None = None,
708+
search: str = "",
709+
org: str = "",
696710
) -> QuerySet:
697711
"""Return a ContentLibrary queryset projected to the unified scope shape.
698712
699713
If allowed_pairs and/or allowed_orgs are provided, filter to matching libraries.
700714
If search is provided, filter by learning_package__title.
715+
If org is provided, filter by org short_name.
701716
"""
702717
qs = ContentLibrary.objects
703718
if allowed_pairs is not None or allowed_orgs is not None:
@@ -712,6 +727,8 @@ def _get_libraries_queryset(
712727
qs = qs.none()
713728
else:
714729
qs = qs.filter(combined)
730+
if org:
731+
qs = qs.filter(org__short_name=org)
715732
if search:
716733
qs = qs.filter(learning_package__title__icontains=search)
717734
return qs.annotate(
@@ -738,16 +755,17 @@ def get_queryset(self) -> QuerySet:
738755
user = self.request.user
739756

740757
# Validate and parse query parameters.
741-
params_serializer = ListScopesSerializer(data=self.request.query_params)
758+
params_serializer = ListScopesQuerySerializer(data=self.request.query_params)
742759
params_serializer.is_valid(raise_exception=True)
743-
scope_type = params_serializer.validated_data["type"]
760+
scope_type = params_serializer.validated_data["scope_type"]
744761
search = params_serializer.validated_data["search"]
762+
org = params_serializer.validated_data.get("org", "")
745763

746764
# Staff and superusers can see all scopes, skip permission filtering.
747765
if user.is_staff or user.is_superuser:
748766
return self._build_queryset(
749-
courses_qs=self._get_courses_queryset(search=search) if scope_type != "library" else None,
750-
libraries_qs=self._get_libraries_queryset(search=search) if scope_type != "course" else None,
767+
courses_qs=(self._get_courses_queryset(search=search, org=org) if scope_type != "library" else None),
768+
libraries_qs=(self._get_libraries_queryset(search=search, org=org) if scope_type != "course" else None),
751769
)
752770

753771
management_only = params_serializer.validated_data["management_permission_only"]
@@ -766,7 +784,12 @@ def get_permission(scope_cls):
766784
s.external_key for s in allowed_course_scopes if not isinstance(s, OrgCourseOverviewGlobData)
767785
}
768786
allowed_course_orgs = {s.org for s in allowed_course_scopes if isinstance(s, OrgCourseOverviewGlobData)}
769-
courses_qs = self._get_courses_queryset(allowed_course_ids, allowed_course_orgs, search=search)
787+
courses_qs = self._get_courses_queryset(
788+
allowed_course_ids,
789+
allowed_course_orgs,
790+
search=search,
791+
org=org,
792+
)
770793

771794
libraries_qs = None
772795
if scope_type != "course":
@@ -780,7 +803,12 @@ def get_permission(scope_cls):
780803
if not isinstance(s, OrgContentLibraryGlobData)
781804
}
782805
allowed_library_orgs = {s.org for s in allowed_library_scopes if isinstance(s, OrgContentLibraryGlobData)}
783-
libraries_qs = self._get_libraries_queryset(allowed_library_pairs, allowed_library_orgs, search=search)
806+
libraries_qs = self._get_libraries_queryset(
807+
allowed_library_pairs,
808+
allowed_library_orgs,
809+
search=search,
810+
org=org,
811+
)
784812

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

0 commit comments

Comments
 (0)