openedx-learning 0.20.0__tar.gz → 0.23.0__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 (146) hide show
  1. {openedx_learning-0.20.0/openedx_learning.egg-info → openedx_learning-0.23.0}/PKG-INFO +5 -5
  2. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/__init__.py +1 -1
  3. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/collections/api.py +46 -1
  4. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/components/api.py +1 -55
  5. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/publishing/admin.py +76 -1
  6. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/publishing/api.py +417 -76
  7. openedx_learning-0.23.0/openedx_learning/apps/authoring/publishing/contextmanagers.py +157 -0
  8. openedx_learning-0.23.0/openedx_learning/apps/authoring/publishing/migrations/0006_draftchangelog.py +68 -0
  9. openedx_learning-0.23.0/openedx_learning/apps/authoring/publishing/migrations/0007_bootstrap_draftchangelog.py +94 -0
  10. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/publishing/models/__init__.py +2 -2
  11. openedx_learning-0.23.0/openedx_learning/apps/authoring/publishing/models/draft_log.py +315 -0
  12. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/publishing/models/publish_log.py +44 -0
  13. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/units/api.py +23 -24
  14. {openedx_learning-0.20.0 → openedx_learning-0.23.0/openedx_learning.egg-info}/PKG-INFO +5 -5
  15. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning.egg-info/SOURCES.txt +4 -1
  16. openedx_learning-0.20.0/openedx_learning/apps/authoring/publishing/models/draft_published.py +0 -95
  17. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/CHANGELOG.rst +0 -0
  18. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/LICENSE.txt +0 -0
  19. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/MANIFEST.in +0 -0
  20. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/README.rst +0 -0
  21. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/api/__init__.py +0 -0
  22. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/api/authoring.py +0 -0
  23. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/api/authoring_models.py +0 -0
  24. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/__init__.py +0 -0
  25. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/__init__.py +0 -0
  26. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/collections/__init__.py +0 -0
  27. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/collections/admin.py +0 -0
  28. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/collections/apps.py +0 -0
  29. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/collections/migrations/0001_initial.py +0 -0
  30. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/collections/migrations/0002_remove_collection_name_collection_created_by_and_more.py +0 -0
  31. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/collections/migrations/0003_collection_entities.py +0 -0
  32. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/collections/migrations/0004_collection_key.py +0 -0
  33. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/collections/migrations/0005_alter_collection_options_alter_collection_enabled.py +0 -0
  34. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/collections/migrations/__init__.py +0 -0
  35. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/collections/models.py +0 -0
  36. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/components/__init__.py +0 -0
  37. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/components/admin.py +0 -0
  38. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/components/apps.py +0 -0
  39. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/components/management/__init__.py +0 -0
  40. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/components/management/commands/__init__.py +0 -0
  41. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/components/management/commands/add_assets_to_component.py +0 -0
  42. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/components/migrations/0001_initial.py +0 -0
  43. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/components/migrations/0002_alter_componentversioncontent_key.py +0 -0
  44. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/components/migrations/0003_remove_componentversioncontent_learner_downloadable.py +0 -0
  45. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/components/migrations/__init__.py +0 -0
  46. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/components/models.py +0 -0
  47. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/contents/__init__.py +0 -0
  48. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/contents/admin.py +0 -0
  49. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/contents/api.py +0 -0
  50. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/contents/apps.py +0 -0
  51. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/contents/migrations/0001_initial.py +0 -0
  52. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/contents/migrations/__init__.py +0 -0
  53. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/contents/models.py +0 -0
  54. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/publishing/__init__.py +0 -0
  55. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/publishing/apps.py +0 -0
  56. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/publishing/migrations/0001_initial.py +0 -0
  57. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/publishing/migrations/0002_alter_learningpackage_key_and_more.py +0 -0
  58. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/publishing/migrations/0003_containers.py +0 -0
  59. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/publishing/migrations/0004_publishableentity_can_stand_alone.py +0 -0
  60. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/publishing/migrations/0005_alter_entitylistrow_options.py +0 -0
  61. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/publishing/migrations/__init__.py +0 -0
  62. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/publishing/models/container.py +0 -0
  63. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/publishing/models/entity_list.py +0 -0
  64. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/publishing/models/learning_package.py +0 -0
  65. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/publishing/models/publishable_entity.py +0 -0
  66. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/units/__init__.py +0 -0
  67. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/units/apps.py +0 -0
  68. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/units/migrations/0001_initial.py +0 -0
  69. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/units/migrations/__init__.py +0 -0
  70. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/apps/authoring/units/models.py +0 -0
  71. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/contrib/__init__.py +0 -0
  72. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/contrib/media_server/__init__.py +0 -0
  73. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/contrib/media_server/apps.py +0 -0
  74. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/contrib/media_server/urls.py +0 -0
  75. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/contrib/media_server/views.py +0 -0
  76. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/lib/__init__.py +0 -0
  77. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/lib/admin_utils.py +0 -0
  78. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/lib/cache.py +0 -0
  79. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/lib/collations.py +0 -0
  80. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/lib/fields.py +0 -0
  81. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/lib/managers.py +0 -0
  82. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/lib/test_utils.py +0 -0
  83. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/lib/validators.py +0 -0
  84. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning/py.typed +0 -0
  85. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning.egg-info/dependency_links.txt +0 -0
  86. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning.egg-info/not-zip-safe +0 -0
  87. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning.egg-info/requires.txt +4 -4
  88. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_learning.egg-info/top_level.txt +0 -0
  89. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/__init__.py +0 -0
  90. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/__init__.py +0 -0
  91. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/__init__.py +0 -0
  92. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/admin.py +0 -0
  93. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/api.py +0 -0
  94. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/apps.py +0 -0
  95. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/data.py +0 -0
  96. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/import_export/__init__.py +0 -0
  97. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/import_export/actions.py +0 -0
  98. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/import_export/api.py +0 -0
  99. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/import_export/exceptions.py +0 -0
  100. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/import_export/import_plan.py +0 -0
  101. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/import_export/parsers.py +0 -0
  102. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/import_export/tasks.py +0 -0
  103. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/import_export/template.csv +0 -0
  104. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/import_export/template.json +0 -0
  105. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/migrations/0001_initial.py +0 -0
  106. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/migrations/0001_squashed.py +0 -0
  107. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/migrations/0002_auto_20230718_2026.py +0 -0
  108. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/migrations/0003_auto_20230721_1238.py +0 -0
  109. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/migrations/0004_auto_20230723_2001.py +0 -0
  110. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/migrations/0005_language_taxonomy.py +0 -0
  111. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/migrations/0006_alter_objecttag_unique_together.py +0 -0
  112. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/migrations/0006_auto_20230802_1631.py +0 -0
  113. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/migrations/0007_tag_import_task_log_null_fix.py +0 -0
  114. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/migrations/0008_taxonomy_description_not_null.py +0 -0
  115. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/migrations/0009_alter_objecttag_object_id.py +0 -0
  116. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/migrations/0010_cleanups.py +0 -0
  117. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/migrations/0011_remove_required.py +0 -0
  118. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/migrations/0012_language_taxonomy.py +0 -0
  119. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/migrations/0013_tag_parent_blank.py +0 -0
  120. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/migrations/0014_minor_fixes.py +0 -0
  121. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/migrations/0015_taxonomy_export_id.py +0 -0
  122. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/migrations/0016_object_tag_export_id.py +0 -0
  123. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/migrations/0017_alter_tagimporttask_status.py +0 -0
  124. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/migrations/0018_objecttag_is_copied.py +0 -0
  125. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/migrations/__init__.py +0 -0
  126. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/models/__init__.py +0 -0
  127. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/models/base.py +0 -0
  128. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/models/import_export.py +0 -0
  129. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/models/system_defined.py +0 -0
  130. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/models/utils.py +0 -0
  131. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/rest_api/__init__.py +0 -0
  132. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/rest_api/paginators.py +0 -0
  133. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/rest_api/urls.py +0 -0
  134. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/rest_api/utils.py +0 -0
  135. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/rest_api/v1/__init__.py +0 -0
  136. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/rest_api/v1/permissions.py +0 -0
  137. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/rest_api/v1/serializers.py +0 -0
  138. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/rest_api/v1/urls.py +0 -0
  139. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/rest_api/v1/views.py +0 -0
  140. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/rest_api/v1/views_import.py +0 -0
  141. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/rules.py +0 -0
  142. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/core/tagging/urls.py +0 -0
  143. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/openedx_tagging/py.typed +0 -0
  144. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/requirements/base.in +0 -0
  145. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/setup.cfg +0 -0
  146. {openedx_learning-0.20.0 → openedx_learning-0.23.0}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openedx-learning
