openedx-learning 0.19.1__py2.py3-none-any.whl → 0.19.2__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,4 +2,4 @@
2
2
  Open edX Learning ("Learning Core").
3
3
  """
4
4
 
5
- __version__ = "0.19.1"
5
+ __version__ = "0.19.2"
@@ -8,6 +8,7 @@ from __future__ import annotations
8
8
 
9
9
  from dataclasses import dataclass
10
10
  from datetime import datetime, timezone
11
+ from enum import Enum
11
12
  from typing import TypeVar
12
13
 
13
14
  from django.core.exceptions import ObjectDoesNotExist, ValidationError
@@ -73,10 +74,12 @@ __all__ = [
73
74
  "get_container",
74
75
  "get_container_by_key",
75
76
  "get_containers",
77
+ "ChildrenEntitiesAction",
76
78
  "ContainerEntityListEntry",
77
79
  "get_entities_in_container",
78
80
  "contains_unpublished_changes",
79
81
  "get_containers_with_entity",
82
+ "get_container_children_count",
80
83
  ]
81
84
 
82
85
 
@@ -771,6 +774,70 @@ def create_container_version(
771
774
  return container_version
772
775
 
773
776
 
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
+
774
841
  def create_next_container_version(
775
842
  container_pk: int,
776
843
  *,
@@ -780,6 +847,7 @@ def create_next_container_version(
780
847
  created: datetime,
781
848
  created_by: int | None,
782
849
  container_version_cls: type[ContainerVersionModel] = ContainerVersion, # type: ignore[assignment]
850
+ entities_action: ChildrenEntitiesAction = ChildrenEntitiesAction.REPLACE,
783
851
  ) -> ContainerVersionModel:
784
852
  """
785
853
  [ 🛑 UNSTABLE ]
@@ -815,13 +883,14 @@ def create_next_container_version(
815
883
  # We're only changing metadata. Keep the same entity list.
816
884
  next_entity_list = last_version.entity_list
817
885
  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
824
892
  )
