openedx-learning 0.19.0__tar.gz → 0.19.2__tar.gz

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.
Files changed (141) hide show
  1. {openedx_learning-0.19.0/openedx_learning.egg-info → openedx_learning-0.19.2}/PKG-INFO +7 -6
  2. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/__init__.py +1 -1
  3. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/components/api.py +15 -2
  4. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/publishing/admin.py +3 -0
  5. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/publishing/api.py +151 -7
  6. openedx_learning-0.19.2/openedx_learning/apps/authoring/publishing/migrations/0004_publishableentity_can_stand_alone.py +21 -0
  7. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/publishing/models/entity_list.py +1 -0
  8. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/publishing/models/publishable_entity.py +5 -0
  9. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/units/api.py +19 -2
  10. {openedx_learning-0.19.0 → openedx_learning-0.19.2/openedx_learning.egg-info}/PKG-INFO +7 -6
  11. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning.egg-info/SOURCES.txt +1 -0
  12. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/CHANGELOG.rst +0 -0
  13. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/LICENSE.txt +0 -0
  14. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/MANIFEST.in +0 -0
  15. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/README.rst +0 -0
  16. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/api/__init__.py +0 -0
  17. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/api/authoring.py +0 -0
  18. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/api/authoring_models.py +0 -0
  19. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/__init__.py +0 -0
  20. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/__init__.py +0 -0
  21. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/collections/__init__.py +0 -0
  22. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/collections/admin.py +0 -0
  23. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/collections/api.py +0 -0
  24. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/collections/apps.py +0 -0
  25. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/collections/migrations/0001_initial.py +0 -0
  26. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/collections/migrations/0002_remove_collection_name_collection_created_by_and_more.py +0 -0
  27. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/collections/migrations/0003_collection_entities.py +0 -0
  28. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/collections/migrations/0004_collection_key.py +0 -0
  29. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/collections/migrations/0005_alter_collection_options_alter_collection_enabled.py +0 -0
  30. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/collections/migrations/__init__.py +0 -0
  31. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/collections/models.py +0 -0
  32. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/components/__init__.py +0 -0
  33. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/components/admin.py +0 -0
  34. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/components/apps.py +0 -0
  35. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/components/management/__init__.py +0 -0
  36. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/components/management/commands/__init__.py +0 -0
  37. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/components/management/commands/add_assets_to_component.py +0 -0
  38. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/components/migrations/0001_initial.py +0 -0
  39. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/components/migrations/0002_alter_componentversioncontent_key.py +0 -0
  40. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/components/migrations/0003_remove_componentversioncontent_learner_downloadable.py +0 -0
  41. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/components/migrations/__init__.py +0 -0
  42. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/components/models.py +0 -0
  43. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/contents/__init__.py +0 -0
  44. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/contents/admin.py +0 -0
  45. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/contents/api.py +0 -0
  46. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/contents/apps.py +0 -0
  47. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/contents/migrations/0001_initial.py +0 -0
  48. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/contents/migrations/__init__.py +0 -0
  49. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/contents/models.py +0 -0
  50. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/publishing/__init__.py +0 -0
  51. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/publishing/apps.py +0 -0
  52. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/publishing/migrations/0001_initial.py +0 -0
  53. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/publishing/migrations/0002_alter_learningpackage_key_and_more.py +0 -0
  54. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/publishing/migrations/0003_containers.py +0 -0
  55. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/publishing/migrations/__init__.py +0 -0
  56. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/publishing/models/__init__.py +0 -0
  57. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/publishing/models/container.py +0 -0
  58. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/publishing/models/draft_published.py +0 -0
  59. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/publishing/models/learning_package.py +0 -0
  60. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/publishing/models/publish_log.py +0 -0
  61. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/units/__init__.py +0 -0
  62. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/units/apps.py +0 -0
  63. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/units/migrations/0001_initial.py +0 -0
  64. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/units/migrations/__init__.py +0 -0
  65. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/apps/authoring/units/models.py +0 -0
  66. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/contrib/__init__.py +0 -0
  67. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/contrib/media_server/__init__.py +0 -0
  68. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/contrib/media_server/apps.py +0 -0
  69. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/contrib/media_server/urls.py +0 -0
  70. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/contrib/media_server/views.py +0 -0
  71. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/lib/__init__.py +0 -0
  72. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/lib/admin_utils.py +0 -0
  73. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/lib/cache.py +0 -0
  74. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/lib/collations.py +0 -0
  75. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/lib/fields.py +0 -0
  76. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/lib/managers.py +0 -0
  77. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/lib/test_utils.py +0 -0
  78. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/lib/validators.py +0 -0
  79. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning/py.typed +0 -0
  80. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning.egg-info/dependency_links.txt +0 -0
  81. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning.egg-info/not-zip-safe +0 -0
  82. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning.egg-info/requires.txt +4 -4
  83. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_learning.egg-info/top_level.txt +0 -0
  84. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/__init__.py +0 -0
  85. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/__init__.py +0 -0
  86. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/__init__.py +0 -0
  87. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/admin.py +0 -0
  88. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/api.py +0 -0
  89. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/apps.py +0 -0
  90. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/data.py +0 -0
  91. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/import_export/__init__.py +0 -0
  92. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/import_export/actions.py +0 -0
  93. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/import_export/api.py +0 -0
  94. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/import_export/exceptions.py +0 -0
  95. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/import_export/import_plan.py +0 -0
  96. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/import_export/parsers.py +0 -0
  97. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/import_export/tasks.py +0 -0
  98. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/import_export/template.csv +0 -0
  99. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/import_export/template.json +0 -0
  100. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/migrations/0001_initial.py +0 -0
  101. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/migrations/0001_squashed.py +0 -0
  102. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/migrations/0002_auto_20230718_2026.py +0 -0
  103. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/migrations/0003_auto_20230721_1238.py +0 -0
  104. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/migrations/0004_auto_20230723_2001.py +0 -0
  105. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/migrations/0005_language_taxonomy.py +0 -0
  106. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/migrations/0006_alter_objecttag_unique_together.py +0 -0
  107. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/migrations/0006_auto_20230802_1631.py +0 -0
  108. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/migrations/0007_tag_import_task_log_null_fix.py +0 -0
  109. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/migrations/0008_taxonomy_description_not_null.py +0 -0
  110. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/migrations/0009_alter_objecttag_object_id.py +0 -0
  111. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/migrations/0010_cleanups.py +0 -0
  112. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/migrations/0011_remove_required.py +0 -0
  113. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/migrations/0012_language_taxonomy.py +0 -0
  114. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/migrations/0013_tag_parent_blank.py +0 -0
  115. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/migrations/0014_minor_fixes.py +0 -0
  116. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/migrations/0015_taxonomy_export_id.py +0 -0
  117. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/migrations/0016_object_tag_export_id.py +0 -0
  118. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/migrations/0017_alter_tagimporttask_status.py +0 -0
  119. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/migrations/0018_objecttag_is_copied.py +0 -0
  120. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/migrations/__init__.py +0 -0
  121. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/models/__init__.py +0 -0
  122. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/models/base.py +0 -0
  123. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/models/import_export.py +0 -0
  124. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/models/system_defined.py +0 -0
  125. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/models/utils.py +0 -0
  126. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/rest_api/__init__.py +0 -0
  127. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/rest_api/paginators.py +0 -0
  128. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/rest_api/urls.py +0 -0
  129. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/rest_api/utils.py +0 -0
  130. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/rest_api/v1/__init__.py +0 -0
  131. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/rest_api/v1/permissions.py +0 -0
  132. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/rest_api/v1/serializers.py +0 -0
  133. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/rest_api/v1/urls.py +0 -0
  134. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/rest_api/v1/views.py +0 -0
  135. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/rest_api/v1/views_import.py +0 -0
  136. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/rules.py +0 -0
  137. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/core/tagging/urls.py +0 -0
  138. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/openedx_tagging/py.typed +0 -0
  139. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/requirements/base.in +0 -0
  140. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/setup.cfg +0 -0
  141. {openedx_learning-0.19.0 → openedx_learning-0.19.2}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: openedx-learning