3
- Version: 0.20.0
3
+ Version: 0.23.0
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,12 +19,12 @@ Classifier: Programming Language :: Python :: 3.11
19
19
  Classifier: Programming Language :: Python :: 3.12
20
20
  Requires-Python: >=3.11
21
21
  License-File: LICENSE.txt
22
- Requires-Dist: djangorestframework<4.0
23
22
  Requires-Dist: attrs
24
- Requires-Dist: celery
25
- Requires-Dist: rules<4.0
26
- Requires-Dist: edx-drf-extensions
27
23
  Requires-Dist: Django
24
+ Requires-Dist: djangorestframework<4.0
25
+ Requires-Dist: edx-drf-extensions
26
+ Requires-Dist: rules<4.0
27
+ Requires-Dist: celery
28
28
  Dynamic: author
29
29
  Dynamic: author-email
30
30
  Dynamic: classifier
@@ -2,4 +2,4 @@
2
2
  Open edX Learning ("Learning Core").
3
3
  """
4
4
 
5
- __version__ = "0.20.0"
5
+ __version__ = "0.23.0"
@@ -10,7 +10,7 @@ from django.db.models import QuerySet
10
10
 
11
11
  from ..publishing import api as publishing_api
12
12
  from ..publishing.models import PublishableEntity
13
- from .models import Collection
13
+ from .models import Collection, CollectionPublishableEntity
14
14
 
15
15
  # The public API that will be re-exported by openedx_learning.apps.authoring.api
16
16
  # is listed in the __all__ entries below. Internal helper functions that are
@@ -27,6 +27,7 @@ __all__ = [
27
27
  "remove_from_collection",
28
28
  "restore_collection",
29
29
  "update_collection",
30
+ "set_collections",
30
31
  ]
31
32
 
32
33
 
@@ -204,3 +205,47 @@ def get_collections(learning_package_id: int, enabled: bool | None = True) -> Qu
204
205
  if enabled is not None:
205
206
  qs = qs.filter(enabled=enabled)
206
207
  return qs.select_related("learning_package").order_by('pk')
208
+
209
+
210
+ def set_collections(
211
+ publishable_entity: PublishableEntity,
212
+ collection_qset: QuerySet[Collection],
213
+ created_by: int | None = None,
214
+ ) -> set[Collection]:
215
+ """
216
+ Set collections for a given publishable entity.
217
+
218
+ These Collections must belong to the same LearningPackage as the PublishableEntity,
219
+ or a ValidationError will be raised.
220
+
221
+ Modified date of all collections related to entity is updated.
222
+
223
+ Returns the updated collections.
224
+ """
225
+ # Disallow adding entities outside the collection's learning package
226
+ if collection_qset.exclude(learning_package_id=publishable_entity.learning_package_id).count():
227
+ raise ValidationError(
228
+ "Collection entities must be from the same learning package as the collection.",
229
+ )
230
+ current_relations = CollectionPublishableEntity.objects.filter(
231
+ entity=publishable_entity
232
+ ).select_related('collection')
233
+ # Clear other collections for given entity and add only new collections from collection_qset
234
+ removed_collections = set(
235
+ r.collection for r in current_relations.exclude(collection__in=collection_qset)
236
+ )
237
+ new_collections = set(collection_qset.exclude(
238
+ id__in=current_relations.values_list('collection', flat=True)
239
+ ))
240
+ # Triggers a m2m_changed signal
241
+ publishable_entity.collections.set(
242
+ objs=collection_qset,
243
+ through_defaults={"created_by_id": created_by},
244
+ )
245
+ # Update modified date via update to avoid triggering post_save signal for all collections, which can be very slow.
246
+ affected_collection = removed_collections | new_collections
247
+ Collection.objects.filter(
248
+ id__in=[collection.id for collection in affected_collection]
249
+ ).update(modified=datetime.now(tz=timezone.utc))
250
+
251
+ return affected_collection
@@ -13,18 +13,16 @@ are stored in this app.
13
13
  from __future__ import annotations
14
14
 
15
15
  import mimetypes
16
- from datetime import datetime, timezone
16
+ from datetime import datetime
17
17
  from enum import StrEnum, auto
18
18
  from logging import getLogger
19
19
  from pathlib import Path
20
20
  from uuid import UUID
21
21
 
22
- from django.core.exceptions import ValidationError
23
22
  from django.db.models import Q, QuerySet
24
23
  from django.db.transaction import atomic
25
24
  from django.http.response import HttpResponse, HttpResponseNotFound
26
25
 
27
- from ..collections.models import Collection, CollectionPublishableEntity
28
26
  from ..contents import api as contents_api
29
27
  from ..publishing import api as publishing_api
30
28
  from .models import Component, ComponentType, ComponentVersion, ComponentVersionContent
@@ -51,7 +49,6 @@ __all__ = [
51
49
  "look_up_component_version_content",
52
50
  "AssetError",
53
51
  "get_redirect_response_for_component_asset",
54
- "set_collections",
55
52
  ]
56
53
 
57
54
 
@@ -605,54 +602,3 @@ def get_redirect_response_for_component_asset(
605
602
  )
606
603
 
607
604
  return HttpResponse(headers={**info_headers, **redirect_headers})
608
-
609
-
610
- def set_collections(
611
- learning_package_id: int,
612
- component: Component,
613
- collection_qset: QuerySet[Collection],
614
- created_by: int | None = None,
615
- ) -> set[Collection]:
616
- """
617
- Set collections for a given component.
618
-
619
- These Collections must belong to the same LearningPackage as the Component, or a ValidationError will be raised.
620
-
621
- Modified date of all collections related to component is updated.
622
-
623
- Returns the updated collections.
624
- """
625
- # Disallow adding entities outside the collection's learning package
626
- invalid_collection = collection_qset.exclude(learning_package_id=learning_package_id).first()
627
- if invalid_collection:
628
- raise ValidationError(
629
- f"Cannot add collection {invalid_collection.pk} in learning package "
630
- f"{invalid_collection.learning_package_id} to component {component} in "
631
- f"learning package {learning_package_id}."
632
- )
633
- current_relations = CollectionPublishableEntity.objects.filter(
634
- entity=component.publishable_entity
635
- ).select_related('collection')
636
- # Clear other collections for given component and add only new collections from collection_qset
637
- removed_collections = set(
638
- r.collection for r in current_relations.exclude(collection__in=collection_qset)
639
- )
640
- new_collections = set(collection_qset.exclude(
641
- id__in=current_relations.values_list('collection', flat=True)
642
- ))
643
- # Use `remove` instead of `CollectionPublishableEntity.delete()` to trigger m2m_changed signal which will handle
644
- # updating component index.
645
- component.publishable_entity.collections.remove(*removed_collections)
646
- component.publishable_entity.collections.add(
647
- *new_collections,
648
- through_defaults={"created_by_id": created_by},
649
- )
650
- # Update modified date via update to avoid triggering post_save signal for collections
651
- # The signal triggers index update for each collection synchronously which will be very slow in this case.
652
- # Instead trigger the index update in the caller function asynchronously.
653
- affected_collection = removed_collections | new_collections
654
- Collection.objects.filter(
655
- id__in=[collection.id for collection in affected_collection]
656
- ).update(modified=datetime.now(tz=timezone.utc))
657
-
658
- return affected_collection
@@ -4,10 +4,19 @@ Django admin for publishing models
4
4
  from __future__ import annotations
5
5
 
6
6
  from django.contrib import admin
7
+ from django.db.models import Count
7
8
 
8
9
  from openedx_learning.lib.admin_utils import ReadOnlyModelAdmin, one_to_one_related_model_html
9
10
 
10
- from .models import LearningPackage, PublishableEntity, Published, PublishLog, PublishLogRecord
11
+ from .models import (
12
+ DraftChangeLog,
13
+ DraftChangeLogRecord,
14
+ LearningPackage,
15
+ PublishableEntity,
16
+ PublishLog,
17
+ PublishLogRecord,
18
+ )
19
+ from .models.publish_log import Published
11
20
 
12
21
 
13
22
  @admin.register(LearningPackage)
@@ -171,3 +180,69 @@ class PublishedAdmin(ReadOnlyModelAdmin):
171
180
 
172
181
  def message(self, published_obj):
173
182
  return published_obj.publish_log_record.publish_log.message
183
+
184
+
185
+ class DraftChangeLogRecordTabularInline(admin.TabularInline):
186
+ """
187
+ Tabular inline for a single Draft change.
188
+ """
189
+ model = DraftChangeLogRecord
190
+
191
+ fields = (
192
+ "entity",
193
+ "title",
194
+ "old_version_num",
195
+ "new_version_num",
196
+ )
197
+ readonly_fields = fields
198
+
199
+ def get_queryset(self, request):
200
+ queryset = super().get_queryset(request)
201
+ return queryset.select_related("entity", "old_version", "new_version") \
202
+ .order_by("entity__key")
203
+
204
+ def old_version_num(self, draft_change: DraftChangeLogRecord):
205
+ if draft_change.old_version is None:
206
+ return "-"
207
+ return draft_change.old_version.version_num
208
+
209
+ def new_version_num(self, draft_change: DraftChangeLogRecord):
210
+ if draft_change.new_version is None:
211
+ return "-"
212
+ return draft_change.new_version.version_num
213
+
214
+ def title(self, draft_change: DraftChangeLogRecord):
215
+ """
216
+ Get the title to display for the DraftChange
217
+ """
218
+ if draft_change.new_version:
219
+ return draft_change.new_version.title
220
+ if draft_change.old_version:
221
+ return draft_change.old_version.title
222
+ return ""
223
+
224
+
225
+ @admin.register(DraftChangeLog)
226
+ class DraftChangeSetAdmin(ReadOnlyModelAdmin):
227
+ """
228
+ Read-only admin to view Draft changes (via inline tables)
229
+ """
230
+ inlines = [DraftChangeLogRecordTabularInline]
231
+ fields = (
232
+ "uuid",
233
+ "learning_package",
234
+ "num_changes",
235
+ "changed_at",
236
+ "changed_by",
237
+ )
238
+ readonly_fields = fields
239
+ list_display = fields
240
+ list_filter = ["learning_package"]
241
+
242
+ def num_changes(self, draft_change_set):
243
+ return draft_change_set.num_changes
244
+
245
+ def get_queryset(self, request):
246
+ queryset = super().get_queryset(request)
247
+ return queryset.select_related("learning_package", "changed_by") \
248
+ .annotate(num_changes=Count("records"))