893
+
825
894
  next_container_version = _create_container_version(
826
895
  container,
827
896
  next_version_num,
@@ -1018,3 +1087,29 @@ def get_containers_with_entity(
1018
1087
  # publishable_entity__draft__version__containerversion__entity_list__in=lists
1019
1088
  # )
1020
1089
  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()
@@ -60,6 +60,7 @@ class EntityListRow(models.Model):
60
60
  )
61
61
 
62
62
  class Meta:
63
+ ordering = ["order_num"]
63
64
  constraints = [
64
65
  # If (entity_list, order_num) is not unique, it likely indicates a race condition - so force uniqueness.
65
66
  models.UniqueConstraint(
@@ -130,6 +130,7 @@ def create_next_unit_version(
130
130
  components: list[Component | ComponentVersion] | None = None,
131
131
  created: datetime,
132
132
  created_by: int | None = None,
133
+ entities_action: publishing_api.ChildrenEntitiesAction = publishing_api.ChildrenEntitiesAction.REPLACE,
133
134
  ) -> UnitVersion:
134
135
  """
135
136
  [ 🛑 UNSTABLE ] Create the next unit version.
@@ -151,6 +152,7 @@ def create_next_unit_version(
151
152
  created=created,
152
153
  created_by=created_by,
153
154
  container_version_cls=UnitVersion,
155
+ entities_action=entities_action,
154
156
  )
155
157
  return unit_version
156
158
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: openedx-learning
3
- Version: 0.19.1
3
+ Version: 0.19.2
4
4
  Summary: Open edX Learning Core and Tagging.
5
5
  Home-page: https://github.com/openedx/openedx-learning
6
6
  Author: David Ormsbee
@@ -19,11 +19,11 @@ Classifier: Programming Language :: Python :: 3.12
19
19
  Requires-Python: >=3.11
20
20
  License-File: LICENSE.txt
21
21
  Requires-Dist: edx-drf-extensions
22
- Requires-Dist: Django<5.0
23
22
  Requires-Dist: rules<4.0
23
+ Requires-Dist: djangorestframework<4.0
24
+ Requires-Dist: Django<5.0
24
25
  Requires-Dist: celery
25
26
  Requires-Dist: attrs
26
- Requires-Dist: djangorestframework<4.0
27
27
  Dynamic: author
28
28
  Dynamic: author-email
29
29
  Dynamic: classifier
@@ -31,6 +31,7 @@ Dynamic: description
31
31
  Dynamic: home-page
32
32
  Dynamic: keywords
33
33
  Dynamic: license
34
+ Dynamic: license-file
34
35
  Dynamic: requires-dist
35
36
  Dynamic: requires-python
36
37
  Dynamic: summary
@@ -1,4 +1,4 @@
1
- openedx_learning/__init__.py,sha256=px6MoC3pWrrXjiOIJXCYYjh1zVYJmN1_36ERKhfnEOQ,69
1
+ openedx_learning/__init__.py,sha256=z4XTeo1JUHOOsjfd2UHkU0k4ZI_v0ZxK3_TeHwEHpDY,69
2
2
  openedx_learning/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  openedx_learning/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  openedx_learning/api/authoring.py,sha256=AuEegFTT3tAer_3zSxcxqLq3Yd_eilk-Hxt4ESYearA,970
@@ -37,7 +37,7 @@ openedx_learning/apps/authoring/contents/migrations/0001_initial.py,sha256=FtOTm
37
37
  openedx_learning/apps/authoring/contents/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
38
  openedx_learning/apps/authoring/publishing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
39
  openedx_learning/apps/authoring/publishing/admin.py,sha256=yPtqznDzlWHj0xpJ_FGFRviyftgPmuql5EoqKLeZQFo,4911
40
- openedx_learning/apps/authoring/publishing/api.py,sha256=OregsRNQev41Nm1Uq6B_zoezJgV-EVsYaf8_iJ3sbxI,37430
40
+ openedx_learning/apps/authoring/publishing/api.py,sha256=tNpDYfQXwjpkp3acVXNItNXN4oCCekGSQa9M5TozXEs,41130
41
41
  openedx_learning/apps/authoring/publishing/apps.py,sha256=v9PTe3YoICaYT9wfu268ZkVAlnZFvxi-DqYdbRi25bY,750
42
42
  openedx_learning/apps/authoring/publishing/migrations/0001_initial.py,sha256=wvekNV19YRSdxRmQaFnLSn_nCsQlHIucPDVMmgKf_OE,9272
43
43
  openedx_learning/apps/authoring/publishing/migrations/0002_alter_learningpackage_key_and_more.py,sha256=toI7qJhNukk6hirKfFx9EpqTpzF2O2Yq1VpFJusDn2M,806
@@ -47,12 +47,12 @@ openedx_learning/apps/authoring/publishing/migrations/__init__.py,sha256=47DEQpj
47
47
  openedx_learning/apps/authoring/publishing/models/__init__.py,sha256=Ou6Ks3qxEhel9uHLpzi77jlkb_IHc9_WIfqxOZJuqaE,1059
48
48
  openedx_learning/apps/authoring/publishing/models/container.py,sha256=GCUD3WTlgvgZSQOKcoOsfhNAfc5pz3Wbs9ClE9mhtB0,2594
49
49
  openedx_learning/apps/authoring/publishing/models/draft_published.py,sha256=dDZfbTcqLmnOhgIG-H8mhPFLRks_c03VE-ynGD_CNeo,3632
50
- openedx_learning/apps/authoring/publishing/models/entity_list.py,sha256=EWpkUHUqJr5xKGJCRQ494xugdULxMXxYviFOot9ypUU,3009
50
+ openedx_learning/apps/authoring/publishing/models/entity_list.py,sha256=8MyJqDdC8dqJY-N9UAu-WS-ZeFOYuRNMKloSY9wMH1w,3042
51
51
  openedx_learning/apps/authoring/publishing/models/learning_package.py,sha256=1fuNLHD6k0qGuL0jXYGf4-TA5WczgxJrXUdAIM_JNBI,2688
52
52
  openedx_learning/apps/authoring/publishing/models/publish_log.py,sha256=-uMj1X8umqRM259ucOqNt6POeKDpY5RLdA8XnWXt7Go,4109
53
53
  openedx_learning/apps/authoring/publishing/models/publishable_entity.py,sha256=-6Fde_sYqaq1AUC4sw4Rf1X9Hh4jun2Py7Xhsl4VWuY,25419
54
54
  openedx_learning/apps/authoring/units/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
55
- openedx_learning/apps/authoring/units/api.py,sha256=WjeM9E_5yH-zawlCzBY0BwCywRVeNxoojq4IDN9gOds,9957
55
+ openedx_learning/apps/authoring/units/api.py,sha256=nhAATIC0j4We1-3K5yr9FvNr7Dyg-LVHyxfa1ik6IMw,10106
56
56
  openedx_learning/apps/authoring/units/apps.py,sha256=cIzphjDw5sjIZ3NLE911N7IMUa8JQSXMReNl03uI7jg,701
57
57
  openedx_learning/apps/authoring/units/models.py,sha256=eTOwFWC9coQLf0ovx08Mj7zi8mPAWCw9QOznybajRk0,1418
58
58
  openedx_learning/apps/authoring/units/migrations/0001_initial.py,sha256=qM_0JGffxECVgXzncHXfgSE-g8u3L3a14R0M1Bnj_Ys,1129
@@ -70,6 +70,7 @@ openedx_learning/lib/fields.py,sha256=eiGoXMPhRuq25EH2qf6BAODshAQE3DBVdIYAMIUAXW
70
70
  openedx_learning/lib/managers.py,sha256=-Q3gxalSqyPZ9Im4DTROW5tF8wVTZLlmfTe62_xmowY,1643
71
71
  openedx_learning/lib/test_utils.py,sha256=g3KLuepIZbaDBCsaj9711YuqyUx7LD4gXDcfNC-mWdc,527
72
72
  openedx_learning/lib/validators.py,sha256=iqEdEAvFV2tC7Ecssx69kjecpdU8nE87AlDJYrqrsnc,404
73
+ openedx_learning-0.19.2.dist-info/licenses/LICENSE.txt,sha256=QTW2QN7q3XszgUAXm9Dzgtu5LXYKbR1SGnqMa7ufEuY,35139
73
74
  openedx_tagging/__init__.py,sha256=V9N8M7f9LYlAbA_DdPUsHzTnWjYRXKGa5qHw9P1JnNI,30
74
75
  openedx_tagging/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
75
76
  openedx_tagging/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -125,8 +126,7 @@ openedx_tagging/core/tagging/rest_api/v1/serializers.py,sha256=0HQD_Jrf6-YpocYfz
125
126
  openedx_tagging/core/tagging/rest_api/v1/urls.py,sha256=dNUKCtUCx_YzrwlbEbpDfjGVQbb2QdJ1VuJCkladj6E,752
126
127
  openedx_tagging/core/tagging/rest_api/v1/views.py,sha256=Hf92cy-tE767DE9FgsZcPKiCYrf5ihfETz8qGKBnuiU,36278
127
128
  openedx_tagging/core/tagging/rest_api/v1/views_import.py,sha256=kbHUPe5A6WaaJ3J1lFIcYCt876ecLNQfd19m7YYub6c,1470
128
- openedx_learning-0.19.1.dist-info/LICENSE.txt,sha256=QTW2QN7q3XszgUAXm9Dzgtu5LXYKbR1SGnqMa7ufEuY,35139
129
- openedx_learning-0.19.1.dist-info/METADATA,sha256=Jm1GoiVhtSJfYV6nlRXB7H5XPy8XVvGTivFNF7j0f0w,8975
130
- openedx_learning-0.19.1.dist-info/WHEEL,sha256=SrDKpSbFN1G94qcmBqS9nyHcDMp9cUS9OC06hC0G3G0,109
131
- openedx_learning-0.19.1.dist-info/top_level.txt,sha256=IYFbr5mgiEHd-LOtZmXj3q3a0bkGK1M9LY7GXgnfi4M,33
132
- openedx_learning-0.19.1.dist-info/RECORD,,
129
+ openedx_learning-0.19.2.dist-info/METADATA,sha256=ThUAT_bbUaS5Xzmsb56SN-YSkYmBKvA3Yg0npDMQReE,8997
130
+ openedx_learning-0.19.2.dist-info/WHEEL,sha256=aoLN90hLOL0c0qxXMxWYUM3HA3WmFGZQqEJHX1V_OJE,109
131
+ openedx_learning-0.19.2.dist-info/top_level.txt,sha256=IYFbr5mgiEHd-LOtZmXj3q3a0bkGK1M9LY7GXgnfi4M,33
132
+ openedx_learning-0.19.2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (76.0.0)
2
+ Generator: setuptools (77.0.3)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py2-none-any
5
5
  Tag: py3-none-any