88
99from dataclasses import dataclass
1010from datetime import datetime , timezone
11+ from enum import Enum
1112from typing import TypeVar
1213
1314from django .core .exceptions import ObjectDoesNotExist , ValidationError
7374 "get_container" ,
7475 "get_container_by_key" ,
7576 "get_containers" ,
77+ "ChildrenEntitiesAction" ,
7678 "ContainerEntityListEntry" ,
7779 "get_entities_in_container" ,
7880 "contains_unpublished_changes" ,
7981 "get_containers_with_entity" ,
82+ "get_container_children_count" ,
8083]
8184
8285
@@ -771,6 +774,70 @@ def create_container_version(
771774 return container_version
772775
773776
777+ class ChildrenEntitiesAction (Enum ):
778+ """Possible actions for children entities"""
779+
780+ APPEND = "append"
781+ REMOVE = "remove"
782+ REPLACE = "replace"
783+
784+
785+ def create_next_entity_list (
786+ learning_package_id : int ,
787+ last_version : ContainerVersion ,
788+ publishable_entities_pks : list [int ],
789+ entity_version_pks : list [int | None ] | None ,
790+ entities_action : ChildrenEntitiesAction = ChildrenEntitiesAction .REPLACE ,
791+ ) -> EntityList :
792+ """
793+ Creates next entity list based on the given entities_action.
794+
795+ Args:
796+ learning_package_id: Learning package ID
797+ last_version: Last version of container.
798+ publishable_entities_pks: The IDs of the members current members of the container.
799+ entity_version_pks: The IDs of the versions to pin to, if pinning is desired.
800+ entities_action: APPEND, REMOVE or REPLACE given entities from/to the container
801+
802+ Returns:
803+ The newly created entity list.
804+ """
805+ if entity_version_pks is None :
806+ entity_version_pks : list [int | None ] = [None ] * len (publishable_entities_pks ) # type: ignore[no-redef]
807+ if entities_action == ChildrenEntitiesAction .APPEND :
808+ # get previous entity list rows
809+ last_entities = last_version .entity_list .entitylistrow_set .only (
810+ "entity_id" ,
811+ "entity_version_id"
812+ ).order_by ("order_num" )
813+ # append given publishable_entities_pks and entity_version_pks
814+ publishable_entities_pks = [entity .entity_id for entity in last_entities ] + publishable_entities_pks
815+ entity_version_pks = [ # type: ignore[operator, assignment]
816+ entity .entity_version_id
817+ for entity in last_entities
818+ ] + entity_version_pks
819+ elif entities_action == ChildrenEntitiesAction .REMOVE :
820+ # get previous entity list rows
821+ last_entities = last_version .entity_list .entitylistrow_set .only (
822+ "entity_id" ,
823+ "entity_version_id"
824+ ).order_by ("order_num" )
825+ # Remove entities that are in publishable_entities_pks
826+ new_entities = [
827+ entity
828+ for entity in last_entities
829+ if entity .entity_id not in publishable_entities_pks
830+ ]
831+ publishable_entities_pks = [entity .entity_id for entity in new_entities ]
832+ entity_version_pks = [entity .entity_version_id for entity in new_entities ]
833+ next_entity_list = create_entity_list_with_rows (
834+ entity_pks = publishable_entities_pks ,
835+ entity_version_pks = entity_version_pks , # type: ignore[arg-type]
836+ learning_package_id = learning_package_id ,
837+ )
838+ return next_entity_list
839+
840+
774841def create_next_container_version (
775842 container_pk : int ,
776843 * ,
@@ -780,6 +847,7 @@ def create_next_container_version(
780847 created : datetime ,
781848 created_by : int | None ,
782849 container_version_cls : type [ContainerVersionModel ] = ContainerVersion , # type: ignore[assignment]
850+ entities_action : ChildrenEntitiesAction = ChildrenEntitiesAction .REPLACE ,
783851) -> ContainerVersionModel :
784852 """
785853 [ 🛑 UNSTABLE ]
@@ -815,13 +883,14 @@ def create_next_container_version(
815883 # We're only changing metadata. Keep the same entity list.
816884 next_entity_list = last_version .entity_list
817885 else :
818- if entity_version_pks is None :
819- entity_version_pks = [ None ] * len ( publishable_entities_pks )
820- next_entity_list = create_entity_list_with_rows (
821- entity_pks = publishable_entities_pks ,
822- entity_version_pks = entity_version_pks ,
823- learning_package_id = entity . learning_package_id ,
886+ next_entity_list = create_next_entity_list (
887+ entity . learning_package_id ,
888+ last_version ,
889+ publishable_entities_pks ,
890+ entity_version_pks ,
891+ entities_action
824892 )
893+
825894 next_container_version = _create_container_version (
826895 container ,
827896 next_version_num ,
@@ -1018,3 +1087,29 @@ def get_containers_with_entity(
10181087 # publishable_entity__draft__version__containerversion__entity_list__in=lists
10191088 # )
10201089 return qs
1090+
1091+
1092+ def get_container_children_count (
1093+ container : Container ,
1094+ * ,
1095+ published : bool ,
1096+ ):
1097+ """
1098+ [ 🛑 UNSTABLE ]
1099+ Get the count of entities in the current draft or published version of the given container.
1100+
1101+ Args:
1102+ container: The Container, e.g. returned by `get_container()`
1103+ published: `True` if we want the published version of the container, or
1104+ `False` for the draft version.
1105+ """
1106+ assert isinstance (container , Container )
1107+ container_version = container .versioning .published if published else container .versioning .draft
1108+ if container_version is None :
1109+ raise ContainerVersion .DoesNotExist # This container has not been published yet, or has been deleted.
1110+ assert isinstance (container_version , ContainerVersion )
1111+ if published :
1112+ filter_deleted = {"entity__published__version__isnull" : False }
1113+ else :
1114+ filter_deleted = {"entity__draft__version__isnull" : False }
1115+ return container_version .entity_list .entitylistrow_set .filter (** filter_deleted ).count ()
0 commit comments