openedx-learning 0.27.0__tar.gz → 0.28.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 (175) hide show
  1. {openedx_learning-0.27.0/openedx_learning.egg-info → openedx_learning-0.28.0}/PKG-INFO +6 -6
  2. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/__init__.py +1 -1
  3. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/api/authoring.py +1 -0
  4. openedx_learning-0.28.0/openedx_learning/apps/authoring/backup_restore/api.py +15 -0
  5. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/backup_restore/management/commands/lp_dump.py +0 -1
  6. openedx_learning-0.28.0/openedx_learning/apps/authoring/backup_restore/toml.py +76 -0
  7. openedx_learning-0.28.0/openedx_learning/apps/authoring/backup_restore/zipper.py +242 -0
  8. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/components/apps.py +2 -2
  9. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/publishing/api.py +67 -13
  10. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/publishing/apps.py +2 -2
  11. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/publishing/models/publishable_entity.py +7 -3
  12. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/sections/api.py +6 -1
  13. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/sections/apps.py +2 -2
  14. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/subsections/api.py +6 -1
  15. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/subsections/apps.py +2 -2
  16. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/units/api.py +6 -1
  17. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/units/apps.py +2 -2
  18. {openedx_learning-0.27.0 → openedx_learning-0.28.0/openedx_learning.egg-info}/PKG-INFO +6 -6
  19. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning.egg-info/SOURCES.txt +1 -0
  20. openedx_learning-0.28.0/openedx_tagging/core/tagging/models/utils.py +82 -0
  21. openedx_learning-0.27.0/openedx_learning/apps/authoring/backup_restore/api.py +0 -25
  22. openedx_learning-0.27.0/openedx_learning/apps/authoring/backup_restore/toml.py +0 -72
  23. openedx_learning-0.27.0/openedx_tagging/core/tagging/models/utils.py +0 -54
  24. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/CHANGELOG.rst +0 -0
  25. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/LICENSE.txt +0 -0
  26. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/MANIFEST.in +0 -0
  27. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/README.rst +0 -0
  28. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/api/__init__.py +0 -0
  29. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/api/authoring_models.py +0 -0
  30. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/__init__.py +0 -0
  31. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/__init__.py +0 -0
  32. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/backup_restore/__init__.py +0 -0
  33. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/backup_restore/admin.py +0 -0
  34. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/backup_restore/apps.py +0 -0
  35. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/backup_restore/management/__init__.py +0 -0
  36. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/backup_restore/management/commands/__init__.py +0 -0
  37. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/backup_restore/migrations/__init__.py +0 -0
  38. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/backup_restore/models.py +0 -0
  39. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/collections/__init__.py +0 -0
  40. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/collections/admin.py +0 -0
  41. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/collections/api.py +0 -0
  42. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/collections/apps.py +0 -0
  43. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/collections/migrations/0001_initial.py +0 -0
  44. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/collections/migrations/0002_remove_collection_name_collection_created_by_and_more.py +0 -0
  45. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/collections/migrations/0003_collection_entities.py +0 -0
  46. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/collections/migrations/0004_collection_key.py +0 -0
  47. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/collections/migrations/0005_alter_collection_options_alter_collection_enabled.py +0 -0
  48. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/collections/migrations/__init__.py +0 -0
  49. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/collections/models.py +0 -0
  50. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/components/__init__.py +0 -0
  51. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/components/admin.py +0 -0
  52. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/components/api.py +0 -0
  53. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/components/management/__init__.py +0 -0
  54. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/components/management/commands/__init__.py +0 -0
  55. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/components/management/commands/add_assets_to_component.py +0 -0
  56. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/components/migrations/0001_initial.py +0 -0
  57. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/components/migrations/0002_alter_componentversioncontent_key.py +0 -0
  58. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/components/migrations/0003_remove_componentversioncontent_learner_downloadable.py +0 -0
  59. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/components/migrations/__init__.py +0 -0
  60. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/components/models.py +0 -0
  61. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/contents/__init__.py +0 -0
  62. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/contents/admin.py +0 -0
  63. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/contents/api.py +0 -0
  64. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/contents/apps.py +0 -0
  65. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/contents/migrations/0001_initial.py +0 -0
  66. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/contents/migrations/__init__.py +0 -0
  67. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/contents/models.py +0 -0
  68. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/publishing/__init__.py +0 -0
  69. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/publishing/admin.py +0 -0
  70. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/publishing/contextmanagers.py +0 -0
  71. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/publishing/migrations/0001_initial.py +0 -0
  72. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/publishing/migrations/0002_alter_learningpackage_key_and_more.py +0 -0
  73. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/publishing/migrations/0003_containers.py +0 -0
  74. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/publishing/migrations/0004_publishableentity_can_stand_alone.py +0 -0
  75. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/publishing/migrations/0005_alter_entitylistrow_options.py +0 -0
  76. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/publishing/migrations/0006_draftchangelog.py +0 -0
  77. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/publishing/migrations/0007_bootstrap_draftchangelog.py +0 -0
  78. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/publishing/migrations/0008_alter_draftchangelogrecord_options_and_more.py +0 -0
  79. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/publishing/migrations/__init__.py +0 -0
  80. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/publishing/models/__init__.py +0 -0
  81. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/publishing/models/container.py +0 -0
  82. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/publishing/models/draft_log.py +0 -0
  83. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/publishing/models/entity_list.py +0 -0
  84. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/publishing/models/learning_package.py +0 -0
  85. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/publishing/models/publish_log.py +0 -0
  86. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/sections/__init__.py +0 -0
  87. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/sections/admin.py +0 -0
  88. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/sections/migrations/0001_initial.py +0 -0
  89. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/sections/migrations/__init__.py +0 -0
  90. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/sections/models.py +0 -0
  91. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/subsections/__init__.py +0 -0
  92. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/subsections/admin.py +0 -0
  93. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/subsections/migrations/0001_initial.py +0 -0
  94. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/subsections/migrations/__init__.py +0 -0
  95. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/subsections/models.py +0 -0
  96. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/units/__init__.py +0 -0
  97. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/units/admin.py +0 -0
  98. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/units/migrations/0001_initial.py +0 -0
  99. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/units/migrations/__init__.py +0 -0
  100. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/apps/authoring/units/models.py +0 -0
  101. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/contrib/__init__.py +0 -0
  102. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/contrib/media_server/__init__.py +0 -0
  103. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/contrib/media_server/apps.py +0 -0
  104. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/contrib/media_server/urls.py +0 -0
  105. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/contrib/media_server/views.py +0 -0
  106. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/lib/__init__.py +0 -0
  107. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/lib/admin_utils.py +0 -0
  108. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/lib/cache.py +0 -0
  109. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/lib/collations.py +0 -0
  110. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/lib/fields.py +0 -0
  111. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/lib/managers.py +0 -0
  112. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/lib/test_utils.py +0 -0
  113. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/lib/validators.py +0 -0
  114. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning/py.typed +0 -0
  115. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning.egg-info/dependency_links.txt +0 -0
  116. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning.egg-info/not-zip-safe +0 -0
  117. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning.egg-info/requires.txt +5 -5
  118. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_learning.egg-info/top_level.txt +0 -0
  119. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/__init__.py +0 -0
  120. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/__init__.py +0 -0
  121. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/__init__.py +0 -0
  122. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/admin.py +0 -0
  123. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/api.py +0 -0
  124. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/apps.py +0 -0
  125. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/data.py +0 -0
  126. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/import_export/__init__.py +0 -0
  127. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/import_export/actions.py +0 -0
  128. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/import_export/api.py +0 -0
  129. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/import_export/exceptions.py +0 -0
  130. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/import_export/import_plan.py +0 -0
  131. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/import_export/parsers.py +0 -0
  132. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/import_export/tasks.py +0 -0
  133. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/import_export/template.csv +0 -0
  134. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/import_export/template.json +0 -0
  135. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/migrations/0001_initial.py +0 -0
  136. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/migrations/0001_squashed.py +0 -0
  137. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/migrations/0002_auto_20230718_2026.py +0 -0
  138. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/migrations/0003_auto_20230721_1238.py +0 -0
  139. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/migrations/0004_auto_20230723_2001.py +0 -0
  140. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/migrations/0005_language_taxonomy.py +0 -0
  141. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/migrations/0006_alter_objecttag_unique_together.py +0 -0
  142. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/migrations/0006_auto_20230802_1631.py +0 -0
  143. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/migrations/0007_tag_import_task_log_null_fix.py +0 -0
  144. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/migrations/0008_taxonomy_description_not_null.py +0 -0
  145. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/migrations/0009_alter_objecttag_object_id.py +0 -0
  146. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/migrations/0010_cleanups.py +0 -0
  147. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/migrations/0011_remove_required.py +0 -0
  148. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/migrations/0012_language_taxonomy.py +0 -0
  149. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/migrations/0013_tag_parent_blank.py +0 -0
  150. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/migrations/0014_minor_fixes.py +0 -0
  151. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/migrations/0015_taxonomy_export_id.py +0 -0
  152. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/migrations/0016_object_tag_export_id.py +0 -0
  153. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/migrations/0017_alter_tagimporttask_status.py +0 -0
  154. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/migrations/0018_objecttag_is_copied.py +0 -0
  155. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/migrations/__init__.py +0 -0
  156. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/models/__init__.py +0 -0
  157. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/models/base.py +0 -0
  158. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/models/import_export.py +0 -0
  159. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/models/system_defined.py +0 -0
  160. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/rest_api/__init__.py +0 -0
  161. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/rest_api/paginators.py +0 -0
  162. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/rest_api/urls.py +0 -0
  163. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/rest_api/utils.py +0 -0
  164. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/rest_api/v1/__init__.py +0 -0
  165. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/rest_api/v1/permissions.py +0 -0
  166. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/rest_api/v1/serializers.py +0 -0
  167. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/rest_api/v1/urls.py +0 -0
  168. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/rest_api/v1/views.py +0 -0
  169. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/rest_api/v1/views_import.py +0 -0
  170. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/rules.py +0 -0
  171. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/core/tagging/urls.py +0 -0
  172. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/openedx_tagging/py.typed +0 -0
  173. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/requirements/base.in +0 -0
  174. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/setup.cfg +0 -0
  175. {openedx_learning-0.27.0 → openedx_learning-0.28.0}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openedx-learning
