@@ -526,6 +526,110 @@ def __repr__(self):
526526 return self .namespaced_key
527527
528528
529+ @define
530+ class CourseOverviewData (ScopeData ):
531+ """A course scope for authorization in the Open edX platform.
532+
533+ Courses uses the CourseKey format for identification.
534+
535+ Attributes:
536+ NAMESPACE (str): 'course-v1' for course scopes.
537+ ID_SEPARATOR (str): '+' for course scopes.
538+ external_key (str): The course identifier (e.g., 'course-v1:TestOrg+TestCourse+2024_T1').
539+ Must be a valid CourseKey format.
540+ namespaced_key (str): The course identifier with namespace
541+ (e.g., 'course-v1^course-v1:TestOrg+TestCourse+2024_T1').
542+ course_id (str): Property alias for external_key.
543+
544+ Examples:
545+ >>> course = CourseOverviewData(external_key='course-v1:TestOrg+TestCourse+2024_T1')
546+ >>> course.namespaced_key
547+ 'course-v1^course-v1:TestOrg+TestCourse+2024_T1'
548+ >>> course.course_id
549+ 'course-v1:TestOrg+TestCourse+2024_T1'
550+ """
551+
552+ NAMESPACE : ClassVar [str ] = "course-v1"
553+ ID_SEPARATOR : ClassVar [str ] = "+"
554+
555+ @property
556+ def course_id (self ) -> str :
557+ """The course identifier as used in Open edX (e.g., 'course-v1:TestOrg+TestCourse+2024_T1').
558+
559+ This is an alias for external_key that represents the course ID without the namespace prefix.
560+
561+ Returns:
562+ str: The course identifier without namespace.
563+ """
564+ return self .external_key
565+
566+ @property
567+ def course_key (self ) -> CourseKey :
568+ """The CourseKey object for the course.
569+
570+ Returns:
571+ CourseKey: The course key object.
572+ """
573+ return CourseKey .from_string (self .course_id )
574+
575+ @classmethod
576+ def validate_external_key (cls , external_key : str ) -> bool :
577+ """Validate the external_key format for CourseOverviewData.
578+
579+ Args:
580+ external_key: The external key to validate.
581+
582+ Returns:
583+ bool: True if valid, False otherwise.
584+ """
585+ try :
586+ CourseKey .from_string (external_key )
587+ return True
588+ except InvalidKeyError :
589+ return False
590+
591+ def get_object (self ) -> CourseOverview | None :
592+ """Retrieve the CourseOverview instance associated with this scope.
593+
594+ This method converts the course_id to a CourseKey and queries the
595+ database to fetch the corresponding CourseOverview object.
596+
597+ Returns:
598+ CourseOverview | None: The CourseOverview instance if found in the database,
599+ or None if the course does not exist or has an invalid key format.
600+
601+ Examples:
602+ >>> course_scope = CourseOverviewData(external_key='course-v1:TestOrg+TestCourse+2024_T1')
603+ >>> course_obj = course_scope.get_object() # CourseOverview object
604+ """
605+ try :
606+ course_obj = CourseOverview .get_from_id (self .course_key )
607+ # Validate canonical key: get_by_key is case-insensitive, but we require exact match
608+ # This ensures authorization uses canonical course IDs consistently
609+ if course_obj .id != self .course_key :
610+ raise CourseOverview .DoesNotExist
611+ except (InvalidKeyError , CourseOverview .DoesNotExist ):
612+ return None
613+
614+ return course_obj
615+
616+ def exists (self ) -> bool :
617+ """Check if the course overview exists.
618+
619+ Returns:
620+ bool: True if the course overview exists, False otherwise.
621+ """
622+ return self .get_object () is not None
623+
624+ def __str__ (self ):
625+ """Human readable string representation of the course overview."""
626+ return self .course_id
627+
628+ def __repr__ (self ):
629+ """Developer friendly string representation of the course overview."""
630+ return self .namespaced_key
631+
632+
529633@define
530634class OrgGlobData (ScopeData ):
531635 """Base class for organization-scoped *glob* scope keys.
@@ -677,110 +781,6 @@ class OrgContentLibraryGlobData(OrgGlobData):
677781 ID_SEPARATOR : ClassVar [str ] = ":"
678782
679783
680- @define
681- class CourseOverviewData (ScopeData ):
682- """A course scope for authorization in the Open edX platform.
683-
684- Courses uses the CourseKey format for identification.
685-
686- Attributes:
687- NAMESPACE (str): 'course-v1' for course scopes.
688- ID_SEPARATOR (str): '+' for course scopes.
689- external_key (str): The course identifier (e.g., 'course-v1:TestOrg+TestCourse+2024_T1').
690- Must be a valid CourseKey format.
691- namespaced_key (str): The course identifier with namespace
692- (e.g., 'course-v1^course-v1:TestOrg+TestCourse+2024_T1').
693- course_id (str): Property alias for external_key.
694-
695- Examples:
696- >>> course = CourseOverviewData(external_key='course-v1:TestOrg+TestCourse+2024_T1')
697- >>> course.namespaced_key
698- 'course-v1^course-v1:TestOrg+TestCourse+2024_T1'
699- >>> course.course_id
700- 'course-v1:TestOrg+TestCourse+2024_T1'
701- """
702-
703- NAMESPACE : ClassVar [str ] = "course-v1"
704- ID_SEPARATOR : ClassVar [str ] = "+"
705-
706- @property
707- def course_id (self ) -> str :
708- """The course identifier as used in Open edX (e.g., 'course-v1:TestOrg+TestCourse+2024_T1').
709-
710- This is an alias for external_key that represents the course ID without the namespace prefix.
711-
712- Returns:
713- str: The course identifier without namespace.
714- """
715- return self .external_key
716-
717- @property
718- def course_key (self ) -> CourseKey :
719- """The CourseKey object for the course.
720-
721- Returns:
722- CourseKey: The course key object.
723- """
724- return CourseKey .from_string (self .course_id )
725-
726- @classmethod
727- def validate_external_key (cls , external_key : str ) -> bool :
728- """Validate the external_key format for CourseOverviewData.
729-
730- Args:
731- external_key: The external key to validate.
732-
733- Returns:
734- bool: True if valid, False otherwise.
735- """
736- try :
737- CourseKey .from_string (external_key )
738- return True
739- except InvalidKeyError :
740- return False
741-
742- def get_object (self ) -> CourseOverview | None :
743- """Retrieve the CourseOverview instance associated with this scope.
744-
745- This method converts the course_id to a CourseKey and queries the
746- database to fetch the corresponding CourseOverview object.
747-
748- Returns:
749- CourseOverview | None: The CourseOverview instance if found in the database,
750- or None if the course does not exist or has an invalid key format.
751-
752- Examples:
753- >>> course_scope = CourseOverviewData(external_key='course-v1:TestOrg+TestCourse+2024_T1')
754- >>> course_obj = course_scope.get_object() # CourseOverview object
755- """
756- try :
757- course_obj = CourseOverview .get_from_id (self .course_key )
758- # Validate canonical key: get_by_key is case-insensitive, but we require exact match
759- # This ensures authorization uses canonical course IDs consistently
760- if course_obj .id != self .course_key :
761- raise CourseOverview .DoesNotExist
762- except (InvalidKeyError , CourseOverview .DoesNotExist ):
763- return None
764-
765- return course_obj
766-
767- def exists (self ) -> bool :
768- """Check if the course overview exists.
769-
770- Returns:
771- bool: True if the course overview exists, False otherwise.
772- """
773- return self .get_object () is not None
774-
775- def __str__ (self ):
776- """Human readable string representation of the course overview."""
777- return self .course_id
778-
779- def __repr__ (self ):
780- """Developer friendly string representation of the course overview."""
781- return self .namespaced_key
782-
783-
784784@define
785785class OrgCourseOverviewGlobData (OrgGlobData ):
786786 """Organization-level glob pattern for courses.
0 commit comments