openedx-learning 0.28.0__tar.gz → 0.29.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.
- {openedx_learning-0.28.0/openedx_learning.egg-info → openedx_learning-0.29.0}/PKG-INFO +14 -5
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/README.rst +9 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/__init__.py +1 -1
- openedx_learning-0.29.0/openedx_learning/apps/authoring/backup_restore/api.py +30 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/backup_restore/management/commands/lp_dump.py +22 -4
- openedx_learning-0.29.0/openedx_learning/apps/authoring/backup_restore/management/commands/lp_load.py +57 -0
- openedx_learning-0.29.0/openedx_learning/apps/authoring/backup_restore/serializers.py +168 -0
- openedx_learning-0.29.0/openedx_learning/apps/authoring/backup_restore/toml.py +254 -0
- openedx_learning-0.29.0/openedx_learning/apps/authoring/backup_restore/zipper.py +1044 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/components/api.py +55 -0
- openedx_learning-0.29.0/openedx_learning/apps/authoring/components/migrations/0004_remove_componentversioncontent_uuid.py +17 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/components/models.py +1 -3
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/publishing/api.py +36 -4
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/sections/api.py +17 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/subsections/api.py +17 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/units/api.py +17 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0/openedx_learning.egg-info}/PKG-INFO +14 -5
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning.egg-info/SOURCES.txt +3 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/models/base.py +7 -5
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/models/utils.py +1 -1
- openedx_learning-0.28.0/openedx_learning/apps/authoring/backup_restore/api.py +0 -15
- openedx_learning-0.28.0/openedx_learning/apps/authoring/backup_restore/toml.py +0 -76
- openedx_learning-0.28.0/openedx_learning/apps/authoring/backup_restore/zipper.py +0 -242
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/CHANGELOG.rst +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/LICENSE.txt +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/MANIFEST.in +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/api/__init__.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/api/authoring.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/api/authoring_models.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/__init__.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/__init__.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/backup_restore/__init__.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/backup_restore/admin.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/backup_restore/apps.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/backup_restore/management/__init__.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/backup_restore/management/commands/__init__.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/backup_restore/migrations/__init__.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/backup_restore/models.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/collections/__init__.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/collections/admin.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/collections/api.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/collections/apps.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/collections/migrations/0001_initial.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/collections/migrations/0002_remove_collection_name_collection_created_by_and_more.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/collections/migrations/0003_collection_entities.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/collections/migrations/0004_collection_key.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/collections/migrations/0005_alter_collection_options_alter_collection_enabled.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/collections/migrations/__init__.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/collections/models.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/components/__init__.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/components/admin.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/components/apps.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/components/management/__init__.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/components/management/commands/__init__.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/components/management/commands/add_assets_to_component.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/components/migrations/0001_initial.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/components/migrations/0002_alter_componentversioncontent_key.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/components/migrations/0003_remove_componentversioncontent_learner_downloadable.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/components/migrations/__init__.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/contents/__init__.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/contents/admin.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/contents/api.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/contents/apps.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/contents/migrations/0001_initial.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/contents/migrations/__init__.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/contents/models.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/publishing/__init__.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/publishing/admin.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/publishing/apps.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/publishing/contextmanagers.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/publishing/migrations/0001_initial.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/publishing/migrations/0002_alter_learningpackage_key_and_more.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/publishing/migrations/0003_containers.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/publishing/migrations/0004_publishableentity_can_stand_alone.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/publishing/migrations/0005_alter_entitylistrow_options.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/publishing/migrations/0006_draftchangelog.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/publishing/migrations/0007_bootstrap_draftchangelog.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/publishing/migrations/0008_alter_draftchangelogrecord_options_and_more.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/publishing/migrations/__init__.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/publishing/models/__init__.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/publishing/models/container.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/publishing/models/draft_log.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/publishing/models/entity_list.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/publishing/models/learning_package.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/publishing/models/publish_log.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/publishing/models/publishable_entity.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/sections/__init__.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/sections/admin.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/sections/apps.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/sections/migrations/0001_initial.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/sections/migrations/__init__.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/sections/models.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/subsections/__init__.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/subsections/admin.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/subsections/apps.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/subsections/migrations/0001_initial.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/subsections/migrations/__init__.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/subsections/models.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/units/__init__.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/units/admin.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/units/apps.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/units/migrations/0001_initial.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/units/migrations/__init__.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/apps/authoring/units/models.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/contrib/__init__.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/contrib/media_server/__init__.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/contrib/media_server/apps.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/contrib/media_server/urls.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/contrib/media_server/views.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/lib/__init__.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/lib/admin_utils.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/lib/cache.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/lib/collations.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/lib/fields.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/lib/managers.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/lib/test_utils.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/lib/validators.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning/py.typed +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning.egg-info/dependency_links.txt +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning.egg-info/not-zip-safe +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning.egg-info/requires.txt +4 -4
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_learning.egg-info/top_level.txt +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/__init__.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/__init__.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/__init__.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/admin.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/api.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/apps.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/data.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/import_export/__init__.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/import_export/actions.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/import_export/api.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/import_export/exceptions.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/import_export/import_plan.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/import_export/parsers.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/import_export/tasks.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/import_export/template.csv +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/import_export/template.json +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/migrations/0001_initial.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/migrations/0001_squashed.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/migrations/0002_auto_20230718_2026.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/migrations/0003_auto_20230721_1238.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/migrations/0004_auto_20230723_2001.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/migrations/0005_language_taxonomy.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/migrations/0006_alter_objecttag_unique_together.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/migrations/0006_auto_20230802_1631.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/migrations/0007_tag_import_task_log_null_fix.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/migrations/0008_taxonomy_description_not_null.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/migrations/0009_alter_objecttag_object_id.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/migrations/0010_cleanups.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/migrations/0011_remove_required.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/migrations/0012_language_taxonomy.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/migrations/0013_tag_parent_blank.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/migrations/0014_minor_fixes.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/migrations/0015_taxonomy_export_id.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/migrations/0016_object_tag_export_id.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/migrations/0017_alter_tagimporttask_status.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/migrations/0018_objecttag_is_copied.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/migrations/__init__.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/models/__init__.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/models/import_export.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/models/system_defined.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/rest_api/__init__.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/rest_api/paginators.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/rest_api/urls.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/rest_api/utils.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/rest_api/v1/__init__.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/rest_api/v1/permissions.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/rest_api/v1/serializers.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/rest_api/v1/urls.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/rest_api/v1/views.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/rest_api/v1/views_import.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/rules.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/core/tagging/urls.py +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/openedx_tagging/py.typed +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/requirements/base.in +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/setup.cfg +0 -0
- {openedx_learning-0.28.0 → openedx_learning-0.29.0}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openedx-learning
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.29.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: djangorestframework<4.0
|
|
23
|
+
Requires-Dist: edx-drf-extensions
|
|
24
|
+
Requires-Dist: attrs
|
|
22
25
|
Requires-Dist: celery
|
|
26
|
+
Requires-Dist: tomlkit
|
|
23
27
|
Requires-Dist: rules<4.0
|
|
24
28
|
Requires-Dist: Django
|
|
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
|
|
@@ -148,6 +148,15 @@ Every time you develop something in this repo
|
|
|
148
148
|
|
|
149
149
|
# Open a PR and ask for review.
|
|
150
150
|
|
|
151
|
+
Configuring Visual Studio Code
|
|
152
|
+
------------------------------
|
|
153
|
+
|
|
154
|
+
If you are using VS Code as your editor, you can enable the Testing bar by copying from the example configuration provided in the ``.vscode`` directory::
|
|
155
|
+
|
|
156
|
+
cd .vscode/
|
|
157
|
+
cp launch.json.example launch.json
|
|
158
|
+
cp settings.json.example settings.json
|
|
159
|
+
|
|
151
160
|
License
|
|
152
161
|
-------
|
|
153
162
|
|
|
@@ -108,6 +108,15 @@ Every time you develop something in this repo
|
|
|
108
108
|
|
|
109
109
|
# Open a PR and ask for review.
|
|
110
110
|
|
|
111
|
+
Configuring Visual Studio Code
|
|
112
|
+
------------------------------
|
|
113
|
+
|
|
114
|
+
If you are using VS Code as your editor, you can enable the Testing bar by copying from the example configuration provided in the ``.vscode`` directory::
|
|
115
|
+
|
|
116
|
+
cd .vscode/
|
|
117
|
+
cp launch.json.example launch.json
|
|
118
|
+
cp settings.json.example settings.json
|
|
119
|
+
|
|
111
120
|
License
|
|
112
121
|
-------
|
|
113
122
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Backup Restore API
|
|
3
|
+
"""
|
|
4
|
+
import zipfile
|
|
5
|
+
|
|
6
|
+
from django.contrib.auth.models import User as UserType # pylint: disable=imported-auth-user
|
|
7
|
+
|
|
8
|
+
from openedx_learning.apps.authoring.backup_restore.zipper import LearningPackageUnzipper, LearningPackageZipper
|
|
9
|
+
from openedx_learning.apps.authoring.publishing.api import get_learning_package_by_key
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def create_zip_file(lp_key: str, path: str, user: UserType | None = None) -> None:
|
|
13
|
+
"""
|
|
14
|
+
Creates a dump zip file for the given learning package key at the given path.
|
|
15
|
+
The zip file contains a TOML representation of the learning package and its contents.
|
|
16
|
+
|
|
17
|
+
Can throw a NotFoundError at get_learning_package_by_key
|
|
18
|
+
"""
|
|
19
|
+
learning_package = get_learning_package_by_key(lp_key)
|
|
20
|
+
LearningPackageZipper(learning_package, user).create_zip(path)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def load_learning_package(path: str, key: str | None = None, user: UserType | None = None) -> dict:
|
|
24
|
+
"""
|
|
25
|
+
Loads a learning package from a zip file at the given path.
|
|
26
|
+
Restores the learning package and its contents to the database.
|
|
27
|
+
Returns a dictionary with the status of the operation and any errors encountered.
|
|
28
|
+
"""
|
|
29
|
+
with zipfile.ZipFile(path, "r") as zipf:
|
|
30
|
+
return LearningPackageUnzipper(zipf, key, user).load()
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Django management commands to handle backup
|
|
2
|
+
Django management commands to handle backup learning packages (WIP)
|
|
3
3
|
"""
|
|
4
4
|
import logging
|
|
5
|
+
import time
|
|
5
6
|
|
|
7
|
+
from django.contrib.auth import get_user_model
|
|
6
8
|
from django.core.management import CommandError
|
|
7
9
|
from django.core.management.base import BaseCommand
|
|
8
10
|
|
|
@@ -12,6 +14,9 @@ from openedx_learning.apps.authoring.publishing.api import LearningPackage
|
|
|
12
14
|
logger = logging.getLogger(__name__)
|
|
13
15
|
|
|
14
16
|
|
|
17
|
+
User = get_user_model()
|
|
18
|
+
|
|
19
|
+
|
|
15
20
|
class Command(BaseCommand):
|
|
16
21
|
"""
|
|
17
22
|
Django management command to export a learning package to a zip file.
|
|
@@ -21,15 +26,28 @@ class Command(BaseCommand):
|
|
|
21
26
|
def add_arguments(self, parser):
|
|
22
27
|
parser.add_argument('lp_key', type=str, help='The key of the LearningPackage to dump')
|
|
23
28
|
parser.add_argument('file_name', type=str, help='The name of the output zip file')
|
|
29
|
+
parser.add_argument(
|
|
30
|
+
'--username',
|
|
31
|
+
type=str,
|
|
32
|
+
help='The username of the user performing the backup operation.',
|
|
33
|
+
default=None
|
|
34
|
+
)
|
|
24
35
|
|
|
25
36
|
def handle(self, *args, **options):
|
|
26
37
|
lp_key = options['lp_key']
|
|
27
38
|
file_name = options['file_name']
|
|
28
|
-
|
|
39
|
+
username = options['username']
|
|
40
|
+
if not file_name.lower().endswith(".zip"):
|
|
29
41
|
raise CommandError("Output file name must end with .zip")
|
|
30
42
|
try:
|
|
31
|
-
|
|
32
|
-
|
|
43
|
+
# Get the user performing the operation
|
|
44
|
+
user = None
|
|
45
|
+
if username:
|
|
46
|
+
user = User.objects.get(username=username)
|
|
47
|
+
start_time = time.time()
|
|
48
|
+
create_zip_file(lp_key, file_name, user=user)
|
|
49
|
+
elapsed = time.time() - start_time
|
|
50
|
+
message = f'{lp_key} written to {file_name} (create_zip_file: {elapsed:.2f} seconds)'
|
|
33
51
|
self.stdout.write(self.style.SUCCESS(message))
|
|
34
52
|
except LearningPackage.DoesNotExist as exc:
|
|
35
53
|
message = f"Learning package with key {lp_key} not found"
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Django management commands to handle restore learning packages (WIP)
|
|
3
|
+
"""
|
|
4
|
+
import logging
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
from django.contrib.auth import get_user_model
|
|
8
|
+
from django.core.management import CommandError
|
|
9
|
+
from django.core.management.base import BaseCommand
|
|
10
|
+
|
|
11
|
+
from openedx_learning.apps.authoring.backup_restore.api import load_learning_package
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
User = get_user_model()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Command(BaseCommand):
|
|
19
|
+
"""
|
|
20
|
+
Django management command to load a learning package from a zip file.
|
|
21
|
+
"""
|
|
22
|
+
help = 'Load a learning package from a zip file.'
|
|
23
|
+
|
|
24
|
+
def add_arguments(self, parser):
|
|
25
|
+
parser.add_argument('file_name', type=str, help='The path of the input zip file to load.')
|
|
26
|
+
parser.add_argument('username', type=str, help='The username of the user performing the load operation.')
|
|
27
|
+
|
|
28
|
+
def handle(self, *args, **options):
|
|
29
|
+
file_name = options['file_name']
|
|
30
|
+
username = options['username']
|
|
31
|
+
if not file_name.lower().endswith(".zip"):
|
|
32
|
+
raise CommandError("Input file name must end with .zip")
|
|
33
|
+
try:
|
|
34
|
+
start_time = time.time()
|
|
35
|
+
# Get the user performing the operation
|
|
36
|
+
user = User.objects.get(username=username)
|
|
37
|
+
|
|
38
|
+
result = load_learning_package(file_name, user=user)
|
|
39
|
+
duration = time.time() - start_time
|
|
40
|
+
if result["status"] == "error":
|
|
41
|
+
message = "Errors encountered during restore:\n"
|
|
42
|
+
log_buffer = result.get("log_file_error")
|
|
43
|
+
if log_buffer:
|
|
44
|
+
message += log_buffer.getvalue()
|
|
45
|
+
raise CommandError(message)
|
|
46
|
+
message = f'{file_name} loaded successfully (duration: {duration:.2f} seconds)'
|
|
47
|
+
self.stdout.write(self.style.SUCCESS(message))
|
|
48
|
+
except FileNotFoundError as exc:
|
|
49
|
+
message = f"Learning package file {file_name} not found: {exc}"
|
|
50
|
+
raise CommandError(message) from exc
|
|
51
|
+
except Exception as e:
|
|
52
|
+
message = f"Failed to load '{file_name}': {e}"
|
|
53
|
+
logger.exception(
|
|
54
|
+
"Failed to load zip file %s ",
|
|
55
|
+
file_name,
|
|
56
|
+
)
|
|
57
|
+
raise CommandError(message) from e
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The serializers module for restoration of authoring data.
|
|
3
|
+
"""
|
|
4
|
+
from datetime import timezone
|
|
5
|
+
|
|
6
|
+
from rest_framework import serializers
|
|
7
|
+
|
|
8
|
+
from openedx_learning.apps.authoring.components import api as components_api
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class LearningPackageSerializer(serializers.Serializer): # pylint: disable=abstract-method
|
|
12
|
+
"""
|
|
13
|
+
Serializer for learning packages.
|
|
14
|
+
|
|
15
|
+
Note:
|
|
16
|
+
The `key` field is serialized, but it is generally not trustworthy for restoration.
|
|
17
|
+
During restore, a new key may be generated or overridden.
|
|
18
|
+
"""
|
|
19
|
+
title = serializers.CharField(required=True)
|
|
20
|
+
key = serializers.CharField(required=True)
|
|
21
|
+
description = serializers.CharField(required=True, allow_blank=True)
|
|
22
|
+
created = serializers.DateTimeField(required=True, default_timezone=timezone.utc)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class LearningPackageMetadataSerializer(serializers.Serializer): # pylint: disable=abstract-method
|
|
26
|
+
"""
|
|
27
|
+
Serializer for learning package metadata.
|
|
28
|
+
|
|
29
|
+
Note:
|
|
30
|
+
This serializer handles data exported to an archive (e.g., during backup),
|
|
31
|
+
but the metadata is not restored to the database and is meant solely for inspection.
|
|
32
|
+
"""
|
|
33
|
+
format_version = serializers.IntegerField(required=True)
|
|
34
|
+
created_by = serializers.CharField(required=False, allow_null=True)
|
|
35
|
+
created_by_email = serializers.EmailField(required=False, allow_null=True)
|
|
36
|
+
created_at = serializers.DateTimeField(required=True, default_timezone=timezone.utc)
|
|
37
|
+
origin_server = serializers.CharField(required=False, allow_null=True)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class EntitySerializer(serializers.Serializer): # pylint: disable=abstract-method
|
|
41
|
+
"""
|
|
42
|
+
Serializer for publishable entities.
|
|
43
|
+
"""
|
|
44
|
+
can_stand_alone = serializers.BooleanField(required=True)
|
|
45
|
+
key = serializers.CharField(required=True)
|
|
46
|
+
created = serializers.DateTimeField(required=True, default_timezone=timezone.utc)
|
|
47
|
+
created_by = serializers.CharField(required=True, allow_null=True)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class EntityVersionSerializer(serializers.Serializer): # pylint: disable=abstract-method
|
|
51
|
+
"""
|
|
52
|
+
Serializer for publishable entity versions.
|
|
53
|
+
"""
|
|
54
|
+
title = serializers.CharField(required=True)
|
|
55
|
+
entity_key = serializers.CharField(required=True)
|
|
56
|
+
created = serializers.DateTimeField(required=True, default_timezone=timezone.utc)
|
|
57
|
+
created_by = serializers.CharField(required=True, allow_null=True)
|
|
58
|
+
version_num = serializers.IntegerField(required=True)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class ComponentSerializer(EntitySerializer): # pylint: disable=abstract-method
|
|
62
|
+
"""
|
|
63
|
+
Serializer for components.
|
|
64
|
+
Contains logic to convert entity_key to component_type and local_key.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
def validate(self, attrs):
|
|
68
|
+
"""
|
|
69
|
+
Custom validation logic:
|
|
70
|
+
parse the entity_key into (component_type, local_key).
|
|
71
|
+
"""
|
|
72
|
+
entity_key = attrs["key"]
|
|
73
|
+
try:
|
|
74
|
+
component_type_obj, local_key = components_api.get_or_create_component_type_by_entity_key(entity_key)
|
|
75
|
+
attrs["component_type"] = component_type_obj
|
|
76
|
+
attrs["local_key"] = local_key
|
|
77
|
+
except ValueError as exc:
|
|
78
|
+
raise serializers.ValidationError({"key": str(exc)})
|
|
79
|
+
return attrs
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class ComponentVersionSerializer(EntityVersionSerializer): # pylint: disable=abstract-method
|
|
83
|
+
"""
|
|
84
|
+
Serializer for component versions.
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class ContainerSerializer(EntitySerializer): # pylint: disable=abstract-method
|
|
89
|
+
"""
|
|
90
|
+
Serializer for containers.
|
|
91
|
+
"""
|
|
92
|
+
container = serializers.DictField(required=True)
|
|
93
|
+
|
|
94
|
+
def validate_container(self, value):
|
|
95
|
+
"""
|
|
96
|
+
Custom validation logic for the container field.
|
|
97
|
+
Ensures that the container dict has exactly one key which is one of
|
|
98
|
+
"section", "subsection", or "unit" values.
|
|
99
|
+
"""
|
|
100
|
+
errors = []
|
|
101
|
+
if not isinstance(value, dict) or len(value) != 1:
|
|
102
|
+
errors.append("Container must be a dict with exactly one key.")
|
|
103
|
+
if len(value) == 1: # Only check the key if there is exactly one
|
|
104
|
+
container_type = list(value.keys())[0]
|
|
105
|
+
if container_type not in ("section", "subsection", "unit"):
|
|
106
|
+
errors.append(f"Invalid container value: {container_type}")
|
|
107
|
+
if errors:
|
|
108
|
+
raise serializers.ValidationError(errors)
|
|
109
|
+
return value
|
|
110
|
+
|
|
111
|
+
def validate(self, attrs):
|
|
112
|
+
"""
|
|
113
|
+
Custom validation logic:
|
|
114
|
+
parse the container dict to extract the container type.
|
|
115
|
+
"""
|
|
116
|
+
container = attrs["container"]
|
|
117
|
+
container_type = list(container.keys())[0] # It is safe to do this after validate_container
|
|
118
|
+
attrs["container_type"] = container_type
|
|
119
|
+
attrs.pop("container") # Remove the container field after processing
|
|
120
|
+
return attrs
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class ContainerVersionSerializer(EntityVersionSerializer): # pylint: disable=abstract-method
|
|
124
|
+
"""
|
|
125
|
+
Serializer for container versions.
|
|
126
|
+
"""
|
|
127
|
+
container = serializers.DictField(required=True)
|
|
128
|
+
|
|
129
|
+
def validate_container(self, value):
|
|
130
|
+
"""
|
|
131
|
+
Custom validation logic for the container field.
|
|
132
|
+
Ensures that the container dict has exactly one key "children" which is a list of strings.
|
|
133
|
+
"""
|
|
134
|
+
errors = []
|
|
135
|
+
if not isinstance(value, dict) or len(value) != 1:
|
|
136
|
+
errors.append("Container must be a dict with exactly one key.")
|
|
137
|
+
if "children" not in value:
|
|
138
|
+
errors.append("Container must have a 'children' key.")
|
|
139
|
+
if "children" in value and not isinstance(value["children"], list):
|
|
140
|
+
errors.append("'children' must be a list.")
|
|
141
|
+
if errors:
|
|
142
|
+
raise serializers.ValidationError(errors)
|
|
143
|
+
return value
|
|
144
|
+
|
|
145
|
+
def validate(self, attrs):
|
|
146
|
+
"""
|
|
147
|
+
Custom validation logic:
|
|
148
|
+
parse the container dict to extract the children list.
|
|
149
|
+
"""
|
|
150
|
+
children = attrs["container"]["children"] # It is safe to do this after validate_container
|
|
151
|
+
attrs["children"] = children
|
|
152
|
+
attrs.pop("container") # Remove the container field after processing
|
|
153
|
+
return attrs
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class CollectionSerializer(serializers.Serializer): # pylint: disable=abstract-method
|
|
157
|
+
"""
|
|
158
|
+
Serializer for collections.
|
|
159
|
+
"""
|
|
160
|
+
title = serializers.CharField(required=True)
|
|
161
|
+
key = serializers.CharField(required=True)
|
|
162
|
+
description = serializers.CharField(required=True, allow_blank=True)
|
|
163
|
+
created_by = serializers.IntegerField(required=True, allow_null=True)
|
|
164
|
+
entities = serializers.ListField(
|
|
165
|
+
child=serializers.CharField(),
|
|
166
|
+
required=True,
|
|
167
|
+
allow_empty=True,
|
|
168
|
+
)
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TOML serialization for learning packages and publishable entities.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from typing import Any, Dict
|
|
7
|
+
|
|
8
|
+
import tomlkit
|
|
9
|
+
from django.contrib.auth.models import User as UserType # pylint: disable=imported-auth-user
|
|
10
|
+
|
|
11
|
+
from openedx_learning.apps.authoring.collections.models import Collection
|
|
12
|
+
from openedx_learning.apps.authoring.publishing import api as publishing_api
|
|
13
|
+
from openedx_learning.apps.authoring.publishing.models import PublishableEntity, PublishableEntityVersion
|
|
14
|
+
from openedx_learning.apps.authoring.publishing.models.learning_package import LearningPackage
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def toml_learning_package(
|
|
18
|
+
learning_package: LearningPackage,
|
|
19
|
+
timestamp: datetime,
|
|
20
|
+
format_version: int = 1,
|
|
21
|
+
user: UserType | None = None,
|
|
22
|
+
origin_server: str | None = None
|
|
23
|
+
) -> str:
|
|
24
|
+
"""
|
|
25
|
+
Create a TOML representation of the learning package.
|
|
26
|
+
|
|
27
|
+
The resulting content looks like:
|
|
28
|
+
[meta]
|
|
29
|
+
format_version = 1
|
|
30
|
+
created_by = "dormsbee"
|
|
31
|
+
created_at = 2025-09-03T17:50:59.536190Z
|
|
32
|
+
origin_server = "cms.test"
|
|
33
|
+
|
|
34
|
+
[learning_package]
|
|
35
|
+
title = "Components Test Case Learning Package"
|
|
36
|
+
key = "ComponentTestCase-test-key"
|
|
37
|
+
description = "This is a test learning package for components."
|
|
38
|
+
created = 2025-09-03T17:50:59.536190Z
|
|
39
|
+
updated = 2025-09-03T17:50:59.536190Z
|
|
40
|
+
"""
|
|
41
|
+
doc = tomlkit.document()
|
|
42
|
+
|
|
43
|
+
# Learning package main info
|
|
44
|
+
section = tomlkit.table()
|
|
45
|
+
section.add("title", learning_package.title)
|
|
46
|
+
section.add("key", learning_package.key)
|
|
47
|
+
section.add("description", learning_package.description)
|
|
48
|
+
section.add("created", learning_package.created)
|
|
49
|
+
section.add("updated", learning_package.updated)
|
|
50
|
+
|
|
51
|
+
# Learning package metadata
|
|
52
|
+
metadata = tomlkit.table()
|
|
53
|
+
metadata.add("format_version", format_version)
|
|
54
|
+
if user:
|
|
55
|
+
metadata.add("created_by", user.username)
|
|
56
|
+
metadata.add("created_by_email", user.email)
|
|
57
|
+
metadata.add("created_at", timestamp)
|
|
58
|
+
if origin_server:
|
|
59
|
+
metadata.add("origin_server", origin_server)
|
|
60
|
+
|
|
61
|
+
doc.add("meta", metadata)
|
|
62
|
+
doc.add("learning_package", section)
|
|
63
|
+
return tomlkit.dumps(doc)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _get_toml_publishable_entity_table(
|
|
67
|
+
entity: PublishableEntity,
|
|
68
|
+
draft_version: PublishableEntityVersion | None,
|
|
69
|
+
published_version: PublishableEntityVersion | None,
|
|
70
|
+
include_versions: bool = True) -> tomlkit.items.Table:
|
|
71
|
+
"""
|
|
72
|
+
Create a TOML representation of a publishable entity.
|
|
73
|
+
|
|
74
|
+
The resulting content looks like:
|
|
75
|
+
[entity]
|
|
76
|
+
can_stand_alone = true
|
|
77
|
+
key = "xblock.v1:problem:my_published_example"
|
|
78
|
+
|
|
79
|
+
[entity.draft]
|
|
80
|
+
version_num = 2
|
|
81
|
+
|
|
82
|
+
[entity.published]
|
|
83
|
+
version_num = 1
|
|
84
|
+
|
|
85
|
+
[entity.container.section]
|
|
86
|
+
|
|
87
|
+
Note: This function returns a tomlkit.items.Table, which represents
|
|
88
|
+
a string-like TOML fragment rather than a complete TOML document.
|
|
89
|
+
"""
|
|
90
|
+
entity_table = tomlkit.table()
|
|
91
|
+
entity_table.add("can_stand_alone", entity.can_stand_alone)
|
|
92
|
+
# Add key since the toml filename doesn't show the real key
|
|
93
|
+
entity_table.add("key", entity.key)
|
|
94
|
+
entity_table.add("created", entity.created)
|
|
95
|
+
|
|
96
|
+
if not include_versions:
|
|
97
|
+
return entity_table
|
|
98
|
+
|
|
99
|
+
if draft_version:
|
|
100
|
+
draft_table = tomlkit.table()
|
|
101
|
+
draft_table.add("version_num", draft_version.version_num)
|
|
102
|
+
entity_table.add("draft", draft_table)
|
|
103
|
+
|
|
104
|
+
published_table = tomlkit.table()
|
|
105
|
+
if published_version:
|
|
106
|
+
published_table.add("version_num", published_version.version_num)
|
|
107
|
+
else:
|
|
108
|
+
published_table.add(tomlkit.comment("unpublished: no published_version_num"))
|
|
109
|
+
entity_table.add("published", published_table)
|
|
110
|
+
|
|
111
|
+
if hasattr(entity, "container"):
|
|
112
|
+
container_table = tomlkit.table()
|
|
113
|
+
container_types = ["section", "subsection", "unit"]
|
|
114
|
+
|
|
115
|
+
for container_type in container_types:
|
|
116
|
+
if hasattr(entity.container, container_type):
|
|
117
|
+
container_table.add(container_type, tomlkit.table())
|
|
118
|
+
break # stop after the first match
|
|
119
|
+
|
|
120
|
+
entity_table.add("container", container_table)
|
|
121
|
+
|
|
122
|
+
return entity_table
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def toml_publishable_entity(
|
|
126
|
+
entity: PublishableEntity,
|
|
127
|
+
versions_to_write: list[PublishableEntityVersion],
|
|
128
|
+
draft_version: PublishableEntityVersion | None,
|
|
129
|
+
published_version: PublishableEntityVersion | None) -> str:
|
|
130
|
+
"""
|
|
131
|
+
Create a TOML representation of a publishable entity and its versions.
|
|
132
|
+
|
|
133
|
+
The resulting content looks like:
|
|
134
|
+
[entity]
|
|
135
|
+
can_stand_alone = true
|
|
136
|
+
key = "xblock.v1:problem:my_published_example"
|
|
137
|
+
|
|
138
|
+
[entity.draft]
|
|
139
|
+
version_num = 2
|
|
140
|
+
|
|
141
|
+
[entity.published]
|
|
142
|
+
version_num = 1
|
|
143
|
+
|
|
144
|
+
[entity.container.section] (if applicable)
|
|
145
|
+
|
|
146
|
+
# ### Versions
|
|
147
|
+
|
|
148
|
+
[[version]]
|
|
149
|
+
title = "My published problem"
|
|
150
|
+
version_num = 1
|
|
151
|
+
|
|
152
|
+
[version.container] (if applicable)
|
|
153
|
+
children = []
|
|
154
|
+
"""
|
|
155
|
+
# Create the TOML representation for the entity itself
|
|
156
|
+
entity_table = _get_toml_publishable_entity_table(entity, draft_version, published_version)
|
|
157
|
+
doc = tomlkit.document()
|
|
158
|
+
doc.add("entity", entity_table)
|
|
159
|
+
|
|
160
|
+
# Add versions as an array of tables (AoT)
|
|
161
|
+
doc.add(tomlkit.nl())
|
|
162
|
+
doc.add(tomlkit.comment("### Versions"))
|
|
163
|
+
for entity_version in versions_to_write:
|
|
164
|
+
version = tomlkit.aot()
|
|
165
|
+
version_table = toml_publishable_entity_version(entity_version)
|
|
166
|
+
version.append(version_table)
|
|
167
|
+
doc.add("version", version)
|
|
168
|
+
|
|
169
|
+
return tomlkit.dumps(doc)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def toml_publishable_entity_version(version: PublishableEntityVersion) -> tomlkit.items.Table:
|
|
173
|
+
"""
|
|
174
|
+
Create a TOML representation of a publishable entity version.
|
|
175
|
+
|
|
176
|
+
The resulting content looks like:
|
|
177
|
+
[[version]]
|
|
178
|
+
title = "My published problem"
|
|
179
|
+
version_num = 1
|
|
180
|
+
|
|
181
|
+
[version.container] (if applicable)
|
|
182
|
+
children = []
|
|
183
|
+
|
|
184
|
+
Note: This function returns a tomlkit.items.Table, which represents
|
|
185
|
+
a string-like TOML fragment rather than a complete TOML document.
|
|
186
|
+
"""
|
|
187
|
+
version_table = tomlkit.table()
|
|
188
|
+
version_table.add("title", version.title)
|
|
189
|
+
version_table.add("version_num", version.version_num)
|
|
190
|
+
|
|
191
|
+
if hasattr(version, 'containerversion'):
|
|
192
|
+
# If the version has a container version, add its children
|
|
193
|
+
container_table = tomlkit.table()
|
|
194
|
+
children = publishing_api.get_container_children_entities_keys(version.containerversion)
|
|
195
|
+
container_table.add("children", children)
|
|
196
|
+
version_table.add("container", container_table)
|
|
197
|
+
return version_table
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def toml_collection(collection: Collection, entity_keys: list[str]) -> str:
|
|
201
|
+
"""
|
|
202
|
+
Create a TOML representation of a collection.
|
|
203
|
+
|
|
204
|
+
The resulting content looks like:
|
|
205
|
+
[collection]
|
|
206
|
+
title = "Collection 1"
|
|
207
|
+
key = "COL1"
|
|
208
|
+
description = "Description of Collection 1"
|
|
209
|
+
created = 2025-09-03T22:28:53.839362Z
|
|
210
|
+
entities = [
|
|
211
|
+
"xblock.v1:problem:my_published_example",
|
|
212
|
+
"xblock.v1:html:my_draft_example",
|
|
213
|
+
]
|
|
214
|
+
"""
|
|
215
|
+
doc = tomlkit.document()
|
|
216
|
+
|
|
217
|
+
entities_array = tomlkit.array()
|
|
218
|
+
entities_array.extend(entity_keys)
|
|
219
|
+
entities_array.multiline(True)
|
|
220
|
+
|
|
221
|
+
collection_table = tomlkit.table()
|
|
222
|
+
collection_table.add("title", collection.title)
|
|
223
|
+
collection_table.add("key", collection.key)
|
|
224
|
+
collection_table.add("description", collection.description)
|
|
225
|
+
collection_table.add("created", collection.created)
|
|
226
|
+
collection_table.add("entities", entities_array)
|
|
227
|
+
|
|
228
|
+
doc.add("collection", collection_table)
|
|
229
|
+
|
|
230
|
+
return tomlkit.dumps(doc)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def parse_learning_package_toml(content: str) -> dict:
|
|
234
|
+
"""
|
|
235
|
+
Parse the learning package TOML content and return a dict of its fields.
|
|
236
|
+
"""
|
|
237
|
+
lp_data: Dict[str, Any] = tomlkit.parse(content)
|
|
238
|
+
return lp_data
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def parse_publishable_entity_toml(content: str) -> dict:
|
|
242
|
+
"""
|
|
243
|
+
Parse the publishable entity TOML file and return a dict of its fields.
|
|
244
|
+
"""
|
|
245
|
+
pe_data: Dict[str, Any] = tomlkit.parse(content)
|
|
246
|
+
return pe_data
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def parse_collection_toml(content: str) -> dict:
|
|
250
|
+
"""
|
|
251
|
+
Parse the collection TOML content and return a dict of its fields.
|
|
252
|
+
"""
|
|
253
|
+
collection_data: Dict[str, Any] = tomlkit.parse(content)
|
|
254
|
+
return collection_data
|