3
- Version: 0.27.0
3
+ Version: 0.28.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,13 +19,13 @@ 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: Django
23
- Requires-Dist: rules<4.0
24
22
  Requires-Dist: celery
25
- Requires-Dist: djangorestframework<4.0
26
- Requires-Dist: attrs
27
- Requires-Dist: tomlkit
23
+ Requires-Dist: rules<4.0
24
+ Requires-Dist: Django
28
25
  Requires-Dist: edx-drf-extensions
26
+ Requires-Dist: tomlkit
27
+ Requires-Dist: attrs
28
+ Requires-Dist: djangorestframework<4.0
29
29
  Dynamic: author
30
30
  Dynamic: author-email
31
31
  Dynamic: classifier
@@ -2,4 +2,4 @@
2
2
  Open edX Learning ("Learning Core").
3
3
  """
4
4
 
5
- __version__ = "0.27.0"
5
+ __version__ = "0.28.0"
@@ -9,6 +9,7 @@ APIs.
9
9
  """
10
10
  # These wildcard imports are okay because these api modules declare __all__.
11
11
  # pylint: disable=wildcard-import
12
+ from ..apps.authoring.backup_restore.api import *
12
13
  from ..apps.authoring.collections.api import *
13
14
  from ..apps.authoring.components.api import *
