88
99from dataclasses import dataclass
1010from datetime import datetime , timezone
11+ from typing import TypeVar
1112
1213from django .core .exceptions import ObjectDoesNotExist , ValidationError
1314from django .db .models import F , Q , QuerySet
1415from django .db .transaction import atomic
1516
16- from .model_mixins import (
17- ContainerMixin ,
18- PublishableContentModelRegistry ,
19- PublishableEntityMixin ,
20- PublishableEntityVersionMixin ,
21- )
17+ from .model_mixins import PublishableContentModelRegistry , PublishableEntityMixin , PublishableEntityVersionMixin
2218from .models import (
2319 Container ,
2420 ContainerVersion ,
3329 PublishLogRecord ,
3430)
3531
32+ ContainerModel = TypeVar ('ContainerModel' , bound = Container )
33+ ContainerVersionModel = TypeVar ('ContainerVersionModel' , bound = ContainerVersion )
34+
3635# The public API that will be re-exported by openedx_learning.apps.authoring.api
3736# is listed in the __all__ entries below. Internal helper functions that are
3837# private to this module should start with an underscore. If a function does not
6665 "create_container" ,
6766 "create_container_version" ,
6867 "create_next_container_version" ,
69- "create_container_and_version" ,
7068 "get_container" ,
7169 "ContainerEntityListEntry" ,
7270 "get_entities_in_draft_container" ,
@@ -581,7 +579,9 @@ def create_container(
581579 key : str ,
582580 created : datetime ,
583581 created_by : int | None ,
584- ) -> Container :
582+ # The types on the following line are correct, but mypy will complain - https://github.com/python/mypy/issues/3737
583+ container_model : type [ContainerModel ] = Container , # type: ignore[assignment]
584+ ) -> ContainerModel :
585585 """
586586 [ 🛑 UNSTABLE ]
587587 Create a new container.
@@ -591,15 +591,17 @@ def create_container(
591591 key: The key of the container.
592592 created: The date and time the container was created.
593593 created_by: The ID of the user who created the container
594+ container_model: The subclass of Container to use, if applicable
594595
595596 Returns:
596597 The newly created container.
597598 """
599+ assert issubclass (container_model , Container )
598600 with atomic ():
599601 publishable_entity = create_publishable_entity (
600602 learning_package_id , key , created , created_by
601603 )
602- container = Container .objects .create (
604+ container = container_model .objects .create (
603605 publishable_entity = publishable_entity ,
604606 )
605607 return container
@@ -635,7 +637,7 @@ def create_entity_list_with_rows(
635637 The newly created entity list.
636638 """
637639 order_nums = range (len (entity_pks ))
638- with atomic ():
640+ with atomic (savepoint = False ):
639641 entity_list = create_entity_list ()
640642 EntityListRow .objects .bulk_create (
641643 [
@@ -662,7 +664,8 @@ def create_container_version(
662664 entity_version_pks : list [int | None ] | None ,
663665 created : datetime ,
664666 created_by : int | None ,
665- ) -> ContainerVersion :
667+ container_version_model : type [ContainerVersionModel ] = ContainerVersion , # type: ignore[assignment]
668+ ) -> ContainerVersionModel :
666669 """
667670 [ 🛑 UNSTABLE ]
668671 Create a new container version.
@@ -675,11 +678,13 @@ def create_container_version(
675678 entity_version_pks: The IDs of the versions to pin to, if pinning is desired.
676679 created: The date and time the container version was created.
677680 created_by: The ID of the user who created the container version.
681+ container_version_model: The subclass of ContainerVersion to use, if applicable.
678682
679683 Returns:
680684 The newly created container version.
681685 """
682- with atomic ():
686+ assert issubclass (container_version_model , ContainerVersion )
687+ with atomic (savepoint = False ):
683688 container = Container .objects .select_related ("publishable_entity" ).get (pk = container_pk )
684689 entity = container .publishable_entity
685690
@@ -706,7 +711,7 @@ def create_container_version(
706711 created = created ,
707712 created_by = created_by ,
708713 )
709- container_version = ContainerVersion .objects .create (
714+ container_version = container_version_model .objects .create (
710715 publishable_entity_version = publishable_entity_version ,
711716 container_id = container_pk ,
712717 entity_list = entity_list ,
@@ -723,7 +728,8 @@ def create_next_container_version(
723728 entity_version_pks : list [int | None ] | None ,
724729 created : datetime ,
725730 created_by : int | None ,
726- ) -> ContainerVersion :
731+ container_version_model : type [ContainerVersionModel ] = ContainerVersion , # type: ignore[assignment]
732+ ) -> ContainerVersionModel :
727733 """
728734 [ 🛑 UNSTABLE ]
729735 Create the next version of a container. A new version of the container is created
@@ -742,10 +748,12 @@ def create_next_container_version(
742748 entity_version_pks: The IDs of the versions to pin to, if pinning is desired.
743749 created: The date and time the container version was created.
744750 created_by: The ID of the user who created the container version.
751+ container_version_model: The subclass of ContainerVersion to use, if applicable.
745752
746753 Returns:
747754 The newly created container version.
748755 """
756+ assert issubclass (container_version_model , ContainerVersion )
749757 with atomic ():
750758 container = Container .objects .select_related ("publishable_entity" ).get (pk = container_pk )
751759 entity = container .publishable_entity
@@ -776,7 +784,7 @@ def create_next_container_version(
776784 created = created ,
777785 created_by = created_by ,
778786 )
779- next_container_version = ContainerVersion .objects .create (
787+ next_container_version = container_version_model .objects .create (
780788 publishable_entity_version = publishable_entity_version ,
781789 container_id = container_pk ,
782790 entity_list = next_entity_list ,
@@ -785,46 +793,6 @@ def create_next_container_version(
785793 return next_container_version
786794
787795
788- def create_container_and_version (
789- learning_package_id : int ,
790- key : str ,
791- * ,
792- created : datetime ,
793- created_by : int | None ,
794- title : str ,
795- publishable_entities_pks : list [int ],
796- entity_version_pks : list [int | None ],
797- ) -> tuple [Container , ContainerVersion ]:
798- """
799- [ 🛑 UNSTABLE ]
800- Create a new container and its first version.
801-
802- Args:
803- learning_package_id: The ID of the learning package that contains the container.
804- key: The key of the container.
805- created: The date and time the container was created.
806- created_by: The ID of the user who created the container.
807- version_num: The version number of the container.
808- title: The title of the container.
809- members_pk: The IDs of the members of the container.
810-
811- Returns:
812- The newly created container version.
813- """
814- with atomic ():
815- container = create_container (learning_package_id , key , created , created_by )
816- container_version = create_container_version (
817- container .publishable_entity .pk ,
818- 1 ,
819- title = title ,
820- publishable_entities_pks = publishable_entities_pks ,
821- entity_version_pks = entity_version_pks ,
822- created = created ,
823- created_by = created_by ,
824- )
825- return (container , container_version )
826-
827-
828796def get_container (pk : int ) -> Container :
829797 """
830798 [ 🛑 UNSTABLE ]
@@ -854,15 +822,13 @@ def entity(self):
854822
855823
856824def get_entities_in_draft_container (
857- container : Container | ContainerMixin ,
825+ container : Container ,
858826) -> list [ContainerEntityListEntry ]:
859827 """
860828 [ 🛑 UNSTABLE ]
861829 Get the list of entities and their versions in the draft version of the
862830 given container.
863831 """
864- if isinstance (container , ContainerMixin ):
865- container = container .container
866832 assert isinstance (container , Container )
867833 entity_list = []
868834 for row in container .versioning .draft .entity_list .entitylistrow_set .order_by ("order_num" ):
@@ -877,19 +843,15 @@ def get_entities_in_draft_container(
877843
878844
879845def get_entities_in_published_container (
880- container : Container | ContainerMixin ,
846+ container : Container ,
881847) -> list [ContainerEntityListEntry ] | None :
882848 """
883849 [ 🛑 UNSTABLE ]
884850 Get the list of entities and their versions in the published version of the
885851 given container.
886852 """
887- if isinstance (container , ContainerMixin ):
888- cv = container .container .versioning .published
889- elif isinstance (container , Container ):
890- cv = container .versioning .published
891- else :
892- raise TypeError (f"Expected Container or ContainerMixin; got { type (container )} " )
853+ assert isinstance (container , Container )
854+ cv = container .versioning .published
893855 if cv is None :
894856 return None # There is no published version of this container. Should this be an exception?
895857 assert isinstance (cv , ContainerVersion )
@@ -905,9 +867,7 @@ def get_entities_in_published_container(
905867 return entity_list
906868
907869
908- def contains_unpublished_changes (
909- container : Container | ContainerMixin ,
910- ) -> bool :
870+ def contains_unpublished_changes (container_id : int ) -> bool :
911871 """
912872 [ 🛑 UNSTABLE ]
913873 Check recursively if a container has any unpublished changes.
@@ -920,14 +880,10 @@ def contains_unpublished_changes(
920880 that's in the container, it will be `False`. This method will return `True`
921881 in either case.
922882 """
923- if isinstance (container , ContainerMixin ):
924- # This is similar to 'get_container(container.container_id)' but pre-loads more data.
925- container = Container .objects .select_related (
926- "publishable_entity__draft__version__containerversion__entity_list" ,
927- ).get (pk = container .container_id )
928- else :
929- pass # TODO: select_related if we're given a raw Container rather than a ContainerMixin like Unit?
930- assert isinstance (container , Container )
883+ # This is similar to 'get_container(container.container_id)' but pre-loads more data.
884+ container = Container .objects .select_related (
885+ "publishable_entity__draft__version__containerversion__entity_list" ,
886+ ).get (pk = container_id )
931887
932888 if container .versioning .has_unpublished_changes :
933889 return True
@@ -949,7 +905,7 @@ def contains_unpublished_changes(
949905 child_container = None
950906 if child_container :
951907 # This is itself a container - check recursively:
952- if contains_unpublished_changes (child_container ):
908+ if contains_unpublished_changes (child_container . pk ):
953909 return True
954910 else :
955911 # This is not a container:
0 commit comments