3
- Version: 0.19.0
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
@@ -18,12 +18,12 @@ Classifier: Programming Language :: Python :: 3.11
18
18
  Classifier: Programming Language :: Python :: 3.12
19
19
  Requires-Python: >=3.11
20
20
  License-File: LICENSE.txt
21
- Requires-Dist: attrs
22
- Requires-Dist: Django<5.0
23
- Requires-Dist: rules<4.0
24
21
  Requires-Dist: edx-drf-extensions
25
- Requires-Dist: celery
22
+ Requires-Dist: rules<4.0
26
23
  Requires-Dist: djangorestframework<4.0
24
+ Requires-Dist: Django<5.0
25
+ Requires-Dist: celery
26
+ Requires-Dist: attrs
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
@@ -2,4 +2,4 @@
2
2
  Open edX Learning ("Learning Core").
3
3
  """
4
4
 
5
- __version__ = "0.19.0"
5
+ __version__ = "0.19.2"
@@ -83,6 +83,8 @@ def create_component(
83
83
  local_key: str,
84
84
  created: datetime,
85
85
  created_by: int | None,
86
+ *,
87
+ can_stand_alone: bool = True,
86
88
  ) -> Component:
87
89
  """
88
90
  Create a new Component (an entity like a Problem or Video)
@@ -90,7 +92,11 @@ def create_component(
90
92
  key = f"{component_type.namespace}:{component_type.name}:{local_key}"
91
93
  with atomic():
92
94
  publishable_entity = publishing_api.create_publishable_entity(
93
- learning_package_id, key, created, created_by
95
+ learning_package_id,
96
+ key,
97
+ created,
98
+ created_by,
99
+ can_stand_alone=can_stand_alone
94
100
  )
95
101
  component = Component.objects.create(
96
102
  publishable_entity=publishable_entity,
@@ -239,13 +245,20 @@ def create_component_and_version( # pylint: disable=too-many-positional-argumen
239
245
  title: str,
240
246
  created: datetime,
241
247
  created_by: int | None = None,
248
+ *,
249
+ can_stand_alone: bool = True,
242
250
  ) -> tuple[Component, ComponentVersion]:
243
251
  """
244
252
  Create a Component and associated ComponentVersion atomically
245
253
  """
246
254
  with atomic():
247
255
  component = create_component(
248
- learning_package_id, component_type, local_key, created, created_by
256
+ learning_package_id,
257
+ component_type,
258
+ local_key,
259
+ created,
260
+ created_by,
261
+ can_stand_alone=can_stand_alone,
249
262
  )
250
263
  component_version = create_component_version(
251
264
  component.pk,
@@ -85,6 +85,7 @@ class PublishableEntityAdmin(ReadOnlyModelAdmin):
85
85
  "learning_package",
86
86
  "created",
87
87
  "created_by",
88
+ "can_stand_alone",
88
89
  ]
89
90
  list_filter = ["learning_package"]
90
91
  search_fields = ["key", "uuid"]
@@ -98,6 +99,7 @@ class PublishableEntityAdmin(ReadOnlyModelAdmin):
98
99
  "created",
99
100
  "created_by",
100
101
  "see_also",
102
+ "can_stand_alone",
101
103
  ]
102
104
  readonly_fields = [
103
105
  "key",
@@ -108,6 +110,7 @@ class PublishableEntityAdmin(ReadOnlyModelAdmin):
108
110
  "created",
109
111
  "created_by",
110
112
  "see_also",
113
+ "can_stand_alone",
111
114
  ]
112
115
 
113
116
  def get_queryset(self, request):
@@ -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
@@ -71,10 +72,14 @@ __all__ = [
71
72
  "create_container_version",
72
73
  "create_next_container_version",
73
74
  "get_container",
75
+ "get_container_by_key",
76
+ "get_containers",
77
+ "ChildrenEntitiesAction",
74
78
  "ContainerEntityListEntry",
75
79
  "get_entities_in_container",
76
80
  "contains_unpublished_changes",
77
81
  "get_containers_with_entity",
82
+ "get_container_children_count",
78
83
  ]
79
84
 
80
85
 
@@ -173,6 +178,8 @@ def create_publishable_entity(
173
178
  created: datetime,
174
179
  # User ID who created this
175
180
  created_by: int | None,
181
+ *,
182
+ can_stand_alone: bool = True,
176
183
  ) -> PublishableEntity:
177
184
  """
178
185
  Create a PublishableEntity.
@@ -185,6 +192,7 @@ def create_publishable_entity(
185
192
  key=key,
186
193
  created=created,
187
194
  created_by_id=created_by,
195
+ can_stand_alone=can_stand_alone,
188
196
  )
189
197
 
190
198
 
@@ -583,6 +591,8 @@ def create_container(
583
591
  key: str,
584
592
  created: datetime,
585
593
  created_by: int | None,
594
+ *,
595
+ can_stand_alone: bool = True,
586
596
  # The types on the following line are correct, but mypy will complain - https://github.com/python/mypy/issues/3737
587
597
  container_cls: type[ContainerModel] = Container, # type: ignore[assignment]
588
598
  ) -> ContainerModel:
@@ -595,6 +605,7 @@ def create_container(
595
605
  key: The key of the container.
596
606
  created: The date and time the container was created.
597
607
  created_by: The ID of the user who created the container
608
+ can_stand_alone: Set to False when created as part of containers
598
609
  container_cls: The subclass of Container to use, if applicable
599
610
 
600
611
  Returns:
@@ -603,7 +614,11 @@ def create_container(
603
614
  assert issubclass(container_cls, Container)
604
615
  with atomic():
605
616
  publishable_entity = create_publishable_entity(
606
- learning_package_id, key, created, created_by
617
+ learning_package_id,
618
+ key,
619
+ created,
620
+ created_by,
621
+ can_stand_alone=can_stand_alone,
607
622
  )
608
623
  container = container_cls.objects.create(
609
624
  publishable_entity=publishable_entity,
@@ -759,6 +774,70 @@ def create_container_version(
759
774
  return container_version
760
775
 
761
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
+
762
841
  def create_next_container_version(
763
842
  container_pk: int,
764
843
  *,
@@ -768,6 +847,7 @@ def create_next_container_version(
768
847
  created: datetime,
769
848
  created_by: int | None,
770
849
  container_version_cls: type[ContainerVersionModel] = ContainerVersion, # type: ignore[assignment]
850
+ entities_action: ChildrenEntitiesAction = ChildrenEntitiesAction.REPLACE,
771
851
  ) -> ContainerVersionModel:
772
852
  """
773
853
  [ 🛑 UNSTABLE ]
@@ -803,13 +883,14 @@ def create_next_container_version(
803
883
  # We're only changing metadata. Keep the same entity list.
804
884
  next_entity_list = last_version.entity_list
805
885
  else:
806
- if entity_version_pks is None:
807
- entity_version_pks = [None] * len(publishable_entities_pks)
808
- next_entity_list = create_entity_list_with_rows(
809
- entity_pks=publishable_entities_pks,
810
- entity_version_pks=entity_version_pks,
811
- 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
812
892
  )
893
+
813
894
  next_container_version = _create_container_version(
814
895
  container,
815
896
  next_version_num,
@@ -837,6 +918,43 @@ def get_container(pk: int) -> Container:
837
918
  return Container.objects.get(pk=pk)
838
919
 
839
920
 
921
+ def get_container_by_key(learning_package_id: int, /, key: str) -> Container:
922
+ """
923
+ [ 🛑 UNSTABLE ]
924
+ Get a container by its learning package and primary key.
925
+
926
+ Args:
927
+ learning_package_id: The ID of the learning package that contains the container.
928
+ key: The primary key of the container.
929
+
930
+ Returns:
931
+ The container with the given primary key.
932
+ """
933
+ return Container.objects.get(
934
+ publishable_entity__learning_package_id=learning_package_id,
935
+ publishable_entity__key=key,
936
+ )
937
+
938
+
939
+ def get_containers(
940
+ learning_package_id: int,
941
+ container_cls: type[ContainerModel] = Container, # type: ignore[assignment]
942
+ ) -> QuerySet[ContainerModel]:
943
+ """
944
+ [ 🛑 UNSTABLE ]
945
+ Get all containers in the given learning package.
946
+
947
+ Args:
948
+ learning_package_id: The primary key of the learning package
949
+ container_cls: The subclass of Container to use, if applicable
950
+
951
+ Returns:
952
+ A queryset containing the container associated with the given learning package.
953
+ """
954
+ assert issubclass(container_cls, Container)
955
+ return container_cls.objects.filter(publishable_entity__learning_package=learning_package_id)
956
+
957
+
840
958
  @dataclass(frozen=True)
841
959
  class ContainerEntityListEntry:
842
960
  """
@@ -969,3 +1087,29 @@ def get_containers_with_entity(
969
1087
  # publishable_entity__draft__version__containerversion__entity_list__in=lists
970
1088
  # )
971
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()
@@ -0,0 +1,21 @@
1
+ # Generated by Django 4.2.19 on 2025-03-17 11:07
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('oel_publishing', '0003_containers'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AddField(
14
+ model_name='publishableentity',
15
+ name='can_stand_alone',
16
+ field=models.BooleanField(
17
+ default=True,
18
+ help_text="Set to True when created independently, False when created as part of a container.",
19
+ ),
20
+ ),
21
+ ]
@@ -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(
@@ -9,6 +9,7 @@ from django.conf import settings
9
9
  from django.core.exceptions import ImproperlyConfigured
10
10
  from django.core.validators import MinValueValidator
11
11
  from django.db import models
12
+ from django.utils.translation import gettext as _
12
13
 
13
14
  from openedx_learning.lib.fields import (
14
15
  case_insensitive_char_field,
@@ -126,6 +127,10 @@ class PublishableEntity(models.Model):
126
127
  null=True,
127
128
  blank=True,
128
129
  )
130
+ can_stand_alone = models.BooleanField(
131
+ default=True,
132
+ help_text=_("Set to True when created independently, False when created as part of a container."),
133
+ )
129
134
 
130
135
  class Meta:
131
136
  constraints = [
@@ -30,7 +30,12 @@ __all__ = [
30
30
 
31
31
 
32
32
  def create_unit(
33
- learning_package_id: int, key: str, created: datetime, created_by: int | None
33
+ learning_package_id: int,
34
+ key: str,
35
+ created: datetime,
36
+ created_by: int | None,
37
+ *,
38
+ can_stand_alone: bool = True,
34
39
  ) -> Unit:
35
40
  """
36
41
  [ 🛑 UNSTABLE ] Create a new unit.
@@ -40,12 +45,14 @@ def create_unit(
40
45
  key: The key.
41
46
  created: The creation date.
42
47
  created_by: The user who created the unit.
48
+ can_stand_alone: Set to False when created as part of containers
43
49
  """
44
50
  return publishing_api.create_container(
45
51
  learning_package_id,
46
52
  key,
47
53
  created,
48
54
  created_by,
55
+ can_stand_alone=can_stand_alone,
49
56
  container_cls=Unit,
50
57
  )
51
58
 
@@ -123,6 +130,7 @@ def create_next_unit_version(
123
130
  components: list[Component | ComponentVersion] | None = None,
124
131
  created: datetime,
125
132
  created_by: int | None = None,
133
+ entities_action: publishing_api.ChildrenEntitiesAction = publishing_api.ChildrenEntitiesAction.REPLACE,
126
134
  ) -> UnitVersion:
127
135
  """
128
136
  [ 🛑 UNSTABLE ] Create the next unit version.
@@ -144,6 +152,7 @@ def create_next_unit_version(
144
152
  created=created,
145
153
  created_by=created_by,
146
154
  container_version_cls=UnitVersion,
155
+ entities_action=entities_action,
147
156
  )
148
157
  return unit_version
149
158
 
@@ -156,6 +165,7 @@ def create_unit_and_version(
156
165
  components: list[Component | ComponentVersion] | None = None,
157
166
  created: datetime,
158
167
  created_by: int | None = None,
168
+ can_stand_alone: bool = True,
159
169
  ) -> tuple[Unit, UnitVersion]:
160
170
  """
161
171
  [ 🛑 UNSTABLE ] Create a new unit and its version.
@@ -165,10 +175,17 @@ def create_unit_and_version(
165
175
  key: The key.
166
176
  created: The creation date.
167
177
  created_by: The user who created the unit.
178
+ can_stand_alone: Set to False when created as part of containers
168
179
  """
169
180
  publishable_entities_pks, entity_version_pks = _pub_entities_for_components(components)
170
181
  with atomic():
171
- unit = create_unit(learning_package_id, key, created, created_by)
182
+ unit = create_unit(
183
+ learning_package_id,
184
+ key,
185
+ created,
186
+ created_by,
187
+ can_stand_alone=can_stand_alone,
188
+ )
172
189
  unit_version = create_unit_version(
173
190
  unit,
174
191
  1,
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: openedx-learning
3
- Version: 0.19.0
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
@@ -18,12 +18,12 @@ Classifier: Programming Language :: Python :: 3.11
18
18
  Classifier: Programming Language :: Python :: 3.12
19
19
  Requires-Python: >=3.11
20
20
  License-File: LICENSE.txt
21
- Requires-Dist: attrs
22
- Requires-Dist: Django<5.0
23
- Requires-Dist: rules<4.0
24
21
  Requires-Dist: edx-drf-extensions
25
- Requires-Dist: celery
22
+ Requires-Dist: rules<4.0
26
23
  Requires-Dist: djangorestframework<4.0
24
+ Requires-Dist: Django<5.0
25
+ Requires-Dist: celery
26
+ Requires-Dist: attrs
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
@@ -54,6 +54,7 @@ openedx_learning/apps/authoring/publishing/apps.py
54
54
  openedx_learning/apps/authoring/publishing/migrations/0001_initial.py
55
55
  openedx_learning/apps/authoring/publishing/migrations/0002_alter_learningpackage_key_and_more.py
56
56
  openedx_learning/apps/authoring/publishing/migrations/0003_containers.py
57
+ openedx_learning/apps/authoring/publishing/migrations/0004_publishableentity_can_stand_alone.py
57
58
  openedx_learning/apps/authoring/publishing/migrations/__init__.py
58
59
  openedx_learning/apps/authoring/publishing/models/__init__.py
59
60
  openedx_learning/apps/authoring/publishing/models/container.py
@@ -1,6 +1,6 @@
1
- attrs
2
- Django<5.0
3
- rules<4.0
4
1
  edx-drf-extensions
5
- celery
2
+ rules<4.0
6
3
  djangorestframework<4.0
4
+ Django<5.0
5
+ celery
6
+ attrs