14
15
  from ..apps.authoring.contents.api import *
@@ -0,0 +1,15 @@
1
+ """
2
+ Backup Restore API
3
+ """
4
+ from openedx_learning.apps.authoring.backup_restore.zipper import LearningPackageZipper
5
+ from openedx_learning.apps.authoring.publishing.api import get_learning_package_by_key
6
+
7
+
8
+ def create_zip_file(lp_key: str, path: str) -> None:
9
+ """
10
+ Creates a zip file with a toml file so far (WIP)
11
+
12
+ Can throw a NotFoundError at get_learning_package_by_key
13
+ """
14
+ learning_package = get_learning_package_by_key(lp_key)
15
+ LearningPackageZipper(learning_package).create_zip(path)
@@ -33,7 +33,6 @@ class Command(BaseCommand):
33
33
  self.stdout.write(self.style.SUCCESS(message))
34
34
  except LearningPackage.DoesNotExist as exc:
35
35
  message = f"Learning package with key {lp_key} not found"
36
- logger.exception(message)
37
36
  raise CommandError(message) from exc
38
37
  except Exception as e:
39
38
  message = f"Failed to export learning package '{lp_key}': {e}"
@@ -0,0 +1,76 @@
1
+ """
2
+ TOML serialization for learning packages and publishable entities.
3
+ """
4
+
5
+ from datetime import datetime
6
+
7
+ import tomlkit
8
+
9
+ from openedx_learning.apps.authoring.publishing import api as publishing_api
10
+ from openedx_learning.apps.authoring.publishing.models import PublishableEntity, PublishableEntityVersion
11
+ from openedx_learning.apps.authoring.publishing.models.learning_package import LearningPackage
12
+
13
+
14
+ def toml_learning_package(learning_package: LearningPackage) -> str:
15
+ """Create a TOML representation of the learning package."""
16
+ doc = tomlkit.document()
17
+ doc.add(tomlkit.comment(f"Datetime of the export: {datetime.now()}"))
18
+ section = tomlkit.table()
19
+ section.add("title", learning_package.title)
20
+ section.add("key", learning_package.key)
21
+ section.add("description", learning_package.description)
22
+ section.add("created", learning_package.created)
23
+ section.add("updated", learning_package.updated)
24
+ doc.add("learning_package", section)
25
+ return tomlkit.dumps(doc)
26
+
27
+
28
+ def toml_publishable_entity(entity: PublishableEntity) -> str:
29
+ """Create a TOML representation of a publishable entity."""
30
+
31
+ current_draft_version = publishing_api.get_draft_version(entity)
32
+ current_published_version = publishing_api.get_published_version(entity)
33
+
34
+ doc = tomlkit.document()
35
+ entity_table = tomlkit.table()
36
+ entity_table.add("uuid", str(entity.uuid))
37
+ entity_table.add("can_stand_alone", entity.can_stand_alone)
38
+
39
+ if current_draft_version:
40
+ draft_table = tomlkit.table()
41
+ draft_table.add("version_num", current_draft_version.version_num)
42
+ entity_table.add("draft", draft_table)
43
+
44
+ published_table = tomlkit.table()
45
+ if current_published_version:
46
+ published_table.add("version_num", current_published_version.version_num)
47
+ else:
48
+ published_table.add(tomlkit.comment("unpublished: no published_version_num"))
49
+ entity_table.add("published", published_table)
50
+
51
+ doc.add("entity", entity_table)
52
+ doc.add(tomlkit.nl())
53
+ doc.add(tomlkit.comment("### Versions"))
54
+
55
+ for entity_version in entity.versions.all():
56
+ version = tomlkit.aot()
57
+ version_table = toml_publishable_entity_version(entity_version)
58
+ version.append(version_table)
59
+ doc.add("version", version)
60
+
61
+ return tomlkit.dumps(doc)
62
+
63
+
64
+ def toml_publishable_entity_version(version: PublishableEntityVersion) -> tomlkit.items.Table:
65
+ """Create a TOML representation of a publishable entity version."""
66
+ version_table = tomlkit.table()
67
+ version_table.add("title", version.title)
68
+ version_table.add("uuid", str(version.uuid))
69
+ version_table.add("version_num", version.version_num)
70
+ container_table = tomlkit.table()
71
+ container_table.add("children", [])
72
+ unit_table = tomlkit.table()
73
+ unit_table.add("graded", True)
74
+ container_table.add("unit", unit_table)
75
+ version_table.add("container", container_table)
76
+ return version_table # For use in AoT
@@ -0,0 +1,242 @@
1
+ """
2
+ This module provides functionality to create a zip file containing the learning package data,
3
+ including a TOML representation of the learning package and its entities.
4
+ """
5
+ import hashlib
6
+ import zipfile
7
+ from pathlib import Path
8
+ from typing import List, Optional
9
+
10
+ from django.db.models import Prefetch, QuerySet
11
+ from django.utils.text import slugify
12
+
13
+ from openedx_learning.api.authoring_models import (
14
+ ComponentVersion,
15
+ ComponentVersionContent,
16
+ Content,
17
+ LearningPackage,
18
+ PublishableEntity,
19
+ PublishableEntityVersion,
20
+ )
21
+ from openedx_learning.apps.authoring.backup_restore.toml import toml_learning_package, toml_publishable_entity
22
+ from openedx_learning.apps.authoring.publishing import api as publishing_api
23
+
24
+ TOML_PACKAGE_NAME = "package.toml"
25
+
26
+
27
+ def slugify_hashed_filename(identifier: str) -> str:
28
+ """
29
+ Generate a filesystem-safe filename from an identifier.
30
+
31
+ Why:
32
+ Identifiers may contain characters that are invalid or ambiguous
33
+ in filesystems (e.g., slashes, colons, case differences).
34
+ Additionally, two different identifiers might normalize to the same
35
+ slug after cleaning. To avoid collisions and ensure uniqueness,
36
+ we append a short blake2b hash.
37
+
38
+ What:
39
+ - Slugify the identifier (preserves most characters, only strips
40
+ filesystem-invalid ones).
41
+ - Append a short hash for uniqueness.
42
+ - Result: human-readable but still unique and filesystem-safe filename.
43
+ """
44
+ slug = slugify(identifier, allow_unicode=True)
45
+ # Short digest ensures uniqueness without overly long filenames
46
+ short_hash = hashlib.blake2b(
47
+ identifier.encode("utf-8"),
48
+ digest_size=3,
49
+ ).hexdigest()
50
+ return f"{slug}_{short_hash}"
51
+
52
+
53
+ class LearningPackageZipper:
54
+ """
55
+ A class to handle the zipping of learning content for backup and restore.
56
+ """
57
+
58
+ def __init__(self, learning_package: LearningPackage):
59
+ self.learning_package = learning_package
60
+ self.folders_already_created: set[Path] = set()
61
+
62
+ def create_folder(self, folder_path: Path, zip_file: zipfile.ZipFile) -> None:
63
+ """
64
+ Create a folder for the zip file structure.
65
+ Skips creating the folder if it already exists based on the folder path.
66
+ Args:
67
+ folder_path (Path): The path of the folder to create.
68
+ """
69
+ if folder_path not in self.folders_already_created:
70
+ zip_info = zipfile.ZipInfo(str(folder_path) + "/")
71
+ zip_file.writestr(zip_info, "") # Add explicit empty directory entry
72
+ self.folders_already_created.add(folder_path)
73
+
74
+ def get_publishable_entities(self) -> QuerySet[PublishableEntity]:
75
+ """
76
+ Retrieve the publishable entities associated with the learning package.
77
+ Prefetches related data for efficiency.
78
+ """
79
+ lp_id = self.learning_package.pk
80
+ publishable_entities: QuerySet[PublishableEntity] = publishing_api.get_publishable_entities(lp_id)
81
+ return (
82
+ publishable_entities
83
+ .select_related(
84
+ "container",
85
+ "component__component_type",
86
+ "draft__version__componentversion",
87
+ "published__version__componentversion",
88
+ )
89
+ .prefetch_related(
90
+ # We should re-evaluate the prefetching strategy here,
91
+ # as the current approach may cause performance issues—
92
+ # especially with large libraries (up to 100K items),
93
+ # which is too large for this type of prefetch.
94
+ Prefetch(
95
+ "draft__version__componentversion__componentversioncontent_set",
96
+ queryset=ComponentVersionContent.objects.select_related("content"),
97
+ to_attr="prefetched_contents",
98
+ ),
99
+ Prefetch(
100
+ "published__version__componentversion__componentversioncontent_set",
101
+ queryset=ComponentVersionContent.objects.select_related("content"),
102
+ to_attr="prefetched_contents",
103
+ ),
104
+ )
105
+ )
106
+
107
+ def get_versions_to_write(self, entity: PublishableEntity):
108
+ """
109
+ Get the versions of a publishable entity that should be written to the zip file.
110
+ It retrieves both draft and published versions.
111
+ """
112
+ draft_version: Optional[PublishableEntityVersion] = publishing_api.get_draft_version(entity)
113
+ published_version: Optional[PublishableEntityVersion] = publishing_api.get_published_version(entity)
114
+
115
+ versions_to_write = [draft_version] if draft_version else []
116
+
117
+ if published_version and published_version != draft_version:
118
+ versions_to_write.append(published_version)
119
+ return versions_to_write
120
+
121
+ def create_zip(self, path: str) -> None:
122
+ """
123
+ Creates a zip file containing the learning package data.
124
+ Args:
125
+ path (str): The path where the zip file will be created.
126
+ Raises:
127
+ Exception: If the learning package cannot be found or if the zip creation fails.
128
+ """
129
+
130
+ with zipfile.ZipFile(path, "w", compression=zipfile.ZIP_DEFLATED) as zipf:
131
+ # Add the package.toml file
132
+ package_toml_content: str = toml_learning_package(self.learning_package)
133
+ zipf.writestr(TOML_PACKAGE_NAME, package_toml_content)
134
+
135
+ # Add the entities directory
136
+ entities_folder = Path("entities")
137
+ self.create_folder(entities_folder, zipf)
138
+
139
+ # Add the collections directory
140
+ collections_folder = Path("collections")
141
+ self.create_folder(collections_folder, zipf)
142
+
143
+ # ------ ENTITIES SERIALIZATION -------------
144
+
145
+ # get the publishable entities
146
+ publishable_entities: QuerySet[PublishableEntity] = self.get_publishable_entities()
147
+
148
+ for entity in publishable_entities:
149
+ # entity: PublishableEntity = entity # Type hint for clarity
150
+
151
+ # Create a TOML representation of the entity
152
+ entity_toml_content: str = toml_publishable_entity(entity)
153
+
154
+ if hasattr(entity, 'container'):
155
+ entity_slugify_hash = slugify_hashed_filename(entity.key)
156
+ entity_toml_filename = f"{entity_slugify_hash}.toml"
157
+ entity_toml_path = entities_folder / entity_toml_filename
158
+ zipf.writestr(str(entity_toml_path), entity_toml_content)
159
+
160
+ if hasattr(entity, 'component'):
161
+ # Create the component folder structure for the entity. The structure is as follows:
162
+ # entities/
163
+ # xblock.v1/ (component namespace)
164
+ # html/ (component type)
165
+ # my_component.toml (entity TOML file)
166
+ # my_component/ (component id)
167
+ # component_versions/
168
+ # v1/
169
+ # static/
170
+
171
+ # Generate the slugified hash for the component local key
172
+ # Example: if the local key is "my_component", the slugified hash might be "my_component_123456"
173
+ # It's a combination of the local key and a hash and should be unique
174
+ entity_slugify_hash = slugify_hashed_filename(entity.component.local_key)
175
+
176
+ # Create the component namespace folder
177
+ # Example of component namespace is: "entities/xblock.v1/"
178
+ component_namespace_folder = entities_folder / entity.component.component_type.namespace
179
+ self.create_folder(component_namespace_folder, zipf)
180
+
181
+ # Create the component type folder
182
+ # Example of component type is: "entities/xblock.v1/html/"
183
+ component_type_folder = component_namespace_folder / entity.component.component_type.name
184
+ self.create_folder(component_type_folder, zipf)
185
+
186
+ # Create the component id folder
187
+ # Example of component id is: "entities/xblock.v1/html/my_component_123456/"
188
+ component_id_folder = component_type_folder / entity_slugify_hash
189
+ self.create_folder(component_id_folder, zipf)
190
+
191
+ # Add the entity TOML file inside the component type folder as well
192
+ # Example: "entities/xblock.v1/html/my_component_123456.toml"
193
+ component_entity_toml_path = component_type_folder / f"{entity_slugify_hash}.toml"
194
+ zipf.writestr(str(component_entity_toml_path), entity_toml_content)
195
+
196
+ # Add component version folder into the component id folder
197
+ # Example: "entities/xblock.v1/html/my_component_123456/component_versions/"
198
+ component_version_folder = component_id_folder / "component_versions"
199
+ self.create_folder(component_version_folder, zipf)
200
+
201
+ # ------ COMPONENT VERSIONING -------------
202
+ # Focusing on draft and published versions
203
+
204
+ # Get the draft and published versions
205
+ versions_to_write: List[PublishableEntityVersion] = self.get_versions_to_write(entity)
206
+
207
+ for version in versions_to_write:
208
+ # Create a folder for the version
209
+ version_number = f"v{version.version_num}"
210
+ version_folder = component_version_folder / version_number
211
+ self.create_folder(version_folder, zipf)
212
+
213
+ # Add static folder for the version
214
+ static_folder = version_folder / "static"
215
+ self.create_folder(static_folder, zipf)
216
+
217
+ # ------ COMPONENT STATIC CONTENT -------------
218
+ component_version: ComponentVersion = version.componentversion
219
+
220
+ # Get content data associated with this version
221
+ contents: QuerySet[
222
+ ComponentVersionContent
223
+ ] = component_version.prefetched_contents # type: ignore[attr-defined]
224
+
225
+ for component_version_content in contents:
226
+ content: Content = component_version_content.content
227
+
228
+ # Important: The component_version_content.key contains implicitly
229
+ # the file name and the file extension
230
+ file_path = version_folder / component_version_content.key
231
+
232
+ if content.has_file and content.path:
233
+ # If has_file, we pull it from the file system
234
+ with content.read_file() as f:
235
+ file_data = f.read()
236
+ elif not content.has_file and content.text:
237
+ # Otherwise, we use the text content as the file data
238
+ file_data = content.text
239
+ else:
240
+ # If no file and no text, we skip this content
241
+ continue
242
+ zipf.writestr(str(file_path), file_data)
@@ -18,7 +18,7 @@ class ComponentsConfig(AppConfig):
18
18
  """
19
19
  Register Component and ComponentVersion.
20
20
  """
