-
Notifications
You must be signed in to change notification settings - Fork 5
feat: add PoC permission and role #209
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
bbeec5d
be420c4
f004d5d
d7da43b
96ac10f
861e5de
bcf703d
892bfda
9761639
74245c8
7b99812
ad7e36c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,8 +9,11 @@ | |
|
|
||
| from attrs import define | ||
| from opaque_keys import InvalidKeyError | ||
| from opaque_keys.edx.keys import CourseKey | ||
| from opaque_keys.edx.locator import LibraryLocatorV2 | ||
|
|
||
| from openedx_authz.tests.stubs.models import CourseOverview | ||
dwong2708 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| try: | ||
| from openedx.core.djangoapps.content_libraries.models import ContentLibrary | ||
| except ImportError: | ||
|
|
@@ -462,6 +465,108 @@ def __repr__(self): | |
| return self.namespaced_key | ||
|
|
||
|
|
||
| @define | ||
| class CourseOverviewData(ScopeData): | ||
| """A course scope for authorization in the Open edX platform. | ||
|
|
||
| Courses uses the CourseKey format for identification. | ||
|
|
||
| Attributes: | ||
| NAMESPACE: 'course' for course scopes. | ||
| external_key: The course identifier (e.g., 'course-v1:TestOrg+TestCourse+2024_T1'). | ||
| Must be a valid CourseKey format. | ||
| namespaced_key: The course identifier with namespace (e.g., 'course^course-v1:TestOrg+TestCourse+2024_T1'). | ||
|
||
| course_id: Property alias for external_key. | ||
|
|
||
| Examples: | ||
| >>> course = CourseOverviewData(external_key='course-v1:TestOrg+TestCourse+2024_T1') | ||
| >>> course.namespaced_key | ||
| 'course^course-v1:TestOrg+TestCourse+2024_T1' | ||
| >>> course.course_id | ||
| 'course-v1:TestOrg+TestCourse+2024_T1' | ||
|
|
||
| """ | ||
|
|
||
| NAMESPACE: ClassVar[str] = "course" | ||
|
|
||
| @property | ||
| def course_id(self) -> str: | ||
| """The course identifier as used in Open edX (e.g., 'course-v1:TestOrg+TestCourse+2024_T1'). | ||
|
|
||
| This is an alias for external_key that represents the course ID without the namespace prefix. | ||
|
|
||
| Returns: | ||
| str: The course identifier without namespace. | ||
| """ | ||
| return self.external_key | ||
|
|
||
| @property | ||
| def course_key(self) -> CourseKey: | ||
| """The CourseKey object for the course. | ||
|
|
||
| Returns: | ||
| CourseKey: The course key object. | ||
| """ | ||
| return CourseKey.from_string(self.course_id) | ||
|
|
||
| @classmethod | ||
| def validate_external_key(cls, external_key: str) -> bool: | ||
| """Validate the external_key format for CourseOverviewData. | ||
|
|
||
| Args: | ||
| external_key: The external key to validate. | ||
|
|
||
| Returns: | ||
| bool: True if valid, False otherwise. | ||
| """ | ||
| try: | ||
| CourseKey.from_string(external_key) | ||
| return True | ||
| except InvalidKeyError: | ||
| return False | ||
|
|
||
| def get_object(self) -> CourseOverview | None: | ||
| """Retrieve the CourseOverview instance associated with this scope. | ||
|
|
||
| This method converts the course_id to a CourseKey and queries the | ||
| database to fetch the corresponding CourseOverview object. | ||
|
|
||
| Returns: | ||
| CourseOverview | None: The CourseOverview instance if found in the database, | ||
| or None if the course does not exist or has an invalid key format. | ||
|
|
||
| Examples: | ||
| >>> course_scope = CourseOverviewData(external_key='course-v1:TestOrg+TestCourse+2024_T1') | ||
| >>> course_obj = course_scope.get_object() # CourseOverview object | ||
| """ | ||
| try: | ||
| course_obj = CourseOverview.get_from_id(self.course_key) | ||
| # Validate canonical key: get_by_key is case-insensitive, but we require exact match | ||
| # This ensures authorization uses canonical course IDs consistently | ||
| if course_obj.id != self.course_key: | ||
| raise CourseOverview.DoesNotExist | ||
| except (InvalidKeyError, CourseOverview.DoesNotExist): | ||
| return None | ||
|
|
||
| return course_obj | ||
|
|
||
| def exists(self) -> bool: | ||
| """Check if the course overview exists. | ||
|
|
||
| Returns: | ||
| bool: True if the course overview exists, False otherwise. | ||
| """ | ||
| return self.get_object() is not None | ||
|
|
||
| def __str__(self): | ||
| """Human readable string representation of the course overview.""" | ||
| return self.course_id | ||
|
|
||
| def __repr__(self): | ||
| """Developer friendly string representation of the course overview.""" | ||
| return self.namespaced_key | ||
|
|
||
|
|
||
| class SubjectMeta(type): | ||
| """Metaclass for SubjectData to handle dynamic subclass instantiation based on namespace.""" | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| # Generated by Django 4.2.24 on 2026-02-06 17:19 | ||
|
|
||
| import django.db.models.deletion | ||
| from django.conf import settings | ||
| from django.db import migrations, models | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
| dependencies = [ | ||
| ("stubs", "__first__"), | ||
dwong2708 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ("openedx_authz", "0006_migrate_legacy_permissions"), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.CreateModel( | ||
| name="CourseScope", | ||
| fields=[ | ||
| ( | ||
| "scope_ptr", | ||
| models.OneToOneField( | ||
| auto_created=True, | ||
| on_delete=django.db.models.deletion.CASCADE, | ||
| parent_link=True, | ||
| primary_key=True, | ||
| serialize=False, | ||
| to="openedx_authz.scope", | ||
| ), | ||
| ), | ||
| ( | ||
| "course_overview", | ||
| models.ForeignKey( | ||
| blank=True, | ||
| null=True, | ||
| on_delete=django.db.models.deletion.CASCADE, | ||
| related_name="authz_scopes", | ||
| to=settings.OPENEDX_AUTHZ_COURSE_OVERVIEW_MODEL, | ||
| ), | ||
| ), | ||
| ], | ||
| options={ | ||
| "abstract": False, | ||
| }, | ||
| bases=("openedx_authz.scope",), | ||
| ), | ||
| ] | ||
Uh oh!
There was an error while loading. Please reload this page.