21
- from ..publishing.api import register_content_models # pylint: disable=import-outside-toplevel
21
+ from ..publishing.api import register_publishable_models # pylint: disable=import-outside-toplevel
22
22
  from .models import Component, ComponentVersion # pylint: disable=import-outside-toplevel
23
23
 
24
- register_content_models(Component, ComponentVersion)
24
+ register_publishable_models(Component, ComponentVersion)
@@ -10,7 +10,7 @@ from contextlib import nullcontext
10
10
  from dataclasses import dataclass
11
11
  from datetime import datetime, timezone
12
12
  from enum import Enum
13
- from typing import ContextManager, TypeVar
13
+ from typing import ContextManager, Optional, TypeVar
14
14
 
15
15
  from django.core.exceptions import ObjectDoesNotExist, ValidationError
16
16
  from django.db.models import F, Q, QuerySet
@@ -58,6 +58,7 @@ __all__ = [
58
58
  "create_publishable_entity_version",
59
59
  "get_publishable_entity",
60
60
  "get_publishable_entity_by_key",
61
+ "get_publishable_entities",
61
62
  "get_last_publish",
62
63
  "get_all_drafts",
63
64
  "get_entities_with_unpublished_changes",
@@ -69,7 +70,7 @@ __all__ = [
69
70
  "set_draft_version",
70
71
  "soft_delete_draft",
71
72
  "reset_drafts_to_published",
72
- "register_content_models",
73
+ "register_publishable_models",
73
74
  "filter_publishable_entities",
74
75
  # 🛑 UNSTABLE: All APIs related to containers are unstable until we've figured
75
76
  # out our approach to dynamic content (randomized, A/B tests, etc.)
@@ -261,6 +262,20 @@ def get_all_drafts(learning_package_id: int, /) -> QuerySet[Draft]:
261
262
  )
262
263
 
263
264
 
265
+ def get_publishable_entities(learning_package_id: int, /) -> QuerySet[PublishableEntity]:
266
+ """
267
+ Get all entities in a learning package.
268
+ """
269
+ return (
270
+ PublishableEntity.objects
271
+ .filter(learning_package_id=learning_package_id)
272
+ .select_related(
273
+ "draft__version",
274
+ "published__version",
275
+ )
276
+ )
277
+
278
+
264
279
  def get_entities_with_unpublished_changes(
265
280
  learning_package_id: int,
266
281
  /,
@@ -417,15 +432,22 @@ def publish_from_drafts(
417
432
  return publish_log
418
433
 
419
434
 
420
- def get_draft_version(publishable_entity_id: int, /) -> PublishableEntityVersion | None:
435
+ def get_draft_version(publishable_entity_or_id: PublishableEntity | int, /) -> PublishableEntityVersion | None:
421
436
  """
422
437
  Return current draft PublishableEntityVersion for this PublishableEntity.
423
438
 
424
439
  This function will return None if there is no current draft.
425
440
  """
441
+ if isinstance(publishable_entity_or_id, PublishableEntity):
442
+ # Fetches the draft version for a given PublishableEntity.
443
+ # Gracefully handles cases where no draft is present.
444
+ draft: Optional[Draft] = getattr(publishable_entity_or_id, "draft", None)
445
+ if draft is None:
446
+ return None
447
+ return draft.version
426
448
  try:
427
449
  draft = Draft.objects.select_related("version").get(
428
- entity_id=publishable_entity_id
450
+ entity_id=publishable_entity_or_id
429
451
  )
430
452
  except Draft.DoesNotExist:
431
453
  # No draft was ever created.
@@ -437,15 +459,22 @@ def get_draft_version(publishable_entity_id: int, /) -> PublishableEntityVersion
437
459
  return draft.version
438
460
 
439
461
 
440
- def get_published_version(publishable_entity_id: int, /) -> PublishableEntityVersion | None:
462
+ def get_published_version(publishable_entity_or_id: PublishableEntity | int, /) -> PublishableEntityVersion | None:
441
463
  """
442
464
  Return current published PublishableEntityVersion for this PublishableEntity.
443
465
 
444
466
  This function will return None if there is no current published version.
445
467
  """
468
+ if isinstance(publishable_entity_or_id, PublishableEntity):
469
+ # Fetches the published version for a given PublishableEntity.
470
+ # Gracefully handles cases where no published version is present.
471
+ published: Optional[Published] = getattr(publishable_entity_or_id, "published", None)
472
+ if published is None:
473
+ return None
474
+ return published.version
446
475
  try:
447
476
  published = Published.objects.select_related("version").get(
448
- entity_id=publishable_entity_id
477
+ entity_id=publishable_entity_or_id
449
478
  )
450
479
  except Published.DoesNotExist:
451
480
  return None
@@ -789,7 +818,7 @@ def reset_drafts_to_published(
789
818
  set_draft_version(draft, published_version_id)
790
819
 
791
820
 
792
- def register_content_models(
821
+ def register_publishable_models(
793
822
  content_model_cls: type[PublishableEntityMixin],
794
823
  content_version_model_cls: type[PublishableEntityVersionMixin],
795
824
  ) -> PublishableContentModelRegistry:
@@ -805,10 +834,10 @@ def register_content_models(
805
834
  method. For example, in the components app, this looks like:
806
835
 
807
836
  def ready(self):
808
- from ..publishing.api import register_content_models
837
+ from ..publishing.api import register_publishable_models
809
838
  from .models import Component, ComponentVersion
810
839
 
811
- register_content_models(Component, ComponentVersion)
840
+ register_publishable_models(Component, ComponentVersion)
812
841
 
813
842
  There may be a more clever way to introspect this information from the model
814
843
  metadata, but this is simple and explicit.
@@ -1275,6 +1304,7 @@ def get_entities_in_container(
1275
1304
  container: Container,
1276
1305
  *,
1277
1306
  published: bool,
1307
+ select_related_version: str | None = None,
1278
1308
  ) -> list[ContainerEntityListEntry]:
1279
1309
  """
1280
1310
  [ 🛑 UNSTABLE ]
@@ -1285,14 +1315,35 @@ def get_entities_in_container(
1285
1315
  container: The Container, e.g. returned by `get_container()`
1286
1316
  published: `True` if we want the published version of the container, or
1287
1317
  `False` for the draft version.
1318
+ select_related_version: An optional optimization; specify a relationship
1319
+ on ContainerVersion, like `componentversion` or `containerversion__x`
1320
+ to preload via select_related.
1288
1321
  """
1289
1322
  assert isinstance(container, Container)
1290
- container_version = container.versioning.published if published else container.versioning.draft
1323
+ if published:
1324
+ # Very minor optimization: reload the container with related 1:1 entities
1325
+ container = Container.objects.select_related(
1326
+ "publishable_entity__published__version__containerversion__entity_list").get(pk=container.pk)
1327
+ container_version = container.versioning.published
1328
+ select_related = ["entity__published__version"]
1329
+ if select_related_version:
1330
+ select_related.append(f"entity__published__version__{select_related_version}")
1331
+ else:
1332
+ # Very minor optimization: reload the container with related 1:1 entities
1333
+ container = Container.objects.select_related(
1334
+ "publishable_entity__draft__version__containerversion__entity_list").get(pk=container.pk)
1335
+ container_version = container.versioning.draft
1336
+ select_related = ["entity__draft__version"]
1337
+ if select_related_version:
1338
+ select_related.append(f"entity__draft__version__{select_related_version}")
1291
1339
  if container_version is None:
1292
1340
  raise ContainerVersion.DoesNotExist # This container has not been published yet, or has been deleted.
1293
1341
  assert isinstance(container_version, ContainerVersion)
1294
- entity_list = []
1295
- for row in container_version.entity_list.entitylistrow_set.order_by("order_num"):
1342
+ entity_list: list[ContainerEntityListEntry] = []
1343
+ for row in container_version.entity_list.entitylistrow_set.select_related(
1344
+ "entity_version",
1345
+ *select_related,
1346
+ ).order_by("order_num"):
1296
1347
  entity_version = row.entity_version # This will be set if pinned
1297
1348
  if not entity_version: # If this entity is "unpinned", use the latest published/draft version:
1298
1349
  entity_version = row.entity.published.version if published else row.entity.draft.version
@@ -1385,7 +1436,10 @@ def get_containers_with_entity(
1385
1436
  qs = Container.objects.filter(
1386
1437
  publishable_entity__draft__version__containerversion__entity_list__entitylistrow__entity_id=publishable_entity_pk, # pylint: disable=line-too-long # noqa: E501
1387
1438
  )
1388
- return qs.order_by("pk").distinct() # Ordering is mostly for consistent test cases.
1439
+ return qs.select_related(
1440
+ "publishable_entity__draft__version__containerversion",
1441
+ "publishable_entity__published__version__containerversion",
1442
+ ).order_by("pk").distinct() # Ordering is mostly for consistent test cases.
1389
1443
 
1390
1444
 
1391
1445
  def get_container_children_count(
@@ -19,7 +19,7 @@ class PublishingConfig(AppConfig):
19
19
  """
20
20
  Register Container and ContainerVersion.
21
21
  """
22
- from .api import register_content_models # pylint: disable=import-outside-toplevel
22
+ from .api import register_publishable_models # pylint: disable=import-outside-toplevel
23
23
  from .models import Container, ContainerVersion # pylint: disable=import-outside-toplevel
24
24
 
25
- register_content_models(Container, ContainerVersion)
25
+ register_publishable_models(Container, ContainerVersion)
@@ -272,7 +272,7 @@ class PublishableEntityMixin(models.Model):
272
272
  Please see docstring for PublishableEntity for more details.
273
273
 
274
274
  If you use this class, you *MUST* also use PublishableEntityVersionMixin and
275
- the publishing app's api.register_content_models (see its docstring for
275
+ the publishing app's api.register_publishable_models (see its docstring for
276
276
  details).
277
277
  """
278
278
  # select these related entities by default for all queries
@@ -294,6 +294,10 @@ class PublishableEntityMixin(models.Model):
294
294
  def uuid(self) -> str:
295
295
  return self.publishable_entity.uuid
296
296
 
297
+ @property
298
+ def can_stand_alone(self) -> bool:
299
+ return self.publishable_entity.can_stand_alone
300
+
297
301
  @property
298
302
  def key(self) -> str:
299
303
  return self.publishable_entity.key
@@ -551,7 +555,7 @@ class PublishableEntityVersionMixin(models.Model):
551
555
  Please see docstring for PublishableEntityVersion for more details.
552
556
 
553
557
  If you use this class, you *MUST* also use PublishableEntityMixin and the
554
- publishing app's api.register_content_models (see its docstring for
558
+ publishing app's api.register_publishable_models (see its docstring for
555
559
  details).
556
560
  """
557
561
 
@@ -609,7 +613,7 @@ class PublishableContentModelRegistry:
609
613
  Register what content model maps to what content version model.
610
614
 
611
615
  If you want to call this from another app, please use the
612
- ``register_content_models`` function in this app's ``api`` module
616
+ ``register_publishable_models`` function in this app's ``api`` module
613
617
  instead.
614
618
  """
615
619
  if not issubclass(content_model_cls, PublishableEntityMixin):
@@ -257,7 +257,12 @@ def get_subsections_in_section(
257
257
  """
258
258
  assert isinstance(section, Section)
259
259
  subsections = []
260
- for entry in publishing_api.get_entities_in_container(section, published=published):
260
+ entries = publishing_api.get_entities_in_container(
261
+ section,
262
+ published=published,
263
+ select_related_version="containerversion__subsectionversion",
264
+ )
265
+ for entry in entries:
261
266
  # Convert from generic PublishableEntityVersion to SubsectionVersion:
262
267
  subsection_version = entry.entity_version.containerversion.subsectionversion
263
268
  assert isinstance(subsection_version, SubsectionVersion)
@@ -19,7 +19,7 @@ class SectionsConfig(AppConfig):
19
19
  """
20
20
  Register Section and SectionVersion.
21
21
  """
22
- from ..publishing.api import register_content_models # pylint: disable=import-outside-toplevel
22
+ from ..publishing.api import register_publishable_models # pylint: disable=import-outside-toplevel
23
23
  from .models import Section, SectionVersion # pylint: disable=import-outside-toplevel
24
24
 
25
- register_content_models(Section, SectionVersion)
25
+ register_publishable_models(Section, SectionVersion)
@@ -256,7 +256,12 @@ def get_units_in_subsection(
256
256
  """
257
257
  assert isinstance(subsection, Subsection)
258
258
  units = []
259
- for entry in publishing_api.get_entities_in_container(subsection, published=published):
259
+ entries = publishing_api.get_entities_in_container(
260
+ subsection,
261
+ published=published,
262
+ select_related_version="containerversion__unitversion",
263
+ )
264
+ for entry in entries:
260
265
  # Convert from generic PublishableEntityVersion to UnitVersion:
261
266
  unit_version = entry.entity_version.containerversion.unitversion
262
267
  assert isinstance(unit_version, UnitVersion)