diff --git a/.annotation_safe_list.yml b/.annotation_safe_list.yml index 54d8341fd..ed349529f 100644 --- a/.annotation_safe_list.yml +++ b/.annotation_safe_list.yml @@ -19,43 +19,43 @@ auth.User: ".. pii_retirement": "consumer_api" contenttypes.ContentType: ".. no_pii:": "This model has no PII" -oel_collections.Collection: +oel_authoring.Collection: ".. no_pii:": "This model has no PII" -oel_collections.CollectionPublishableEntity: +oel_authoring.CollectionPublishableEntity: ".. no_pii:": "This model has no PII" -oel_components.Component: +oel_authoring.Component: ".. no_pii:": "This model has no PII" -oel_components.ComponentType: +oel_authoring.ComponentType: ".. no_pii:": "This model has no PII" -oel_components.ComponentVersion: +oel_authoring.ComponentVersion: ".. no_pii:": "This model has no PII" -oel_components.ComponentVersionContent: +oel_authoring.ComponentVersionContent: ".. no_pii:": "This model has no PII" -oel_contents.Content: +oel_authoring.Content: ".. no_pii:": "This model has no PII" -oel_contents.MediaType: +oel_authoring.MediaType: ".. no_pii:": "This model has no PII" -oel_publishing.Container: +oel_authoring.Container: ".. no_pii:": "This model has no PII" -oel_publishing.ContainerVersion: +oel_authoring.ContainerVersion: ".. no_pii:": "This model has no PII" -oel_publishing.Draft: +oel_authoring.Draft: ".. no_pii:": "This model has no PII" -oel_publishing.EntityList: +oel_authoring.EntityList: ".. no_pii:": "This model has no PII" -oel_publishing.EntityListRow: +oel_authoring.EntityListRow: ".. no_pii:": "This model has no PII" -oel_publishing.LearningPackage: +oel_authoring.LearningPackage: ".. no_pii:": "This model has no PII" -oel_publishing.PublishLog: +oel_authoring.PublishLog: ".. no_pii:": "This model has no PII" -oel_publishing.PublishLogRecord: +oel_authoring.PublishLogRecord: ".. no_pii:": "This model has no PII" -oel_publishing.PublishableEntity: +oel_authoring.PublishableEntity: ".. no_pii:": "This model has no PII" -oel_publishing.PublishableEntityVersion: +oel_authoring.PublishableEntityVersion: ".. no_pii:": "This model has no PII" -oel_publishing.Published: +oel_authoring.Published: ".. no_pii:": "This model has no PII" oel_tagging.ObjectTag: ".. no_pii:": "This model has no PII" @@ -65,17 +65,17 @@ oel_tagging.TagImportTask: ".. no_pii:": "This model has no PII" oel_tagging.Taxonomy: ".. no_pii:": "This model has no PII" -oel_sections.Section: +oel_authoring.Section: ".. no_pii:": "This model has no PII" -oel_sections.SectionVersion: +oel_authoring.SectionVersion: ".. no_pii:": "This model has no PII" -oel_subsections.Subsection: +oel_authoring.Subsection: ".. no_pii:": "This model has no PII" -oel_subsections.SubsectionVersion: +oel_authoring.SubsectionVersion: ".. no_pii:": "This model has no PII" -oel_units.Unit: +oel_authoring.Unit: ".. no_pii:": "This model has no PII" -oel_units.UnitVersion: +oel_authoring.UnitVersion: ".. no_pii:": "This model has no PII" social_django.Association: ".. no_pii:": "This model has no PII" diff --git a/.gitignore b/.gitignore index 6123dd4cf..faa881b9a 100644 --- a/.gitignore +++ b/.gitignore @@ -75,7 +75,12 @@ venv/ !.vscode/settings.json.example # Media files (for uploads) -media/ +/media/ # Media files generated during test runs -test_media/ +/test_media/ + +# uv stuff +.lock +CACHEDIR.TAG +pyvenv.cfg diff --git a/.importlinter b/.importlinter index 6c278d0dd..6b14b4a31 100644 --- a/.importlinter +++ b/.importlinter @@ -36,24 +36,24 @@ layers= openedx_learning.api.authoring # The "backup_restore" app handle the new export and import mechanism. - openedx_learning.apps.authoring.backup_restore + openedx_learning.apps.authoring.applets.backup_restore # The "components" app is responsible for storing versioned Components, # which is Open edX Studio terminology maps to things like individual # Problems, Videos, and blocks of HTML text. This is also the type we would # associate with a single "leaf" XBlock–one that is not a container type and # has no child elements. - openedx_learning.apps.authoring.components + openedx_learning.apps.authoring.applets.components # The "contents" app stores the simplest pieces of binary and text data, # without versioning information. These belong to a single Learning Package. - openedx_learning.apps.authoring.contents + openedx_learning.apps.authoring.applets.contents # The "collections" app stores arbitrary groupings of PublishableEntities. # Its only dependency should be the publishing app. - openedx_learning.apps.authoring.collections + openedx_learning.apps.authoring.applets.collections # The lowest layer is "publishing", which holds the basic primitives needed # to create Learning Packages and manage the draft and publish states for # various types of content. - openedx_learning.apps.authoring.publishing + openedx_learning.apps.authoring.applets.publishing diff --git a/docs/decisions/0016-python-public-api-conventions.rst b/docs/decisions/0016-python-public-api-conventions.rst index 20bb1f766..23644a47d 100644 --- a/docs/decisions/0016-python-public-api-conventions.rst +++ b/docs/decisions/0016-python-public-api-conventions.rst @@ -20,7 +20,7 @@ Learning Core Django apps will be grouped into packages. Apps in ``openedx_learning`` will be grouped into broadly related packages under ``openedx_learning.apps``. The first of these groups will be "authoring" (``openedx_learning.apps.authoring``). Future packages may include "learner", "personalization", "activity", "grading", etc. Learning Core Django apps will continue to have their own ``api`` modules. - So for example, ``openedx_learning.apps.authoring.components.api`` will continue to exist. + So for example, ``openedx_learning.apps.authoring.applets.components.api`` will continue to exist. Learning Core will have a top level package for its public API. All public APIs intended for use by consumers of Learning Core will be represented as modules in the ``openedx_learning.api`` package that corresponds to the app groupings (e.g. ``openedx_learning.api.authoring``). @@ -35,14 +35,14 @@ App ``api`` modules will define their public functions using ``__all__``. This relies on the individual apps to properly set ``__all__`` to the list of functions that they are willing to publicly support. App ``api`` modules within a package of apps still import from each other. - So for example, ``openedx_learning.apps.authoring.components.api`` will continue to import APIs that it needs from ``..publishing.api``, instead of using the public API at ``openedx_learning.api.authoring``. These imports should not use wildcards. + So for example, ``openedx_learning.apps.authoring.applets.components.api`` will continue to import APIs that it needs from ``..publishing.api``, instead of using the public API at ``openedx_learning.api.authoring``. These imports should not use wildcards. Functions and constants that are not listed as part of a module's ``__all__`` may still be imported by other app APIs in the same package grouping. This should allow a package more flexibility to create provisional APIs that we may not want to support publicly. If a function or attribute is intended to be completely private to an app's ``api`` module (i.e. not used even by other apps in its package), it should be prefixed with an underscore. App ``api`` modules should not import directly from apps outside their package. - For example, ``openedx_learning.apps.personalization.api`` should import authoring API functions from ``openedx_learning.api.authoring``, **not** directly from something like ``openedx_learning.apps.authoring.components.api``. This will help to limit the impact of refactoring app package internal changes, as well as exposing shortcomings in the existing public APIs. + For example, ``openedx_learning.apps.personalization.api`` should import authoring API functions from ``openedx_learning.api.authoring``, **not** directly from something like ``openedx_learning.apps.authoring.applets.components.api``. This will help to limit the impact of refactoring app package internal changes, as well as exposing shortcomings in the existing public APIs. Public API modules may implement their own functions. In addition to aggregating app ``api`` modules via wildcard imports, public API modules like ``openedx_learning.api.authoring`` may implement their own functionality. This will be useful for convenience functions that invoke multiple app APIs, and for backwards compatibility shims. When possible, the bulk of the logic for these should continue to live in app-defined APIs, with the public API module acting more as a glue layer. diff --git a/docs/decisions/0020-authoring-as-one-app.rst b/docs/decisions/0020-authoring-as-one-app.rst new file mode 100644 index 000000000..d65ed8b32 --- /dev/null +++ b/docs/decisions/0020-authoring-as-one-app.rst @@ -0,0 +1,49 @@ +20. Authoring as an Umbrella App of Smaller Applets +=================================================== + +Context +------- + +Up to this point, Learning Core has used many small apps with a narrow focus (e.g. ``components``, ``collections``, etc.) in order to make each individual app simpler to reason about. This has been useful overall, but it has made refactoring more cumbersome. For instance: + +#. Moving models between apps is tricky, requiring the use of Django's ``SeparateDatabaseAndState`` functionality to fake a deletion in one app and a creation in another without actually altering the database. It also requires doctoring the migration files for models in other repos that might have foreign key relations to the model being moved, so that they're pointing to the new ``app_label``. This will be an issue when we try to extract container-related models and logic out of publishing and into a new ``containers`` app. +#. Renaming an app is also cumbersome, because the process requires creating a new app and transitioning the models over. This came up when trying to rename the ``contents`` app to ``media``. + +There have also been minor inconveniences, like having a long list of ``INSTALLED_APPS`` to maintain in edx-platform over time, or not having these tables easily grouped together in the Django admin interface. + +Decisions +--------- + +1. Single Authoring App +~~~~~~~~~~~~~~~~~~~~~~~ + +All existing authoring apps will be merged into one Django app (``openedx_learning.app.authoring``). Some consequences of this decision: + +- The tables will be renamed to have the ``oel_authoring`` label prefix. +- All management commands will be moved to the ``authoring`` app. + +2. Logical Separation via Applets +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +We will continue to keep internal API boundaries between individual applets, and use the ``api.py`` modules. This is both to insulate applets from implementation changes in other applets, as well as to provide a set of APIs that third-party plugins can utilize. As before, we will use Import Linter to enforce dependency ordering. + +3. Restructuring Specifics +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In one pull request, we are going to: + +#. Create bare shells of the existing ``authoring`` apps (``backup_restore``, ``collections``, ``components``, ``contents``, ``publishing``, ``sections``, ``subsections``, ``units``), and move them to the ``openedx_learning.apps.authoring.backcompat`` package. These shells will have an ``apps.py`` file and the ``migrations`` package for each existing app. This will allow for a smooth schema migration to transition the models from these individual apps to ``authoring``. +#. Move the actual models files and API logic for our existing authoring apps to the ``openedx_learning.apps.authoring.applets`` package. +#. Convert the top level ``openedx_learning.apps.authoring`` package to be a Django app. The top level ``admin.py``, ``api.py``, and ``models.py`` modules will do wildcard imports from the corresponding modules across all applet packages. + +In terms of model migrations, all existing apps will have a final migration that uses ``SeparateDatabaseAndState`` to remove all model state, but make no actual database changes. After that, the initial ``authoring`` app migration will list all these "deletion" migrations as dependencies, and then also use ``SeparateDatabaseAndState`` to create the model state without doing any actual database operations. The next ``authoring`` app migration will rename all existing +database tables to use the ``oel_authoring`` prefix, for uniformity. + +There are a few edx-platform apps that already have foreign keys and migrations that reference these models. It will be necessary to alter those historical migrations to pretend that these models have always come from the ``authoring`` app (with the label ``oel_authoring``). + +In a future release (no earlier than Willow), we would remove the old apps entirely, and alter the intial ``authoring`` app migration so that it looks like a simple schema creation without state separation. We would also remove all references to the original set of small apps. This shouldn't affect existing installs because the ``authoring`` migration would have already run on those sites. This may require edx-platform apps to alter their migration dependencies to repoint to the initial ``authoring`` migration. + +4. The Bigger Picture +~~~~~~~~~~~~~~~~~~~~~ + +This practice means that the ``authoring`` Django app corresponds to a Subdomain in Domain Driven Design terminology, with each applet being a Bounded Context. We call these "Applets" instead of "Bounded Contexts" because we don't want it to get confused for Django's notion of Contexts and Context Processors (or Python's notion of Context Managers). diff --git a/olx_importer/management/commands/load_components.py b/olx_importer/management/commands/load_components.py index 55cd268c8..a52ce72db 100644 --- a/olx_importer/management/commands/load_components.py +++ b/olx_importer/management/commands/load_components.py @@ -28,9 +28,9 @@ from django.db import transaction # Model references to remove -from openedx_learning.apps.authoring.components import api as components_api -from openedx_learning.apps.authoring.contents import api as contents_api -from openedx_learning.apps.authoring.publishing import api as publishing_api +from openedx_learning.apps.authoring.applets.components import api as components_api +from openedx_learning.apps.authoring.applets.contents import api as contents_api +from openedx_learning.apps.authoring.applets.publishing import api as publishing_api SUPPORTED_TYPES = ["problem", "video", "html"] logger = logging.getLogger(__name__) diff --git a/openedx_learning/__init__.py b/openedx_learning/__init__.py index c7fcd06b1..4457588a2 100644 --- a/openedx_learning/__init__.py +++ b/openedx_learning/__init__.py @@ -2,4 +2,4 @@ Open edX Learning ("Learning Core"). """ -__version__ = "0.30.2" +__version__ = "0.31.0" diff --git a/openedx_learning/api/authoring.py b/openedx_learning/api/authoring.py index 9082b33fb..9b9a70411 100644 --- a/openedx_learning/api/authoring.py +++ b/openedx_learning/api/authoring.py @@ -9,14 +9,7 @@ """ # These wildcard imports are okay because these api modules declare __all__. # pylint: disable=wildcard-import -from ..apps.authoring.backup_restore.api import * -from ..apps.authoring.collections.api import * -from ..apps.authoring.components.api import * -from ..apps.authoring.contents.api import * -from ..apps.authoring.publishing.api import * -from ..apps.authoring.sections.api import * -from ..apps.authoring.subsections.api import * -from ..apps.authoring.units.api import * +from ..apps.authoring.api import * # This was renamed after the authoring API refactoring pushed this and other # app APIs into the openedx_learning.api.authoring module. Here I'm aliasing to diff --git a/openedx_learning/api/authoring_models.py b/openedx_learning/api/authoring_models.py index 617d85dc4..cb7d3bd82 100644 --- a/openedx_learning/api/authoring_models.py +++ b/openedx_learning/api/authoring_models.py @@ -7,10 +7,10 @@ """ # These wildcard imports are okay because these modules declare __all__. # pylint: disable=wildcard-import -from ..apps.authoring.collections.models import * -from ..apps.authoring.components.models import * -from ..apps.authoring.contents.models import * -from ..apps.authoring.publishing.models import * -from ..apps.authoring.sections.models import * -from ..apps.authoring.subsections.models import * -from ..apps.authoring.units.models import * +from ..apps.authoring.applets.collections.models import * +from ..apps.authoring.applets.components.models import * +from ..apps.authoring.applets.contents.models import * +from ..apps.authoring.applets.publishing.models import * +from ..apps.authoring.applets.sections.models import * +from ..apps.authoring.applets.subsections.models import * +from ..apps.authoring.applets.units.models import * diff --git a/openedx_learning/api/django.py b/openedx_learning/api/django.py new file mode 100644 index 000000000..1030ca727 --- /dev/null +++ b/openedx_learning/api/django.py @@ -0,0 +1,23 @@ +""" +Module for parts of the Learning Core API that exist to make it easier to use in +Django projects. +""" + +def learning_core_apps_to_install(): + """ + Return all app names for appending to INSTALLED_APPS. + + This function exists to better insulate edx-platform and potential plugins + over time, as we eventually plan to remove the backcompat apps. + """ + return [ + "openedx_learning.apps.authoring", + "openedx_learning.apps.authoring.backcompat.backup_restore", + "openedx_learning.apps.authoring.backcompat.collections", + "openedx_learning.apps.authoring.backcompat.components", + "openedx_learning.apps.authoring.backcompat.contents", + "openedx_learning.apps.authoring.backcompat.publishing", + "openedx_learning.apps.authoring.backcompat.sections", + "openedx_learning.apps.authoring.backcompat.subsections", + "openedx_learning.apps.authoring.backcompat.units", + ] diff --git a/openedx_learning/apps/authoring/admin.py b/openedx_learning/apps/authoring/admin.py new file mode 100644 index 000000000..f603a5d54 --- /dev/null +++ b/openedx_learning/apps/authoring/admin.py @@ -0,0 +1,13 @@ +""" +This module aggregates all applet Django Admin modules. +""" +# pylint: disable=wildcard-import + +from .applets.backup_restore.admin import * +from .applets.collections.admin import * +from .applets.components.admin import * +from .applets.contents.admin import * +from .applets.publishing.admin import * +from .applets.sections.admin import * +from .applets.subsections.admin import * +from .applets.units.admin import * diff --git a/openedx_learning/apps/authoring/api.py b/openedx_learning/apps/authoring/api.py new file mode 100644 index 000000000..082b369e2 --- /dev/null +++ b/openedx_learning/apps/authoring/api.py @@ -0,0 +1,16 @@ +""" +This module aggregates all applet API modules. + +Question: Should this replace openedx_learning.api.authoring? +""" + +# pylint: disable=wildcard-import + +from .applets.backup_restore.api import * +from .applets.collections.api import * +from .applets.components.api import * +from .applets.contents.api import * +from .applets.publishing.api import * +from .applets.sections.api import * +from .applets.subsections.api import * +from .applets.units.api import * diff --git a/openedx_learning/apps/authoring/backup_restore/__init__.py b/openedx_learning/apps/authoring/applets/__init__.py similarity index 100% rename from openedx_learning/apps/authoring/backup_restore/__init__.py rename to openedx_learning/apps/authoring/applets/__init__.py diff --git a/openedx_learning/apps/authoring/backup_restore/management/__init__.py b/openedx_learning/apps/authoring/applets/backup_restore/__init__.py similarity index 100% rename from openedx_learning/apps/authoring/backup_restore/management/__init__.py rename to openedx_learning/apps/authoring/applets/backup_restore/__init__.py diff --git a/openedx_learning/apps/authoring/backup_restore/admin.py b/openedx_learning/apps/authoring/applets/backup_restore/admin.py similarity index 100% rename from openedx_learning/apps/authoring/backup_restore/admin.py rename to openedx_learning/apps/authoring/applets/backup_restore/admin.py diff --git a/openedx_learning/apps/authoring/backup_restore/api.py b/openedx_learning/apps/authoring/applets/backup_restore/api.py similarity index 84% rename from openedx_learning/apps/authoring/backup_restore/api.py rename to openedx_learning/apps/authoring/applets/backup_restore/api.py index 802bf6ff3..097d86f89 100644 --- a/openedx_learning/apps/authoring/backup_restore/api.py +++ b/openedx_learning/apps/authoring/applets/backup_restore/api.py @@ -5,8 +5,8 @@ from django.contrib.auth.models import User as UserType # pylint: disable=imported-auth-user -from openedx_learning.apps.authoring.backup_restore.zipper import LearningPackageUnzipper, LearningPackageZipper -from openedx_learning.apps.authoring.publishing.api import get_learning_package_by_key +from .zipper import LearningPackageUnzipper, LearningPackageZipper +from ..publishing.api import get_learning_package_by_key def create_zip_file(lp_key: str, path: str, user: UserType | None = None, origin_server: str | None = None) -> None: diff --git a/openedx_learning/apps/authoring/backup_restore/models.py b/openedx_learning/apps/authoring/applets/backup_restore/models.py similarity index 100% rename from openedx_learning/apps/authoring/backup_restore/models.py rename to openedx_learning/apps/authoring/applets/backup_restore/models.py diff --git a/openedx_learning/apps/authoring/backup_restore/serializers.py b/openedx_learning/apps/authoring/applets/backup_restore/serializers.py similarity index 98% rename from openedx_learning/apps/authoring/backup_restore/serializers.py rename to openedx_learning/apps/authoring/applets/backup_restore/serializers.py index c34e81059..d8f7a5c15 100644 --- a/openedx_learning/apps/authoring/backup_restore/serializers.py +++ b/openedx_learning/apps/authoring/applets/backup_restore/serializers.py @@ -5,7 +5,7 @@ from rest_framework import serializers -from openedx_learning.apps.authoring.components import api as components_api +from ..components import api as components_api class LearningPackageSerializer(serializers.Serializer): # pylint: disable=abstract-method diff --git a/openedx_learning/apps/authoring/backup_restore/toml.py b/openedx_learning/apps/authoring/applets/backup_restore/toml.py similarity index 95% rename from openedx_learning/apps/authoring/backup_restore/toml.py rename to openedx_learning/apps/authoring/applets/backup_restore/toml.py index a3ab9a03d..a75e7a0ad 100644 --- a/openedx_learning/apps/authoring/backup_restore/toml.py +++ b/openedx_learning/apps/authoring/applets/backup_restore/toml.py @@ -8,10 +8,10 @@ import tomlkit from django.contrib.auth.models import User as UserType # pylint: disable=imported-auth-user -from openedx_learning.apps.authoring.collections.models import Collection -from openedx_learning.apps.authoring.publishing import api as publishing_api -from openedx_learning.apps.authoring.publishing.models import PublishableEntity, PublishableEntityVersion -from openedx_learning.apps.authoring.publishing.models.learning_package import LearningPackage +from ..collections.models import Collection +from ..publishing import api as publishing_api +from ..publishing.models import PublishableEntity, PublishableEntityVersion +from ..publishing.models.learning_package import LearningPackage def toml_learning_package( diff --git a/openedx_learning/apps/authoring/backup_restore/zipper.py b/openedx_learning/apps/authoring/applets/backup_restore/zipper.py similarity index 98% rename from openedx_learning/apps/authoring/backup_restore/zipper.py rename to openedx_learning/apps/authoring/applets/backup_restore/zipper.py index 27ddcacc3..4040983ae 100644 --- a/openedx_learning/apps/authoring/backup_restore/zipper.py +++ b/openedx_learning/apps/authoring/applets/backup_restore/zipper.py @@ -28,7 +28,7 @@ PublishableEntity, PublishableEntityVersion, ) -from openedx_learning.apps.authoring.backup_restore.serializers import ( +from .serializers import ( CollectionSerializer, ComponentSerializer, ComponentVersionSerializer, @@ -37,7 +37,7 @@ LearningPackageMetadataSerializer, LearningPackageSerializer, ) -from openedx_learning.apps.authoring.backup_restore.toml import ( +from .toml import ( parse_collection_toml, parse_learning_package_toml, parse_publishable_entity_toml, @@ -45,13 +45,13 @@ toml_learning_package, toml_publishable_entity, ) -from openedx_learning.apps.authoring.collections import api as collections_api -from openedx_learning.apps.authoring.components import api as components_api -from openedx_learning.apps.authoring.contents import api as contents_api -from openedx_learning.apps.authoring.publishing import api as publishing_api -from openedx_learning.apps.authoring.sections import api as sections_api -from openedx_learning.apps.authoring.subsections import api as subsections_api -from openedx_learning.apps.authoring.units import api as units_api +from ..collections import api as collections_api +from ..components import api as components_api +from ..contents import api as contents_api +from ..publishing import api as publishing_api +from ..sections import api as sections_api +from ..subsections import api as subsections_api +from ..units import api as units_api TOML_PACKAGE_NAME = "package.toml" DEFAULT_USERNAME = "command" diff --git a/openedx_learning/apps/authoring/backup_restore/management/commands/__init__.py b/openedx_learning/apps/authoring/applets/collections/__init__.py similarity index 100% rename from openedx_learning/apps/authoring/backup_restore/management/commands/__init__.py rename to openedx_learning/apps/authoring/applets/collections/__init__.py diff --git a/openedx_learning/apps/authoring/collections/admin.py b/openedx_learning/apps/authoring/applets/collections/admin.py similarity index 100% rename from openedx_learning/apps/authoring/collections/admin.py rename to openedx_learning/apps/authoring/applets/collections/admin.py diff --git a/openedx_learning/apps/authoring/collections/api.py b/openedx_learning/apps/authoring/applets/collections/api.py similarity index 100% rename from openedx_learning/apps/authoring/collections/api.py rename to openedx_learning/apps/authoring/applets/collections/api.py diff --git a/openedx_learning/apps/authoring/collections/models.py b/openedx_learning/apps/authoring/applets/collections/models.py similarity index 98% rename from openedx_learning/apps/authoring/collections/models.py rename to openedx_learning/apps/authoring/applets/collections/models.py index 731f2cb73..225f2fc20 100644 --- a/openedx_learning/apps/authoring/collections/models.py +++ b/openedx_learning/apps/authoring/applets/collections/models.py @@ -180,7 +180,10 @@ class Meta: ), ] indexes = [ - models.Index(fields=["learning_package", "title"]), + models.Index( + fields=["learning_package", "title"], + name="oel_authoring_coll_lp_title", + ), ] def __repr__(self) -> str: diff --git a/openedx_learning/apps/authoring/collections/readme.rst b/openedx_learning/apps/authoring/applets/collections/readme.rst similarity index 100% rename from openedx_learning/apps/authoring/collections/readme.rst rename to openedx_learning/apps/authoring/applets/collections/readme.rst diff --git a/openedx_learning/apps/authoring/backup_restore/migrations/__init__.py b/openedx_learning/apps/authoring/applets/components/__init__.py similarity index 100% rename from openedx_learning/apps/authoring/backup_restore/migrations/__init__.py rename to openedx_learning/apps/authoring/applets/components/__init__.py diff --git a/openedx_learning/apps/authoring/components/admin.py b/openedx_learning/apps/authoring/applets/components/admin.py similarity index 98% rename from openedx_learning/apps/authoring/components/admin.py rename to openedx_learning/apps/authoring/applets/components/admin.py index 2fa501ddf..682e28c8d 100644 --- a/openedx_learning/apps/authoring/components/admin.py +++ b/openedx_learning/apps/authoring/applets/components/admin.py @@ -27,7 +27,7 @@ class ComponentVersionInline(admin.TabularInline): def format_uuid(self, cv_obj): return format_html( '{}', - reverse("admin:oel_components_componentversion_change", args=(cv_obj.pk,)), + reverse("admin:oel_authoring_componentversion_change", args=(cv_obj.pk,)), cv_obj.uuid, ) diff --git a/openedx_learning/apps/authoring/components/api.py b/openedx_learning/apps/authoring/applets/components/api.py similarity index 100% rename from openedx_learning/apps/authoring/components/api.py rename to openedx_learning/apps/authoring/applets/components/api.py diff --git a/openedx_learning/apps/authoring/components/models.py b/openedx_learning/apps/authoring/applets/components/models.py similarity index 98% rename from openedx_learning/apps/authoring/components/models.py rename to openedx_learning/apps/authoring/applets/components/models.py index b53077637..340aa2505 100644 --- a/openedx_learning/apps/authoring/components/models.py +++ b/openedx_learning/apps/authoring/applets/components/models.py @@ -21,8 +21,8 @@ from django.db import models -from ....lib.fields import case_sensitive_char_field, key_field -from ....lib.managers import WithRelationsManager +from openedx_learning.lib.fields import case_sensitive_char_field, key_field +from openedx_learning.lib.managers import WithRelationsManager from ..contents.models import Content from ..publishing.models import LearningPackage, PublishableEntityMixin, PublishableEntityVersionMixin @@ -63,6 +63,7 @@ class ComponentType(models.Model): # the UsageKey. name = case_sensitive_char_field(max_length=100, blank=True) + # TODO: this needs to go into a class Meta constraints = [ models.UniqueConstraint( fields=[ diff --git a/openedx_learning/apps/authoring/components/readme.rst b/openedx_learning/apps/authoring/applets/components/readme.rst similarity index 100% rename from openedx_learning/apps/authoring/components/readme.rst rename to openedx_learning/apps/authoring/applets/components/readme.rst diff --git a/openedx_learning/apps/authoring/collections/__init__.py b/openedx_learning/apps/authoring/applets/contents/__init__.py similarity index 100% rename from openedx_learning/apps/authoring/collections/__init__.py rename to openedx_learning/apps/authoring/applets/contents/__init__.py diff --git a/openedx_learning/apps/authoring/contents/admin.py b/openedx_learning/apps/authoring/applets/contents/admin.py similarity index 100% rename from openedx_learning/apps/authoring/contents/admin.py rename to openedx_learning/apps/authoring/applets/contents/admin.py diff --git a/openedx_learning/apps/authoring/contents/api.py b/openedx_learning/apps/authoring/applets/contents/api.py similarity index 99% rename from openedx_learning/apps/authoring/contents/api.py rename to openedx_learning/apps/authoring/applets/contents/api.py index 319935882..ed8251e53 100644 --- a/openedx_learning/apps/authoring/contents/api.py +++ b/openedx_learning/apps/authoring/applets/contents/api.py @@ -12,7 +12,7 @@ from django.core.files.base import ContentFile from django.db.transaction import atomic -from ....lib.fields import create_hash_digest +from openedx_learning.lib.fields import create_hash_digest from .models import Content, MediaType # The public API that will be re-exported by openedx_learning.apps.authoring.api diff --git a/openedx_learning/apps/authoring/contents/models.py b/openedx_learning/apps/authoring/applets/contents/models.py similarity index 98% rename from openedx_learning/apps/authoring/contents/models.py rename to openedx_learning/apps/authoring/applets/contents/models.py index c087c122f..d399c2f23 100644 --- a/openedx_learning/apps/authoring/contents/models.py +++ b/openedx_learning/apps/authoring/applets/contents/models.py @@ -16,8 +16,13 @@ from django.db import models from django.utils.module_loading import import_string -from ....lib.fields import MultiCollationTextField, case_insensitive_char_field, hash_field, manual_date_time_field -from ....lib.managers import WithRelationsManager +from openedx_learning.lib.fields import ( + MultiCollationTextField, + case_insensitive_char_field, + hash_field, + manual_date_time_field, +) +from openedx_learning.lib.managers import WithRelationsManager from ..publishing.models import LearningPackage logger = getLogger() diff --git a/openedx_learning/apps/authoring/collections/migrations/__init__.py b/openedx_learning/apps/authoring/applets/publishing/__init__.py similarity index 100% rename from openedx_learning/apps/authoring/collections/migrations/__init__.py rename to openedx_learning/apps/authoring/applets/publishing/__init__.py diff --git a/openedx_learning/apps/authoring/publishing/admin.py b/openedx_learning/apps/authoring/applets/publishing/admin.py similarity index 100% rename from openedx_learning/apps/authoring/publishing/admin.py rename to openedx_learning/apps/authoring/applets/publishing/admin.py diff --git a/openedx_learning/apps/authoring/publishing/api.py b/openedx_learning/apps/authoring/applets/publishing/api.py similarity index 100% rename from openedx_learning/apps/authoring/publishing/api.py rename to openedx_learning/apps/authoring/applets/publishing/api.py diff --git a/openedx_learning/apps/authoring/publishing/contextmanagers.py b/openedx_learning/apps/authoring/applets/publishing/contextmanagers.py similarity index 100% rename from openedx_learning/apps/authoring/publishing/contextmanagers.py rename to openedx_learning/apps/authoring/applets/publishing/contextmanagers.py diff --git a/openedx_learning/apps/authoring/publishing/models/__init__.py b/openedx_learning/apps/authoring/applets/publishing/models/__init__.py similarity index 100% rename from openedx_learning/apps/authoring/publishing/models/__init__.py rename to openedx_learning/apps/authoring/applets/publishing/models/__init__.py diff --git a/openedx_learning/apps/authoring/publishing/models/container.py b/openedx_learning/apps/authoring/applets/publishing/models/container.py similarity index 100% rename from openedx_learning/apps/authoring/publishing/models/container.py rename to openedx_learning/apps/authoring/applets/publishing/models/container.py diff --git a/openedx_learning/apps/authoring/publishing/models/draft_log.py b/openedx_learning/apps/authoring/applets/publishing/models/draft_log.py similarity index 100% rename from openedx_learning/apps/authoring/publishing/models/draft_log.py rename to openedx_learning/apps/authoring/applets/publishing/models/draft_log.py diff --git a/openedx_learning/apps/authoring/publishing/models/entity_list.py b/openedx_learning/apps/authoring/applets/publishing/models/entity_list.py similarity index 99% rename from openedx_learning/apps/authoring/publishing/models/entity_list.py rename to openedx_learning/apps/authoring/applets/publishing/models/entity_list.py index 252ea2e37..37874acee 100644 --- a/openedx_learning/apps/authoring/publishing/models/entity_list.py +++ b/openedx_learning/apps/authoring/applets/publishing/models/entity_list.py @@ -18,6 +18,7 @@ class EntityList(models.Model): anonymous in a sense–they're pointed to by ContainerVersions and other models, rather than being looked up by their own identifiers. """ + @cached_property def rows(self): """ diff --git a/openedx_learning/apps/authoring/publishing/models/learning_package.py b/openedx_learning/apps/authoring/applets/publishing/models/learning_package.py similarity index 100% rename from openedx_learning/apps/authoring/publishing/models/learning_package.py rename to openedx_learning/apps/authoring/applets/publishing/models/learning_package.py diff --git a/openedx_learning/apps/authoring/publishing/models/publish_log.py b/openedx_learning/apps/authoring/applets/publishing/models/publish_log.py similarity index 100% rename from openedx_learning/apps/authoring/publishing/models/publish_log.py rename to openedx_learning/apps/authoring/applets/publishing/models/publish_log.py diff --git a/openedx_learning/apps/authoring/publishing/models/publishable_entity.py b/openedx_learning/apps/authoring/applets/publishing/models/publishable_entity.py similarity index 100% rename from openedx_learning/apps/authoring/publishing/models/publishable_entity.py rename to openedx_learning/apps/authoring/applets/publishing/models/publishable_entity.py diff --git a/openedx_learning/apps/authoring/publishing/readme.rst b/openedx_learning/apps/authoring/applets/publishing/readme.rst similarity index 100% rename from openedx_learning/apps/authoring/publishing/readme.rst rename to openedx_learning/apps/authoring/applets/publishing/readme.rst diff --git a/openedx_learning/apps/authoring/components/__init__.py b/openedx_learning/apps/authoring/applets/sections/__init__.py similarity index 100% rename from openedx_learning/apps/authoring/components/__init__.py rename to openedx_learning/apps/authoring/applets/sections/__init__.py diff --git a/openedx_learning/apps/authoring/sections/admin.py b/openedx_learning/apps/authoring/applets/sections/admin.py similarity index 100% rename from openedx_learning/apps/authoring/sections/admin.py rename to openedx_learning/apps/authoring/applets/sections/admin.py diff --git a/openedx_learning/apps/authoring/sections/api.py b/openedx_learning/apps/authoring/applets/sections/api.py similarity index 99% rename from openedx_learning/apps/authoring/sections/api.py rename to openedx_learning/apps/authoring/applets/sections/api.py index f6d58b858..589636075 100644 --- a/openedx_learning/apps/authoring/sections/api.py +++ b/openedx_learning/apps/authoring/applets/sections/api.py @@ -7,7 +7,7 @@ from django.db.transaction import atomic -from openedx_learning.apps.authoring.subsections.models import Subsection, SubsectionVersion +from ..subsections.models import Subsection, SubsectionVersion from ..publishing import api as publishing_api from .models import Section, SectionVersion diff --git a/openedx_learning/apps/authoring/sections/models.py b/openedx_learning/apps/authoring/applets/sections/models.py similarity index 100% rename from openedx_learning/apps/authoring/sections/models.py rename to openedx_learning/apps/authoring/applets/sections/models.py diff --git a/openedx_learning/apps/authoring/components/management/__init__.py b/openedx_learning/apps/authoring/applets/subsections/__init__.py similarity index 100% rename from openedx_learning/apps/authoring/components/management/__init__.py rename to openedx_learning/apps/authoring/applets/subsections/__init__.py diff --git a/openedx_learning/apps/authoring/subsections/admin.py b/openedx_learning/apps/authoring/applets/subsections/admin.py similarity index 100% rename from openedx_learning/apps/authoring/subsections/admin.py rename to openedx_learning/apps/authoring/applets/subsections/admin.py diff --git a/openedx_learning/apps/authoring/subsections/api.py b/openedx_learning/apps/authoring/applets/subsections/api.py similarity index 99% rename from openedx_learning/apps/authoring/subsections/api.py rename to openedx_learning/apps/authoring/applets/subsections/api.py index 02c1edf93..97a9e3ff2 100644 --- a/openedx_learning/apps/authoring/subsections/api.py +++ b/openedx_learning/apps/authoring/applets/subsections/api.py @@ -7,7 +7,7 @@ from django.db.transaction import atomic -from openedx_learning.apps.authoring.units.models import Unit, UnitVersion +from ..units.models import Unit, UnitVersion from ..publishing import api as publishing_api from .models import Subsection, SubsectionVersion diff --git a/openedx_learning/apps/authoring/subsections/models.py b/openedx_learning/apps/authoring/applets/subsections/models.py similarity index 100% rename from openedx_learning/apps/authoring/subsections/models.py rename to openedx_learning/apps/authoring/applets/subsections/models.py diff --git a/openedx_learning/apps/authoring/components/management/commands/__init__.py b/openedx_learning/apps/authoring/applets/units/__init__.py similarity index 100% rename from openedx_learning/apps/authoring/components/management/commands/__init__.py rename to openedx_learning/apps/authoring/applets/units/__init__.py diff --git a/openedx_learning/apps/authoring/units/admin.py b/openedx_learning/apps/authoring/applets/units/admin.py similarity index 100% rename from openedx_learning/apps/authoring/units/admin.py rename to openedx_learning/apps/authoring/applets/units/admin.py diff --git a/openedx_learning/apps/authoring/units/api.py b/openedx_learning/apps/authoring/applets/units/api.py similarity index 99% rename from openedx_learning/apps/authoring/units/api.py rename to openedx_learning/apps/authoring/applets/units/api.py index ca9a2468d..779b5b3d0 100644 --- a/openedx_learning/apps/authoring/units/api.py +++ b/openedx_learning/apps/authoring/applets/units/api.py @@ -7,8 +7,7 @@ from django.db.transaction import atomic -from openedx_learning.apps.authoring.components.models import Component, ComponentVersion - +from ..components.models import Component, ComponentVersion from ..publishing import api as publishing_api from .models import Unit, UnitVersion diff --git a/openedx_learning/apps/authoring/units/models.py b/openedx_learning/apps/authoring/applets/units/models.py similarity index 100% rename from openedx_learning/apps/authoring/units/models.py rename to openedx_learning/apps/authoring/applets/units/models.py diff --git a/openedx_learning/apps/authoring/apps.py b/openedx_learning/apps/authoring/apps.py new file mode 100644 index 000000000..fd92adff0 --- /dev/null +++ b/openedx_learning/apps/authoring/apps.py @@ -0,0 +1,39 @@ +""" +App Config for our umbrella authoring app. +""" +from django.apps import AppConfig + + +class AuthoringConfig(AppConfig): + """ + Initialization for all applets must happen in here. + """ + + name = "openedx_learning.apps.authoring" + verbose_name = "Learning Core > Authoring" + default_auto_field = "django.db.models.BigAutoField" + label = "oel_authoring" + + def ready(self): + """ + Currently used to register nitialize publishable models. + + May later be used to register signal handlers as well. + """ + # Local imports in AppConfig.ready() are common and expected in Django, + # since you can't import models at the top level without an error. + # + # pylint: disable=import-outside-toplevel + from .api import register_publishable_models + from .models import ( + Component, ComponentVersion, + Container, ContainerVersion, + Section, SectionVersion, + Subsection, SubsectionVersion, + Unit, UnitVersion, + ) + register_publishable_models(Component, ComponentVersion) + register_publishable_models(Container, ContainerVersion) + register_publishable_models(Section, SectionVersion) + register_publishable_models(Subsection, SubsectionVersion) + register_publishable_models(Unit, UnitVersion) diff --git a/openedx_learning/apps/authoring/components/migrations/__init__.py b/openedx_learning/apps/authoring/backcompat/__init__.py similarity index 100% rename from openedx_learning/apps/authoring/components/migrations/__init__.py rename to openedx_learning/apps/authoring/backcompat/__init__.py diff --git a/openedx_learning/apps/authoring/contents/__init__.py b/openedx_learning/apps/authoring/backcompat/backup_restore/__init__.py similarity index 100% rename from openedx_learning/apps/authoring/contents/__init__.py rename to openedx_learning/apps/authoring/backcompat/backup_restore/__init__.py diff --git a/openedx_learning/apps/authoring/backup_restore/apps.py b/openedx_learning/apps/authoring/backcompat/backup_restore/apps.py similarity index 79% rename from openedx_learning/apps/authoring/backup_restore/apps.py rename to openedx_learning/apps/authoring/backcompat/backup_restore/apps.py index 7aa3f022b..e6067bafa 100644 --- a/openedx_learning/apps/authoring/backup_restore/apps.py +++ b/openedx_learning/apps/authoring/backcompat/backup_restore/apps.py @@ -6,7 +6,7 @@ class BackupRestoreConfig(AppConfig): - name = 'openedx_learning.apps.authoring.backup_restore' + name = 'openedx_learning.apps.authoring.backcompat.backup_restore' verbose_name = "Learning Core > Authoring > Backup Restore" default_auto_field = 'django.db.models.BigAutoField' label = "oel_backup_restore" diff --git a/openedx_learning/apps/authoring/contents/migrations/__init__.py b/openedx_learning/apps/authoring/backcompat/backup_restore/migrations/__init__.py similarity index 100% rename from openedx_learning/apps/authoring/contents/migrations/__init__.py rename to openedx_learning/apps/authoring/backcompat/backup_restore/migrations/__init__.py diff --git a/openedx_learning/apps/authoring/publishing/__init__.py b/openedx_learning/apps/authoring/backcompat/collections/__init__.py similarity index 100% rename from openedx_learning/apps/authoring/publishing/__init__.py rename to openedx_learning/apps/authoring/backcompat/collections/__init__.py diff --git a/openedx_learning/apps/authoring/collections/apps.py b/openedx_learning/apps/authoring/backcompat/collections/apps.py similarity index 84% rename from openedx_learning/apps/authoring/collections/apps.py rename to openedx_learning/apps/authoring/backcompat/collections/apps.py index b1ca50c49..cf78d2916 100644 --- a/openedx_learning/apps/authoring/collections/apps.py +++ b/openedx_learning/apps/authoring/backcompat/collections/apps.py @@ -9,7 +9,7 @@ class CollectionsConfig(AppConfig): Configuration for the Collections Django application. """ - name = "openedx_learning.apps.authoring.collections" + name = "openedx_learning.apps.authoring.backcompat.collections" verbose_name = "Learning Core > Authoring > Collections" default_auto_field = "django.db.models.BigAutoField" label = "oel_collections" diff --git a/openedx_learning/apps/authoring/collections/migrations/0001_initial.py b/openedx_learning/apps/authoring/backcompat/collections/migrations/0001_initial.py similarity index 100% rename from openedx_learning/apps/authoring/collections/migrations/0001_initial.py rename to openedx_learning/apps/authoring/backcompat/collections/migrations/0001_initial.py diff --git a/openedx_learning/apps/authoring/collections/migrations/0002_remove_collection_name_collection_created_by_and_more.py b/openedx_learning/apps/authoring/backcompat/collections/migrations/0002_remove_collection_name_collection_created_by_and_more.py similarity index 100% rename from openedx_learning/apps/authoring/collections/migrations/0002_remove_collection_name_collection_created_by_and_more.py rename to openedx_learning/apps/authoring/backcompat/collections/migrations/0002_remove_collection_name_collection_created_by_and_more.py diff --git a/openedx_learning/apps/authoring/collections/migrations/0003_collection_entities.py b/openedx_learning/apps/authoring/backcompat/collections/migrations/0003_collection_entities.py similarity index 100% rename from openedx_learning/apps/authoring/collections/migrations/0003_collection_entities.py rename to openedx_learning/apps/authoring/backcompat/collections/migrations/0003_collection_entities.py diff --git a/openedx_learning/apps/authoring/collections/migrations/0004_collection_key.py b/openedx_learning/apps/authoring/backcompat/collections/migrations/0004_collection_key.py similarity index 100% rename from openedx_learning/apps/authoring/collections/migrations/0004_collection_key.py rename to openedx_learning/apps/authoring/backcompat/collections/migrations/0004_collection_key.py diff --git a/openedx_learning/apps/authoring/collections/migrations/0005_alter_collection_options_alter_collection_enabled.py b/openedx_learning/apps/authoring/backcompat/collections/migrations/0005_alter_collection_options_alter_collection_enabled.py similarity index 100% rename from openedx_learning/apps/authoring/collections/migrations/0005_alter_collection_options_alter_collection_enabled.py rename to openedx_learning/apps/authoring/backcompat/collections/migrations/0005_alter_collection_options_alter_collection_enabled.py diff --git a/openedx_learning/apps/authoring/backcompat/collections/migrations/0006_remove_collectionpublishableentity_collection_and_more.py b/openedx_learning/apps/authoring/backcompat/collections/migrations/0006_remove_collectionpublishableentity_collection_and_more.py new file mode 100644 index 000000000..51b24662a --- /dev/null +++ b/openedx_learning/apps/authoring/backcompat/collections/migrations/0006_remove_collectionpublishableentity_collection_and_more.py @@ -0,0 +1,37 @@ +# Generated by Django 5.2.9 on 2026-01-09 19:07 + +from django.db import migrations +from django.db.migrations.operations.special import SeparateDatabaseAndState + + +class Migration(migrations.Migration): + + dependencies = [ + ('oel_collections', '0005_alter_collection_options_alter_collection_enabled'), + ] + + operations = [ + SeparateDatabaseAndState( + database_operations=[], + state_operations=[ + migrations.RemoveField( + model_name='collectionpublishableentity', + name='collection', + ), + migrations.RemoveField( + model_name='collectionpublishableentity', + name='created_by', + ), + migrations.RemoveField( + model_name='collectionpublishableentity', + name='entity', + ), + migrations.DeleteModel( + name='Collection', + ), + migrations.DeleteModel( + name='CollectionPublishableEntity', + ), + ] + ) + ] \ No newline at end of file diff --git a/openedx_learning/apps/authoring/publishing/migrations/__init__.py b/openedx_learning/apps/authoring/backcompat/collections/migrations/__init__.py similarity index 100% rename from openedx_learning/apps/authoring/publishing/migrations/__init__.py rename to openedx_learning/apps/authoring/backcompat/collections/migrations/__init__.py diff --git a/openedx_learning/apps/authoring/sections/__init__.py b/openedx_learning/apps/authoring/backcompat/components/__init__.py similarity index 100% rename from openedx_learning/apps/authoring/sections/__init__.py rename to openedx_learning/apps/authoring/backcompat/components/__init__.py diff --git a/openedx_learning/apps/authoring/backcompat/components/apps.py b/openedx_learning/apps/authoring/backcompat/components/apps.py new file mode 100644 index 000000000..1997650f1 --- /dev/null +++ b/openedx_learning/apps/authoring/backcompat/components/apps.py @@ -0,0 +1,15 @@ +""" +Django metadata for the Components Django application. +""" +from django.apps import AppConfig + + +class ComponentsConfig(AppConfig): + """ + Configuration for the Components Django application. + """ + + name = "openedx_learning.apps.authoring.backcompat.components" + verbose_name = "Learning Core > Authoring > Components" + default_auto_field = "django.db.models.BigAutoField" + label = "oel_components" diff --git a/openedx_learning/apps/authoring/components/migrations/0001_initial.py b/openedx_learning/apps/authoring/backcompat/components/migrations/0001_initial.py similarity index 100% rename from openedx_learning/apps/authoring/components/migrations/0001_initial.py rename to openedx_learning/apps/authoring/backcompat/components/migrations/0001_initial.py diff --git a/openedx_learning/apps/authoring/components/migrations/0002_alter_componentversioncontent_key.py b/openedx_learning/apps/authoring/backcompat/components/migrations/0002_alter_componentversioncontent_key.py similarity index 100% rename from openedx_learning/apps/authoring/components/migrations/0002_alter_componentversioncontent_key.py rename to openedx_learning/apps/authoring/backcompat/components/migrations/0002_alter_componentversioncontent_key.py diff --git a/openedx_learning/apps/authoring/components/migrations/0003_remove_componentversioncontent_learner_downloadable.py b/openedx_learning/apps/authoring/backcompat/components/migrations/0003_remove_componentversioncontent_learner_downloadable.py similarity index 100% rename from openedx_learning/apps/authoring/components/migrations/0003_remove_componentversioncontent_learner_downloadable.py rename to openedx_learning/apps/authoring/backcompat/components/migrations/0003_remove_componentversioncontent_learner_downloadable.py diff --git a/openedx_learning/apps/authoring/components/migrations/0004_remove_componentversioncontent_uuid.py b/openedx_learning/apps/authoring/backcompat/components/migrations/0004_remove_componentversioncontent_uuid.py similarity index 100% rename from openedx_learning/apps/authoring/components/migrations/0004_remove_componentversioncontent_uuid.py rename to openedx_learning/apps/authoring/backcompat/components/migrations/0004_remove_componentversioncontent_uuid.py diff --git a/openedx_learning/apps/authoring/backcompat/components/migrations/0005_remove_component_component_type_and_more.py b/openedx_learning/apps/authoring/backcompat/components/migrations/0005_remove_component_component_type_and_more.py new file mode 100644 index 000000000..bf456d7a6 --- /dev/null +++ b/openedx_learning/apps/authoring/backcompat/components/migrations/0005_remove_component_component_type_and_more.py @@ -0,0 +1,63 @@ +# Generated by Django 5.2.9 on 2026-01-09 19:07 + +from django.db import migrations +from django.db.migrations.operations.special import SeparateDatabaseAndState + + +class Migration(migrations.Migration): + + dependencies = [ + ('oel_components', '0004_remove_componentversioncontent_uuid'), + ] + + operations = [ + SeparateDatabaseAndState( + database_operations=[], + state_operations=[ + migrations.RemoveField( + model_name='component', + name='component_type', + ), + migrations.RemoveField( + model_name='component', + name='learning_package', + ), + migrations.RemoveField( + model_name='component', + name='publishable_entity', + ), + migrations.RemoveField( + model_name='componentversion', + name='component', + ), + migrations.RemoveField( + model_name='componentversion', + name='contents', + ), + migrations.RemoveField( + model_name='componentversion', + name='publishable_entity_version', + ), + migrations.RemoveField( + model_name='componentversioncontent', + name='component_version', + ), + migrations.RemoveField( + model_name='componentversioncontent', + name='content', + ), + migrations.DeleteModel( + name='ComponentType', + ), + migrations.DeleteModel( + name='Component', + ), + migrations.DeleteModel( + name='ComponentVersion', + ), + migrations.DeleteModel( + name='ComponentVersionContent', + ), + ] + ) + ] diff --git a/openedx_learning/apps/authoring/sections/migrations/__init__.py b/openedx_learning/apps/authoring/backcompat/components/migrations/__init__.py similarity index 100% rename from openedx_learning/apps/authoring/sections/migrations/__init__.py rename to openedx_learning/apps/authoring/backcompat/components/migrations/__init__.py diff --git a/openedx_learning/apps/authoring/subsections/__init__.py b/openedx_learning/apps/authoring/backcompat/contents/__init__.py similarity index 100% rename from openedx_learning/apps/authoring/subsections/__init__.py rename to openedx_learning/apps/authoring/backcompat/contents/__init__.py diff --git a/openedx_learning/apps/authoring/contents/apps.py b/openedx_learning/apps/authoring/backcompat/contents/apps.py similarity index 84% rename from openedx_learning/apps/authoring/contents/apps.py rename to openedx_learning/apps/authoring/backcompat/contents/apps.py index 3c23bd4f5..9d212ca4b 100644 --- a/openedx_learning/apps/authoring/contents/apps.py +++ b/openedx_learning/apps/authoring/backcompat/contents/apps.py @@ -9,7 +9,7 @@ class ContentsConfig(AppConfig): Configuration for the Contents Django application. """ - name = "openedx_learning.apps.authoring.contents" + name = "openedx_learning.apps.authoring.backcompat.contents" verbose_name = "Learning Core > Authoring > Contents" default_auto_field = "django.db.models.BigAutoField" label = "oel_contents" diff --git a/openedx_learning/apps/authoring/contents/migrations/0001_initial.py b/openedx_learning/apps/authoring/backcompat/contents/migrations/0001_initial.py similarity index 100% rename from openedx_learning/apps/authoring/contents/migrations/0001_initial.py rename to openedx_learning/apps/authoring/backcompat/contents/migrations/0001_initial.py diff --git a/openedx_learning/apps/authoring/backcompat/contents/migrations/0002_delete_content_delete_mediatype.py b/openedx_learning/apps/authoring/backcompat/contents/migrations/0002_delete_content_delete_mediatype.py new file mode 100644 index 000000000..6147cae97 --- /dev/null +++ b/openedx_learning/apps/authoring/backcompat/contents/migrations/0002_delete_content_delete_mediatype.py @@ -0,0 +1,26 @@ +# Generated by Django 5.2.9 on 2026-01-09 19:07 + +from django.db import migrations +from django.db.migrations.operations.special import SeparateDatabaseAndState + + +class Migration(migrations.Migration): + + dependencies = [ + ('oel_components', '0005_remove_component_component_type_and_more'), + ('oel_contents', '0001_initial'), + ] + + operations = [ + SeparateDatabaseAndState( + database_operations=[], + state_operations=[ + migrations.DeleteModel( + name='Content', + ), + migrations.DeleteModel( + name='MediaType', + ), + ] + ) + ] diff --git a/openedx_learning/apps/authoring/subsections/migrations/__init__.py b/openedx_learning/apps/authoring/backcompat/contents/migrations/__init__.py similarity index 100% rename from openedx_learning/apps/authoring/subsections/migrations/__init__.py rename to openedx_learning/apps/authoring/backcompat/contents/migrations/__init__.py diff --git a/openedx_learning/apps/authoring/units/__init__.py b/openedx_learning/apps/authoring/backcompat/publishing/__init__.py similarity index 100% rename from openedx_learning/apps/authoring/units/__init__.py rename to openedx_learning/apps/authoring/backcompat/publishing/__init__.py diff --git a/openedx_learning/apps/authoring/backcompat/publishing/apps.py b/openedx_learning/apps/authoring/backcompat/publishing/apps.py new file mode 100644 index 000000000..f8b11058c --- /dev/null +++ b/openedx_learning/apps/authoring/backcompat/publishing/apps.py @@ -0,0 +1,16 @@ +""" +publishing Django application initialization. +""" + +from django.apps import AppConfig + + +class PublishingConfig(AppConfig): + """ + Configuration for the publishing Django application. + """ + + name = "openedx_learning.apps.authoring.backcompat.publishing" + verbose_name = "Learning Core > Authoring > Publishing" + default_auto_field = "django.db.models.BigAutoField" + label = "oel_publishing" diff --git a/openedx_learning/apps/authoring/publishing/migrations/0001_initial.py b/openedx_learning/apps/authoring/backcompat/publishing/migrations/0001_initial.py similarity index 100% rename from openedx_learning/apps/authoring/publishing/migrations/0001_initial.py rename to openedx_learning/apps/authoring/backcompat/publishing/migrations/0001_initial.py diff --git a/openedx_learning/apps/authoring/publishing/migrations/0002_alter_learningpackage_key_and_more.py b/openedx_learning/apps/authoring/backcompat/publishing/migrations/0002_alter_learningpackage_key_and_more.py similarity index 100% rename from openedx_learning/apps/authoring/publishing/migrations/0002_alter_learningpackage_key_and_more.py rename to openedx_learning/apps/authoring/backcompat/publishing/migrations/0002_alter_learningpackage_key_and_more.py diff --git a/openedx_learning/apps/authoring/publishing/migrations/0003_containers.py b/openedx_learning/apps/authoring/backcompat/publishing/migrations/0003_containers.py similarity index 100% rename from openedx_learning/apps/authoring/publishing/migrations/0003_containers.py rename to openedx_learning/apps/authoring/backcompat/publishing/migrations/0003_containers.py diff --git a/openedx_learning/apps/authoring/publishing/migrations/0004_publishableentity_can_stand_alone.py b/openedx_learning/apps/authoring/backcompat/publishing/migrations/0004_publishableentity_can_stand_alone.py similarity index 100% rename from openedx_learning/apps/authoring/publishing/migrations/0004_publishableentity_can_stand_alone.py rename to openedx_learning/apps/authoring/backcompat/publishing/migrations/0004_publishableentity_can_stand_alone.py diff --git a/openedx_learning/apps/authoring/publishing/migrations/0005_alter_entitylistrow_options.py b/openedx_learning/apps/authoring/backcompat/publishing/migrations/0005_alter_entitylistrow_options.py similarity index 100% rename from openedx_learning/apps/authoring/publishing/migrations/0005_alter_entitylistrow_options.py rename to openedx_learning/apps/authoring/backcompat/publishing/migrations/0005_alter_entitylistrow_options.py diff --git a/openedx_learning/apps/authoring/publishing/migrations/0006_draftchangelog.py b/openedx_learning/apps/authoring/backcompat/publishing/migrations/0006_draftchangelog.py similarity index 100% rename from openedx_learning/apps/authoring/publishing/migrations/0006_draftchangelog.py rename to openedx_learning/apps/authoring/backcompat/publishing/migrations/0006_draftchangelog.py diff --git a/openedx_learning/apps/authoring/publishing/migrations/0007_bootstrap_draftchangelog.py b/openedx_learning/apps/authoring/backcompat/publishing/migrations/0007_bootstrap_draftchangelog.py similarity index 100% rename from openedx_learning/apps/authoring/publishing/migrations/0007_bootstrap_draftchangelog.py rename to openedx_learning/apps/authoring/backcompat/publishing/migrations/0007_bootstrap_draftchangelog.py diff --git a/openedx_learning/apps/authoring/publishing/migrations/0008_alter_draftchangelogrecord_options_and_more.py b/openedx_learning/apps/authoring/backcompat/publishing/migrations/0008_alter_draftchangelogrecord_options_and_more.py similarity index 100% rename from openedx_learning/apps/authoring/publishing/migrations/0008_alter_draftchangelogrecord_options_and_more.py rename to openedx_learning/apps/authoring/backcompat/publishing/migrations/0008_alter_draftchangelogrecord_options_and_more.py diff --git a/openedx_learning/apps/authoring/publishing/migrations/0009_dependencies_and_hashing.py b/openedx_learning/apps/authoring/backcompat/publishing/migrations/0009_dependencies_and_hashing.py similarity index 100% rename from openedx_learning/apps/authoring/publishing/migrations/0009_dependencies_and_hashing.py rename to openedx_learning/apps/authoring/backcompat/publishing/migrations/0009_dependencies_and_hashing.py diff --git a/openedx_learning/apps/authoring/backcompat/publishing/migrations/0010_backfill_dependencies.py b/openedx_learning/apps/authoring/backcompat/publishing/migrations/0010_backfill_dependencies.py new file mode 100644 index 000000000..12bdae357 --- /dev/null +++ b/openedx_learning/apps/authoring/backcompat/publishing/migrations/0010_backfill_dependencies.py @@ -0,0 +1,423 @@ +""" +Backfill PublishableEntityVersionDependency entries based on ContainerVersions. + +We're introducing a lower-level publishing concept of a dependency that will be +used by Containers, but this means we have to backfill that dependency info for +existing Containers in the system. +""" +from django.db import migrations +from django.db.models import F, Prefetch + +from openedx_learning.lib.fields import create_hash_digest + + +def create_backfill(apps, schema_editor): + """ + Create dependency entries and update dep hashes for Draft and Published. + """ + _create_dependencies(apps) + _update_drafts(apps) + _update_draft_dependencies_hashes(apps) + _update_published_dependencies_hashes(apps) + + +def _create_dependencies(apps): + """ + Populate the PublishableEntityVersion.dependencies relation. + + The only ones we should have in the system at this point are the ones from + containers, so we query ContainerVersion for that. + """ + PublishableEntityVersionDependency = apps.get_model( + "oel_publishing", "PublishableEntityVersionDependency" + ) + ContainerVersion = apps.get_model("oel_publishing", "ContainerVersion") + + for container_version in ContainerVersion.objects.all(): + # child_entity_ids is a set to de-dupe. This doesn't handle pinned + # child references yet, but you can't actually make those in a real + # library yet, so we shouldn't have that data lying around to migrate. + child_entity_ids = set( + container_version + .entity_list + .entitylistrow_set + .all() + .values_list("entity_id", flat=True) + ) + PublishableEntityVersionDependency.objects.bulk_create( + [ + PublishableEntityVersionDependency( + referring_version_id=container_version.pk, + referenced_entity_id=entity_id + ) + for entity_id in child_entity_ids + ], + ignore_conflicts=True, + ) + + +def _update_drafts(apps): + """ + Update Draft entries to point to their most recent DraftLogRecord. + + This is slow and expensive. + """ + Draft = apps.get_model("oel_publishing", "Draft") + DraftChangeLogRecord = apps.get_model("oel_publishing", "DraftChangeLogRecord") + for draft in Draft.objects.all(): + draft_log_record = ( + # Find the most recent DraftChangeLogRecord related to this Draft, + DraftChangeLogRecord.objects + .filter(entity_id=draft.pk) + .order_by('-pk') + .first() + ) + draft.draft_log_record = draft_log_record + draft.save() + + +def _update_draft_dependencies_hashes(apps): + """ + Update the dependency_hash_digest for all DraftChangeLogRecords. + + Backfill dependency state hashes. The important thing here is that things + without dependencies will have the default (blank) state hash, so we only + need to query for Draft entries for Containers. + + We are only backfilling the current DraftChangeLogRecords pointed to by the + Draft entries now. We are not backfilling all historical + DraftChangeLogRecords. Full historical reconstruction is probably possible, + but it's not really worth the cost and complexity. + """ + DraftChangeLog = apps.get_model("oel_publishing", "DraftChangeLog") + + # All DraftChangeLogs that have records that are pointed to by the current + # Draft and have a possibility of having dependencies. + change_logs = DraftChangeLog.objects.filter( + pk=F('records__entity__draft__draft_log_record__draft_change_log'), + records__entity__draft__version__isnull=False, + records__entity__container__isnull=False, + ).distinct() + for change_log in change_logs: + update_dependencies_hash_digests_for_log(change_log, apps) + +def _update_published_dependencies_hashes(apps): + """ + Update all container Published.dependencies_hash_digest + + Backfill dependency state hashes. The important thing here is that things + without dependencies will have the default (blank) state hash, so we only + need to query for Published entries for Containers. + """ + PublishLog = apps.get_model("oel_publishing", "PublishLog") + + # All PublishLogs that have records that are pointed to by the current + # Published and have a possibility of having dependencies. + change_logs = PublishLog.objects.filter( + pk=F('records__entity__published__publish_log_record__publish_log'), + records__entity__published__version__isnull=False, + records__entity__container__isnull=False, + ).distinct() + for change_log in change_logs: + update_dependencies_hash_digests_for_log(change_log, apps) + +def remove_backfill(apps, schema_editor): + """ + Reset all dep hash values to default ('') and remove dependencies. + """ + Draft = apps.get_model("oel_publishing", "Draft") + DraftChangeLogRecord = apps.get_model("oel_publishing", "DraftChangeLogRecord") + PublishLogRecord = apps.get_model("oel_publishing", "PublishLogRecord") + PublishableEntityVersionDependency = apps.get_model( + "oel_publishing", "PublishableEntityVersionDependency" + ) + + PublishLogRecord.objects.all().update(dependencies_hash_digest='') + DraftChangeLogRecord.objects.all().update(dependencies_hash_digest='') + PublishableEntityVersionDependency.objects.all().delete() + Draft.objects.all().update(draft_log_record=None) + + +def update_dependencies_hash_digests_for_log( + change_log, # this is a historical DraftChangeLog or PublishLog + apps, + backfill=True, +) -> None: + """ + Update dependencies_hash_digest for Drafts or Published in a change log. + + This is copied from the publishing API to make sure we don't accidentally + break it with future changes as the data model evolves. It has also been + modified to use historical models, rather than having references to the new + ones that have been moved to the centralized authoring app. It has also been + modified to assume that it's being used as a backfill (the original makes it + optional). + + All the data for Draft/Published, DraftChangeLog/PublishLog, and + DraftChangeLogRecord/PublishLogRecord have been set at this point *except* + the dependencies_hash_digest of DraftChangeLogRecord/PublishLogRecord. Those + log records are newly created at this point, so dependencies_hash_digest are + set to their default values. + + Args: + change_log: A DraftChangeLog or PublishLog that already has all + side-effects added to it. The Draft and Published models should + already be updated to point to the post-change versions. + backfill: If this is true, we will not trust the hash values stored on + log records outside of our log, i.e. things that we would normally + expect to be pre-calculated. This will be important for the initial + data migration. + """ + DraftChangeLog = apps.get_model("oel_publishing", "DraftChangeLog") + DraftChangeLogRecord = apps.get_model("oel_publishing", "DraftChangeLogRecord") + PublishLog = apps.get_model("oel_publishing", "PublishLog") + PublishLogRecord = apps.get_model("oel_publishing", "PublishLogRecord") + PublishableEntity = apps.get_model("oel_publishing", "PublishableEntity") + + if isinstance(change_log, DraftChangeLog): + branch = "draft" + log_record_relation = "draft_log_record" + record_cls = DraftChangeLogRecord + elif isinstance(change_log, PublishLog): + branch = "published" + log_record_relation = "publish_log_record" + record_cls = PublishLogRecord # type: ignore[assignment] + else: + raise TypeError( + f"expected DraftChangeLog or PublishLog, not {type(change_log)}" + ) + + dependencies_prefetch = Prefetch( + "new_version__dependencies", + queryset=PublishableEntity.objects + .select_related( + f"{branch}__version", + f"{branch}__{log_record_relation}", + ) + .order_by(f"{branch}__version__uuid") + ) + changed_records = ( + change_log.records + .select_related("new_version", f"entity__{branch}") + .prefetch_related(dependencies_prefetch) + ) + + record_ids_to_hash_digests: dict[int, str | None] = {} + record_ids_to_live_deps: dict[int, list] = {} + records_that_need_hashes = [] + + for record in changed_records: + # This is a soft-deletion, so the dependency hash is default/blank. We + # set this value in our record_ids_to_hash_digests cache, but we don't + # need to write it to the database because it's just the default value. + if record.new_version is None: + record_ids_to_hash_digests[record.id] = '' + continue + + # Now check to see if the new version has "live" dependencies, i.e. + # dependencies that have not been deleted. + deps = list( + entity for entity in record.new_version.dependencies.all() + if hasattr(entity, branch) and getattr(entity, branch).version + ) + + # If there are no live dependencies, this log record also gets the + # default/blank value. + if not deps: + record_ids_to_hash_digests[record.id] = '' + continue + + # If we've gotten this far, it means that this record has dependencies + # and does need to get a hash computed for it. + records_that_need_hashes.append(record) + record_ids_to_live_deps[record.id] = deps + + if backfill: + untrusted_record_id_set = None + else: + untrusted_record_id_set = set(rec.id for rec in records_that_need_hashes) + + for record in records_that_need_hashes: + record.dependencies_hash_digest = hash_for_log_record( + apps, + record, + record_ids_to_hash_digests, + record_ids_to_live_deps, + untrusted_record_id_set, + ) + + _bulk_update_hashes(record_cls, records_that_need_hashes) + + +def _bulk_update_hashes(model_cls, records): + """ + bulk_update using the model class (PublishLogRecord or DraftChangeLogRecord) + + This is copied from the publishing API to make sure we don't accidentally + break it with future changes as the data model evolves. + """ + model_cls.objects.bulk_update(records, ['dependencies_hash_digest']) + + +def hash_for_log_record( + apps, + record, # historical DraftChangeLogRecord | PublishLogRecord, + record_ids_to_hash_digests: dict, + record_ids_to_live_deps: dict, + untrusted_record_id_set: set | None, +) -> str: + """ + The hash digest for a given change log record. + + This is copied from the publishing API to make sure we don't accidentally + break it with future changes as the data model evolves. It has also been + modified to use historical models, rather than having references to the new + ones that have been moved to the centralized authoring app. + + Note that this code is a little convoluted because we're working hard to + minimize the number of database requests. All the data we really need could + be derived from querying various relations off the record that's passed in + as the first parameter, but at a far higher cost. + + The hash calculated here will be used for the dependencies_hash_digest + attribute of DraftChangeLogRecord and PublishLogRecord. The hash is intended + to calculate the currently "live" (current draft or published) state of all + dependencies (and transitive dependencies) of the PublishableEntityVersion + pointed to by DraftChangeLogRecord.new_version/PublishLogRecord.new_version. + + The common case we have at the moment is when a container type like a Unit + has unpinned child Components as dependencies. In the data model, those + dependency relationships are represented by the "dependencies" M:M relation + on PublishableEntityVersion. Since the Unit version's references to its + child Components are unpinned, the draft Unit is always pointing to the + latest draft versions of those Components and the published Unit is always + pointing to the latest published versions of those Components. + + This means that the total draft or published state of any PublishableEntity + depends on the combination of: + + 1. The definition of the current draft/published version of that entity. + Example: Version 1 of a Unit would define that it had children [C1, C2]. + Version 2 of the same Unit might have children [C1, C2, C3]. + 2. The current draft/published versions of all dependencies. Example: What + are the current draft and published versions of C1, C2, and C3. + + This is why it makes sense to capture in a log record, since + PublishLogRecords or DraftChangeLogRecords are created whenever one of the + above two things changes. + + Here are the possible scenarios, including edge cases: + + EntityVersions with no dependencies + If record.new_version has no dependencies, dependencies_hash_digest is + set to the default value of ''. This will be the most common case. + + EntityVersions with dependencies + If an EntityVersion has dependencies, then its draft/published state + hash is based on the concatenation of, for each non-deleted dependency: + (i) the dependency's draft/published EntityVersion primary key, and + (ii) the dependency's own draft/published state hash, recursively re- + calculated if necessary. + + Soft-deletions + If the record.new_version is None, that means we've just soft-deleted + something (or published the soft-delete of something). We adopt the + convention that if something is soft-deleted, its dependencies_hash_digest + is reset to the default value of ''. This is not strictly necessary for + the recursive hash calculation, but deleted entities will not have their + hash updated even as their non-deleted dependencies are updated underneath + them, so we set to '' to avoid falsely implying that the deleted entity's + dep hash is up to date. + + EntityVersions with soft-deleted dependencies + A soft-deleted dependency isn't counted (it's as if the dependency were + removed). If all of an EntityVersion's dependencies are soft-deleted, + then it will go back to having to having the default blank string for its + dependencies_hash_digest. + """ + DraftChangeLogRecord = apps.get_model("oel_publishing", "DraftChangeLogRecord") + PublishLogRecord = apps.get_model("oel_publishing", "PublishLogRecord") + + # Case #1: We've already computed this, or it was bootstrapped for us in the + # cache because the record is a deletion or doesn't have dependencies. + if record.id in record_ids_to_hash_digests: + return record_ids_to_hash_digests[record.id] + + # Case #2: The log_record is a dependency of something that was affected by + # a change, but the dependency itself did not change in any way (neither + # directly, nor as a side-effect). + # + # Example: A Unit has two Components. One of the Components changed, forcing + # us to recalculate the dependencies_hash_digest for that Unit. Doing that + # recalculation requires us to fetch the dependencies_hash_digest of the + # unchanged child Component as well. + # + # If we aren't given an explicit untrusted_record_id_set, it means we can't + # trust anything. This would happen when we're bootstrapping things with an + # initial data migration. + if (untrusted_record_id_set is not None) and (record.id not in untrusted_record_id_set): + return record.dependencies_hash_digest + + # Normal recursive case starts here: + if isinstance(record, DraftChangeLogRecord): + branch = "draft" + elif isinstance(record, PublishLogRecord): + branch = "published" + else: + raise TypeError( + f"expected DraftChangeLogRecord or PublishLogRecord, not {type(record)}" + ) + + # This is extra work that only happens in case of a backfill, where we might + # need to compute dependency hashes for things outside of our log (because + # we don't trust them). + if record.id not in record_ids_to_live_deps: + if record.new_version is None: + record_ids_to_hash_digests[record.id] = '' + return '' + deps = list( + entity for entity in record.new_version.dependencies.all() + if hasattr(entity, branch) and getattr(entity, branch).version + ) + # If there are no live dependencies, this log record also gets the + # default/blank value. + if not deps: + record_ids_to_hash_digests[record.id] = '' + return '' + + record_ids_to_live_deps[record.id] = deps + # End special handling for backfill. + + # Begin normal + dependencies = sorted( + record_ids_to_live_deps[record.id], + key=lambda entity: getattr(entity, branch).log_record.new_version_id, + ) + dep_state_entries = [] + for dep_entity in dependencies: + new_version_id = getattr(dep_entity, branch).log_record.new_version_id + hash_digest = hash_for_log_record( + apps, + getattr(dep_entity, branch).log_record, + record_ids_to_hash_digests, + record_ids_to_live_deps, + untrusted_record_id_set, + ) + dep_state_entries.append(f"{new_version_id}:{hash_digest}") + summary_text = "\n".join(dep_state_entries) + + digest = create_hash_digest(summary_text.encode(), num_bytes=4) + record_ids_to_hash_digests[record.id] = digest + + return digest + + +class Migration(migrations.Migration): + + dependencies = [ + ('oel_publishing', '0009_dependencies_and_hashing'), + ] + + operations = [ + migrations.RunPython(create_backfill, reverse_code=remove_backfill) + ] diff --git a/openedx_learning/apps/authoring/backcompat/publishing/migrations/0011_remove_containerversion_container_and_more.py b/openedx_learning/apps/authoring/backcompat/publishing/migrations/0011_remove_containerversion_container_and_more.py new file mode 100644 index 000000000..d2cdbe094 --- /dev/null +++ b/openedx_learning/apps/authoring/backcompat/publishing/migrations/0011_remove_containerversion_container_and_more.py @@ -0,0 +1,213 @@ +# Generated by Django 5.2.9 on 2026-01-09 19:07 + +from django.db import migrations +from django.db.migrations.operations.special import SeparateDatabaseAndState + + +class Migration(migrations.Migration): + + dependencies = [ + ('oel_collections', '0006_remove_collectionpublishableentity_collection_and_more'), + ('oel_components', '0005_remove_component_component_type_and_more'), + ('oel_contents', '0002_delete_content_delete_mediatype'), + ('oel_publishing', '0010_backfill_dependencies'), + ('oel_sections', '0002_remove_sectionversion_container_version_and_more'), + ('oel_subsections', '0002_remove_subsectionversion_container_version_and_more'), + ('oel_units', '0002_remove_unitversion_container_version_delete_unit_and_more'), + ] + + operations = [ + SeparateDatabaseAndState( + database_operations=[], + state_operations=[ + migrations.RemoveField( + model_name='containerversion', + name='container', + ), + migrations.RemoveField( + model_name='containerversion', + name='entity_list', + ), + migrations.RemoveField( + model_name='containerversion', + name='publishable_entity_version', + ), + migrations.RemoveField( + model_name='draft', + name='draft_log_record', + ), + migrations.RemoveField( + model_name='draft', + name='entity', + ), + migrations.RemoveField( + model_name='draft', + name='version', + ), + migrations.RemoveField( + model_name='draftchangelog', + name='changed_by', + ), + migrations.RemoveField( + model_name='draftchangelog', + name='learning_package', + ), + migrations.RemoveField( + model_name='draftchangelogrecord', + name='draft_change_log', + ), + migrations.RemoveField( + model_name='draftchangelogrecord', + name='entity', + ), + migrations.RemoveField( + model_name='draftchangelogrecord', + name='new_version', + ), + migrations.RemoveField( + model_name='draftchangelogrecord', + name='old_version', + ), + migrations.RemoveField( + model_name='draftsideeffect', + name='effect', + ), + migrations.RemoveField( + model_name='draftsideeffect', + name='cause', + ), + migrations.RemoveField( + model_name='entitylistrow', + name='entity_list', + ), + migrations.RemoveField( + model_name='entitylistrow', + name='entity', + ), + migrations.RemoveField( + model_name='entitylistrow', + name='entity_version', + ), + migrations.RemoveField( + model_name='publishlog', + name='learning_package', + ), + migrations.RemoveField( + model_name='publishableentity', + name='learning_package', + ), + migrations.RemoveField( + model_name='publishableentity', + name='created_by', + ), + migrations.RemoveField( + model_name='published', + name='entity', + ), + migrations.RemoveField( + model_name='publishableentityversion', + name='dependencies', + ), + migrations.RemoveField( + model_name='publishableentityversion', + name='entity', + ), + migrations.RemoveField( + model_name='publishlogrecord', + name='entity', + ), + migrations.RemoveField( + model_name='publishableentityversiondependency', + name='referenced_entity', + ), + migrations.RemoveField( + model_name='publishableentityversion', + name='created_by', + ), + migrations.RemoveField( + model_name='published', + name='version', + ), + migrations.RemoveField( + model_name='publishlogrecord', + name='new_version', + ), + migrations.RemoveField( + model_name='publishlogrecord', + name='old_version', + ), + migrations.RemoveField( + model_name='publishableentityversiondependency', + name='referring_version', + ), + migrations.RemoveField( + model_name='published', + name='publish_log_record', + ), + migrations.RemoveField( + model_name='publishlog', + name='published_by', + ), + migrations.RemoveField( + model_name='publishlogrecord', + name='publish_log', + ), + migrations.RemoveField( + model_name='publishsideeffect', + name='cause', + ), + migrations.RemoveField( + model_name='publishsideeffect', + name='effect', + ), + migrations.DeleteModel( + name='Container', + ), + migrations.DeleteModel( + name='ContainerVersion', + ), + migrations.DeleteModel( + name='Draft', + ), + migrations.DeleteModel( + name='DraftChangeLog', + ), + migrations.DeleteModel( + name='DraftChangeLogRecord', + ), + migrations.DeleteModel( + name='DraftSideEffect', + ), + migrations.DeleteModel( + name='EntityList', + ), + migrations.DeleteModel( + name='EntityListRow', + ), + migrations.DeleteModel( + name='LearningPackage', + ), + migrations.DeleteModel( + name='PublishableEntity', + ), + migrations.DeleteModel( + name='PublishableEntityVersion', + ), + migrations.DeleteModel( + name='PublishableEntityVersionDependency', + ), + migrations.DeleteModel( + name='Published', + ), + migrations.DeleteModel( + name='PublishLog', + ), + migrations.DeleteModel( + name='PublishLogRecord', + ), + migrations.DeleteModel( + name='PublishSideEffect', + ), + ] + ) + ] diff --git a/openedx_learning/apps/authoring/units/migrations/__init__.py b/openedx_learning/apps/authoring/backcompat/publishing/migrations/__init__.py similarity index 100% rename from openedx_learning/apps/authoring/units/migrations/__init__.py rename to openedx_learning/apps/authoring/backcompat/publishing/migrations/__init__.py diff --git a/openedx_learning/apps/authoring/backcompat/readme.rst b/openedx_learning/apps/authoring/backcompat/readme.rst new file mode 100644 index 000000000..d6f57633c --- /dev/null +++ b/openedx_learning/apps/authoring/backcompat/readme.rst @@ -0,0 +1,6 @@ +Backwards Compatibility App Package +=================================== + +The apps in this package should not be modified. They are a byproduct of our migration from having a bunch of little authoring apps to having one unified app. They exist to provide backwards compatibilty for database migrations (see `<0020-authoring-as-one-app.rst>`_). + +At some point in the future, we will remove this package and modify the initial migration for the ``authoring`` app to actually create the models for real, instead of using ``SeparateDatabaseAndState`` to fake the database side of the migration. For anyone who has already run the ``oel_authoring`` migrations, the modified initial migration won't run anyway. Anyone setting things up for the first time would get the ``oel_authoring`` models created without the intermediate steps of creating all the smaller app models first and renaming them. We should not do this before the Willow release, but there's no real downside to doing it later. diff --git a/tests/openedx_learning/apps/authoring/backup_restore/__init__.py b/openedx_learning/apps/authoring/backcompat/sections/__init__.py similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/__init__.py rename to openedx_learning/apps/authoring/backcompat/sections/__init__.py diff --git a/openedx_learning/apps/authoring/backcompat/sections/apps.py b/openedx_learning/apps/authoring/backcompat/sections/apps.py new file mode 100644 index 000000000..e86ce90b4 --- /dev/null +++ b/openedx_learning/apps/authoring/backcompat/sections/apps.py @@ -0,0 +1,16 @@ +""" +Sections Django application initialization. +""" + +from django.apps import AppConfig + + +class SectionsConfig(AppConfig): + """ + Configuration for the Sections Django application. + """ + + name = "openedx_learning.apps.authoring.backcompat.sections" + verbose_name = "Learning Core > Authoring > Sections" + default_auto_field = "django.db.models.BigAutoField" + label = "oel_sections" diff --git a/openedx_learning/apps/authoring/sections/migrations/0001_initial.py b/openedx_learning/apps/authoring/backcompat/sections/migrations/0001_initial.py similarity index 100% rename from openedx_learning/apps/authoring/sections/migrations/0001_initial.py rename to openedx_learning/apps/authoring/backcompat/sections/migrations/0001_initial.py diff --git a/openedx_learning/apps/authoring/backcompat/sections/migrations/0002_remove_sectionversion_container_version_and_more.py b/openedx_learning/apps/authoring/backcompat/sections/migrations/0002_remove_sectionversion_container_version_and_more.py new file mode 100644 index 000000000..cca1a06fa --- /dev/null +++ b/openedx_learning/apps/authoring/backcompat/sections/migrations/0002_remove_sectionversion_container_version_and_more.py @@ -0,0 +1,29 @@ +# Generated by Django 5.2.9 on 2026-01-09 19:07 + +from django.db import migrations +from django.db.migrations.operations.special import SeparateDatabaseAndState + + +class Migration(migrations.Migration): + + dependencies = [ + ('oel_sections', '0001_initial'), + ] + + operations = [ + SeparateDatabaseAndState( + database_operations=[], + state_operations=[ + migrations.RemoveField( + model_name='sectionversion', + name='container_version', + ), + migrations.DeleteModel( + name='Section', + ), + migrations.DeleteModel( + name='SectionVersion', + ), + ] + ) + ] diff --git a/tests/openedx_learning/apps/authoring/collections/__init__.py b/openedx_learning/apps/authoring/backcompat/sections/migrations/__init__.py similarity index 100% rename from tests/openedx_learning/apps/authoring/collections/__init__.py rename to openedx_learning/apps/authoring/backcompat/sections/migrations/__init__.py diff --git a/tests/openedx_learning/apps/authoring/components/__init__.py b/openedx_learning/apps/authoring/backcompat/subsections/__init__.py similarity index 100% rename from tests/openedx_learning/apps/authoring/components/__init__.py rename to openedx_learning/apps/authoring/backcompat/subsections/__init__.py diff --git a/openedx_learning/apps/authoring/backcompat/subsections/apps.py b/openedx_learning/apps/authoring/backcompat/subsections/apps.py new file mode 100644 index 000000000..da2974685 --- /dev/null +++ b/openedx_learning/apps/authoring/backcompat/subsections/apps.py @@ -0,0 +1,16 @@ +""" +Subsection Django application initialization. +""" + +from django.apps import AppConfig + + +class SubsectionsConfig(AppConfig): + """ + Configuration for the subsections Django application. + """ + + name = "openedx_learning.apps.authoring.backcompat.subsections" + verbose_name = "Learning Core > Authoring > Subsections" + default_auto_field = "django.db.models.BigAutoField" + label = "oel_subsections" diff --git a/openedx_learning/apps/authoring/subsections/migrations/0001_initial.py b/openedx_learning/apps/authoring/backcompat/subsections/migrations/0001_initial.py similarity index 100% rename from openedx_learning/apps/authoring/subsections/migrations/0001_initial.py rename to openedx_learning/apps/authoring/backcompat/subsections/migrations/0001_initial.py diff --git a/openedx_learning/apps/authoring/backcompat/subsections/migrations/0002_remove_subsectionversion_container_version_and_more.py b/openedx_learning/apps/authoring/backcompat/subsections/migrations/0002_remove_subsectionversion_container_version_and_more.py new file mode 100644 index 000000000..86b89ee7f --- /dev/null +++ b/openedx_learning/apps/authoring/backcompat/subsections/migrations/0002_remove_subsectionversion_container_version_and_more.py @@ -0,0 +1,29 @@ +# Generated by Django 5.2.9 on 2026-01-09 19:07 + +from django.db import migrations +from django.db.migrations.operations.special import SeparateDatabaseAndState + + +class Migration(migrations.Migration): + + dependencies = [ + ('oel_subsections', '0001_initial'), + ] + + operations = [ + SeparateDatabaseAndState( + database_operations=[], + state_operations=[ + migrations.RemoveField( + model_name='subsectionversion', + name='container_version', + ), + migrations.DeleteModel( + name='Subsection', + ), + migrations.DeleteModel( + name='SubsectionVersion', + ), + ] + ) + ] diff --git a/tests/openedx_learning/apps/authoring/contents/__init__.py b/openedx_learning/apps/authoring/backcompat/subsections/migrations/__init__.py similarity index 100% rename from tests/openedx_learning/apps/authoring/contents/__init__.py rename to openedx_learning/apps/authoring/backcompat/subsections/migrations/__init__.py diff --git a/tests/openedx_learning/apps/authoring/publishing/__init__.py b/openedx_learning/apps/authoring/backcompat/units/__init__.py similarity index 100% rename from tests/openedx_learning/apps/authoring/publishing/__init__.py rename to openedx_learning/apps/authoring/backcompat/units/__init__.py diff --git a/openedx_learning/apps/authoring/backcompat/units/apps.py b/openedx_learning/apps/authoring/backcompat/units/apps.py new file mode 100644 index 000000000..643b7ba66 --- /dev/null +++ b/openedx_learning/apps/authoring/backcompat/units/apps.py @@ -0,0 +1,16 @@ +""" +Unit Django application initialization. +""" + +from django.apps import AppConfig + + +class UnitsConfig(AppConfig): + """ + Configuration for the units Django application. + """ + + name = "openedx_learning.apps.authoring.backcompat.units" + verbose_name = "Learning Core > Authoring > Units" + default_auto_field = "django.db.models.BigAutoField" + label = "oel_units" diff --git a/openedx_learning/apps/authoring/units/migrations/0001_initial.py b/openedx_learning/apps/authoring/backcompat/units/migrations/0001_initial.py similarity index 100% rename from openedx_learning/apps/authoring/units/migrations/0001_initial.py rename to openedx_learning/apps/authoring/backcompat/units/migrations/0001_initial.py diff --git a/openedx_learning/apps/authoring/backcompat/units/migrations/0002_remove_unitversion_container_version_delete_unit_and_more.py b/openedx_learning/apps/authoring/backcompat/units/migrations/0002_remove_unitversion_container_version_delete_unit_and_more.py new file mode 100644 index 000000000..238fae321 --- /dev/null +++ b/openedx_learning/apps/authoring/backcompat/units/migrations/0002_remove_unitversion_container_version_delete_unit_and_more.py @@ -0,0 +1,29 @@ +# Generated by Django 5.2.9 on 2026-01-09 19:07 + +from django.db import migrations +from django.db.migrations.operations.special import SeparateDatabaseAndState + + +class Migration(migrations.Migration): + + dependencies = [ + ('oel_units', '0001_initial'), + ] + + operations = [ + SeparateDatabaseAndState( + database_operations=[], + state_operations=[ + migrations.RemoveField( + model_name='unitversion', + name='container_version', + ), + migrations.DeleteModel( + name='Unit', + ), + migrations.DeleteModel( + name='UnitVersion', + ), + ] + ) + ] diff --git a/tests/openedx_learning/apps/authoring/sections/__init__.py b/openedx_learning/apps/authoring/backcompat/units/migrations/__init__.py similarity index 100% rename from tests/openedx_learning/apps/authoring/sections/__init__.py rename to openedx_learning/apps/authoring/backcompat/units/migrations/__init__.py diff --git a/openedx_learning/apps/authoring/components/apps.py b/openedx_learning/apps/authoring/components/apps.py deleted file mode 100644 index 591e60f6c..000000000 --- a/openedx_learning/apps/authoring/components/apps.py +++ /dev/null @@ -1,24 +0,0 @@ -""" -Django metadata for the Components Django application. -""" -from django.apps import AppConfig - - -class ComponentsConfig(AppConfig): - """ - Configuration for the Components Django application. - """ - - name = "openedx_learning.apps.authoring.components" - verbose_name = "Learning Core > Authoring > Components" - default_auto_field = "django.db.models.BigAutoField" - label = "oel_components" - - def ready(self) -> None: - """ - Register Component and ComponentVersion. - """ - from ..publishing.api import register_publishable_models # pylint: disable=import-outside-toplevel - from .models import Component, ComponentVersion # pylint: disable=import-outside-toplevel - - register_publishable_models(Component, ComponentVersion) diff --git a/tests/openedx_learning/apps/authoring/subsections/__init__.py b/openedx_learning/apps/authoring/management/__init__.py similarity index 100% rename from tests/openedx_learning/apps/authoring/subsections/__init__.py rename to openedx_learning/apps/authoring/management/__init__.py diff --git a/tests/openedx_learning/apps/authoring/units/__init__.py b/openedx_learning/apps/authoring/management/commands/__init__.py similarity index 100% rename from tests/openedx_learning/apps/authoring/units/__init__.py rename to openedx_learning/apps/authoring/management/commands/__init__.py diff --git a/openedx_learning/apps/authoring/components/management/commands/add_assets_to_component.py b/openedx_learning/apps/authoring/management/commands/add_assets_to_component.py similarity index 97% rename from openedx_learning/apps/authoring/components/management/commands/add_assets_to_component.py rename to openedx_learning/apps/authoring/management/commands/add_assets_to_component.py index 5e6518a99..d4306210c 100644 --- a/openedx_learning/apps/authoring/components/management/commands/add_assets_to_component.py +++ b/openedx_learning/apps/authoring/management/commands/add_assets_to_component.py @@ -9,8 +9,7 @@ from django.core.management.base import BaseCommand -from ....publishing.api import get_learning_package_by_key -from ...api import create_next_component_version, get_component_by_key +from ...api import create_next_component_version, get_component_by_key, get_learning_package_by_key class Command(BaseCommand): diff --git a/openedx_learning/apps/authoring/backup_restore/management/commands/lp_dump.py b/openedx_learning/apps/authoring/management/commands/lp_dump.py similarity index 93% rename from openedx_learning/apps/authoring/backup_restore/management/commands/lp_dump.py rename to openedx_learning/apps/authoring/management/commands/lp_dump.py index b1fb52b4e..de6a158c9 100644 --- a/openedx_learning/apps/authoring/backup_restore/management/commands/lp_dump.py +++ b/openedx_learning/apps/authoring/management/commands/lp_dump.py @@ -8,8 +8,8 @@ from django.core.management import CommandError from django.core.management.base import BaseCommand -from openedx_learning.apps.authoring.backup_restore.api import create_zip_file -from openedx_learning.apps.authoring.publishing.api import LearningPackage +from openedx_learning.apps.authoring.applets.backup_restore.api import create_zip_file +from openedx_learning.apps.authoring.applets.publishing.api import LearningPackage logger = logging.getLogger(__name__) diff --git a/openedx_learning/apps/authoring/backup_restore/management/commands/lp_load.py b/openedx_learning/apps/authoring/management/commands/lp_load.py similarity index 95% rename from openedx_learning/apps/authoring/backup_restore/management/commands/lp_load.py rename to openedx_learning/apps/authoring/management/commands/lp_load.py index a326a0850..18084f73a 100644 --- a/openedx_learning/apps/authoring/backup_restore/management/commands/lp_load.py +++ b/openedx_learning/apps/authoring/management/commands/lp_load.py @@ -8,7 +8,7 @@ from django.core.management import CommandError from django.core.management.base import BaseCommand -from openedx_learning.apps.authoring.backup_restore.api import load_learning_package +from openedx_learning.apps.authoring.applets.backup_restore.api import load_learning_package logger = logging.getLogger(__name__) diff --git a/openedx_learning/apps/authoring/migrations/0001_initial.py b/openedx_learning/apps/authoring/migrations/0001_initial.py new file mode 100644 index 000000000..e5fc145a2 --- /dev/null +++ b/openedx_learning/apps/authoring/migrations/0001_initial.py @@ -0,0 +1,655 @@ +""" +This migration has two modes it needs to run in: + +1. Existing installs that have migration data that is current through 0.30.2 + (bundled with the Ulmo release). +2. New installs. +""" +import uuid + +import django.core.validators +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models +from django.db.migrations.operations.special import SeparateDatabaseAndState +from django.db.migrations.recorder import MigrationRecorder + +import openedx_learning.lib.fields +import openedx_learning.lib.validators + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("oel_collections", "0006_remove_collectionpublishableentity_collection_and_more"), + ("oel_components", "0005_remove_component_component_type_and_more"), + ("oel_contents", "0002_delete_content_delete_mediatype"), + ("oel_publishing", "0011_remove_containerversion_container_and_more"), + ('oel_sections', '0002_remove_sectionversion_container_version_and_more'), + ('oel_subsections', '0002_remove_subsectionversion_container_version_and_more'), + ('oel_units', '0002_remove_unitversion_container_version_delete_unit_and_more'), + ] + + operations = [ + SeparateDatabaseAndState( + database_operations=[], + state_operations=[ + migrations.CreateModel( + name='PublishableEntity', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True, verbose_name='UUID')), + ('key', openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'}, db_column='_key', max_length=500)), + ('created', models.DateTimeField(validators=[openedx_learning.lib.validators.validate_utc_datetime])), + ('can_stand_alone', models.BooleanField(default=True, help_text='Set to True when created independently, False when created as part of a container.')), + ('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'Publishable Entity', + 'verbose_name_plural': 'Publishable Entities', + 'db_table': 'oel_publishing_publishableentity', + }, + ), + migrations.CreateModel( + name='ComponentType', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('namespace', openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'}, max_length=100)), + ('name', openedx_learning.lib.fields.MultiCollationCharField(blank=True, db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'}, max_length=100)), + ], + options={ + 'db_table': 'oel_components_componenttype', + }, + ), + migrations.CreateModel( + name='PublishableEntityVersion', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True, verbose_name='UUID')), + ('title', openedx_learning.lib.fields.MultiCollationCharField(blank=True, db_collations={'mysql': 'utf8mb4_unicode_ci', 'sqlite': 'NOCASE'}, default='', max_length=500)), + ('version_num', models.PositiveIntegerField(validators=[django.core.validators.MinValueValidator(1)])), + ('created', models.DateTimeField(validators=[openedx_learning.lib.validators.validate_utc_datetime])), + ('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)), + ('entity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='versions', to='oel_authoring.publishableentity')), + ], + options={ + 'verbose_name': 'Publishable Entity Version', + 'verbose_name_plural': 'Publishable Entity Versions', + 'db_table': 'oel_publishing_publishableentityversion', + }, + ), + migrations.CreateModel( + name='Content', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('size', models.PositiveBigIntegerField(validators=[django.core.validators.MaxValueValidator(50000000)])), + ('hash_digest', models.CharField(editable=False, max_length=40)), + ('has_file', models.BooleanField()), + ('text', openedx_learning.lib.fields.MultiCollationTextField(blank=True, db_collations={'mysql': 'utf8mb4_unicode_ci', 'sqlite': 'NOCASE'}, max_length=50000, null=True)), + ('created', models.DateTimeField(validators=[openedx_learning.lib.validators.validate_utc_datetime])), + ], + options={ + 'verbose_name': 'Content', + 'verbose_name_plural': 'Contents', + 'db_table': 'oel_contents_content', + }, + ), + migrations.CreateModel( + name='EntityList', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ], + options={ + 'db_table': 'oel_publishing_entitylist', + }, + ), + migrations.CreateModel( + name='LearningPackage', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True, verbose_name='UUID')), + ('key', openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'}, db_column='_key', max_length=500)), + ('title', openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_unicode_ci', 'sqlite': 'NOCASE'}, max_length=500)), + ('description', openedx_learning.lib.fields.MultiCollationTextField(blank=True, db_collations={'mysql': 'utf8mb4_unicode_ci', 'sqlite': 'NOCASE'}, default='', max_length=10000)), + ('created', models.DateTimeField(validators=[openedx_learning.lib.validators.validate_utc_datetime])), + ('updated', models.DateTimeField(validators=[openedx_learning.lib.validators.validate_utc_datetime])), + ], + options={ + 'verbose_name': 'Learning Package', + 'verbose_name_plural': 'Learning Packages', + 'db_table': 'oel_publishing_learningpackage', + }, + ), + migrations.CreateModel( + name='MediaType', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('type', openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_unicode_ci', 'sqlite': 'NOCASE'}, max_length=127)), + ('sub_type', openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_unicode_ci', 'sqlite': 'NOCASE'}, max_length=127)), + ('suffix', openedx_learning.lib.fields.MultiCollationCharField(blank=True, db_collations={'mysql': 'utf8mb4_unicode_ci', 'sqlite': 'NOCASE'}, max_length=127)), + ], + options={ + 'db_table': "oel_contents_mediatype", + }, + ), + migrations.CreateModel( + name='PublishableEntityVersionDependency', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ], + options={ + 'db_table': 'oel_publishing_publishableentityversiondependency', + }, + ), + migrations.CreateModel( + name='PublishLog', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True, verbose_name='UUID')), + ('message', openedx_learning.lib.fields.MultiCollationCharField(blank=True, db_collations={'mysql': 'utf8mb4_unicode_ci', 'sqlite': 'NOCASE'}, default='', max_length=500)), + ('published_at', models.DateTimeField(validators=[openedx_learning.lib.validators.validate_utc_datetime])), + ], + options={ + 'verbose_name': 'Publish Log', + 'verbose_name_plural': 'Publish Logs', + 'db_table': 'oel_publishing_publishlog', + }, + ), + migrations.CreateModel( + name='PublishLogRecord', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('dependencies_hash_digest', models.CharField(blank=True, default='', editable=False, max_length=8)), + ], + options={ + 'verbose_name': 'Publish Log Record', + 'verbose_name_plural': 'Publish Log Records', + 'db_table': 'oel_publishing_publishlogrecord', + }, + ), + migrations.CreateModel( + name='PublishSideEffect', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ], + options={ + 'verbose_name': 'Publish Side Effect', + 'verbose_name_plural': 'Publish Side Effects', + 'db_table': 'oel_publishing_publishsideeffect', + }, + ), + migrations.CreateModel( + name='Collection', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('key', openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'}, db_column='_key', max_length=500)), + ('title', openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_unicode_ci', 'sqlite': 'NOCASE'}, help_text='The title of the collection.', max_length=500)), + ('description', openedx_learning.lib.fields.MultiCollationTextField(blank=True, db_collations={'mysql': 'utf8mb4_unicode_ci', 'sqlite': 'NOCASE'}, default='', help_text='Provides extra information for the user about this collection.', max_length=10000)), + ('enabled', models.BooleanField(default=True, help_text='Disabled collections are "soft deleted", and should be re-enabled before use, or be deleted.')), + ('created', models.DateTimeField(auto_now_add=True, validators=[openedx_learning.lib.validators.validate_utc_datetime])), + ('modified', models.DateTimeField(auto_now=True, validators=[openedx_learning.lib.validators.validate_utc_datetime])), + ('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name_plural': 'Collections', + 'db_table': 'oel_collections_collection', + }, + ), + migrations.CreateModel( + name='Component', + fields=[ + ('publishable_entity', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='oel_authoring.publishableentity')), + ('local_key', openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'}, max_length=500)), + ], + options={ + 'verbose_name': 'Component', + 'verbose_name_plural': 'Components', + 'db_table': 'oel_components_component', + }, + ), + migrations.CreateModel( + name='Container', + fields=[ + ('publishable_entity', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='oel_authoring.publishableentity')), + ], + options={ + 'db_table': 'oel_publishing_container', + }, + ), + migrations.CreateModel( + name='Draft', + fields=[ + ('entity', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='oel_authoring.publishableentity')), + ], + options={ + 'db_table': 'oel_publishing_draft', + }, + ), + migrations.CreateModel( + name='Published', + fields=[ + ('entity', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='oel_authoring.publishableentity')), + ], + options={ + 'verbose_name': 'Published Entity', + 'verbose_name_plural': 'Published Entities', + 'db_table': 'oel_publishing_published', + }, + ), + migrations.CreateModel( + name='CollectionPublishableEntity', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', models.DateTimeField(auto_now_add=True, validators=[openedx_learning.lib.validators.validate_utc_datetime])), + ('collection', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='oel_authoring.collection')), + ('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)), + ('entity', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, to='oel_authoring.publishableentity')), + ], + options={ + 'db_table': 'oel_collections_collectionpublishableentity', + }, + ), + migrations.AddField( + model_name='collection', + name='entities', + field=models.ManyToManyField(related_name='collections', through='oel_authoring.CollectionPublishableEntity', to='oel_authoring.publishableentity'), + ), + migrations.CreateModel( + name='ComponentVersion', + fields=[ + ('publishable_entity_version', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='oel_authoring.publishableentityversion')), + ], + options={ + 'verbose_name': 'Component Version', + 'verbose_name_plural': 'Component Versions', + 'db_table': 'oel_components_componentversion', + }, + ), + migrations.CreateModel( + name='ContainerVersion', + fields=[ + ('publishable_entity_version', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='oel_authoring.publishableentityversion')), + ], + options={ + 'db_table': 'oel_publishing_containerversion', + }, + ), + migrations.CreateModel( + name='ComponentVersionContent', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('key', openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'}, db_column='_key', max_length=500)), + ('content', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, to='oel_authoring.content')), + ], + options={ + 'db_table': 'oel_components_componentversioncontent', + }, + ), + migrations.CreateModel( + name='DraftChangeLog', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True, verbose_name='UUID')), + ('changed_at', models.DateTimeField(validators=[openedx_learning.lib.validators.validate_utc_datetime])), + ('changed_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'Draft Change Log', + 'verbose_name_plural': 'Draft Change Logs', + 'db_table': 'oel_publishing_draftchangelog', + }, + ), + migrations.CreateModel( + name='DraftChangeLogRecord', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('dependencies_hash_digest', models.CharField(blank=True, default='', editable=False, max_length=8)), + ('draft_change_log', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='records', to='oel_authoring.draftchangelog')), + ('entity', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, to='oel_authoring.publishableentity')), + ('new_version', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.RESTRICT, to='oel_authoring.publishableentityversion')), + ('old_version', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.RESTRICT, related_name='+', to='oel_authoring.publishableentityversion')), + ], + options={ + 'verbose_name': 'Draft Change Log Record', + 'verbose_name_plural': 'Draft Change Log Records', + 'db_table': 'oel_publishing_draftchangelogrecord', + }, + ), + migrations.CreateModel( + name='DraftSideEffect', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('cause', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, related_name='causes', to='oel_authoring.draftchangelogrecord')), + ('effect', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, related_name='affected_by', to='oel_authoring.draftchangelogrecord')), + ], + options={ + 'verbose_name': 'Draft Side Effect', + 'verbose_name_plural': 'Draft Side Effects', + 'db_table': 'oel_publishing_draftsideeffect', + }, + ), + migrations.CreateModel( + name='EntityListRow', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('order_num', models.PositiveIntegerField()), + ('entity', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, to='oel_authoring.publishableentity')), + ('entity_list', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='oel_authoring.entitylist')), + ('entity_version', models.ForeignKey(null=True, on_delete=django.db.models.deletion.RESTRICT, related_name='+', to='oel_authoring.publishableentityversion')), + ], + options={ + 'db_table': 'oel_publishing_entitylistrow', + 'ordering': ['order_num'], + }, + ), + migrations.AddField( + model_name='publishableentity', + name='learning_package', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='publishable_entities', to='oel_authoring.learningpackage'), + ), + migrations.AddConstraint( + model_name='learningpackage', + constraint=models.UniqueConstraint(fields=('key',), name='oel_publishing_lp_uniq_key'), + ), + migrations.AddField( + model_name='draftchangelog', + name='learning_package', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='oel_authoring.learningpackage'), + ), + migrations.AddField( + model_name='content', + name='learning_package', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='oel_authoring.learningpackage'), + ), + migrations.AddField( + model_name='collection', + name='learning_package', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='oel_authoring.learningpackage'), + ), + migrations.AddConstraint( + model_name='mediatype', + constraint=models.UniqueConstraint(fields=('type', 'sub_type', 'suffix'), name='oel_contents_uniq_t_st_sfx'), + ), + migrations.AddField( + model_name='content', + name='media_type', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='oel_authoring.mediatype'), + ), + migrations.AddField( + model_name='publishableentityversiondependency', + name='referenced_entity', + field=models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, to='oel_authoring.publishableentity'), + ), + migrations.AddField( + model_name='publishableentityversiondependency', + name='referring_version', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='oel_authoring.publishableentityversion'), + ), + migrations.AddField( + model_name='publishableentityversion', + name='dependencies', + field=models.ManyToManyField(related_name='affects', through='oel_authoring.PublishableEntityVersionDependency', to='oel_authoring.publishableentity'), + ), + migrations.AddField( + model_name='publishlog', + name='learning_package', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='oel_authoring.learningpackage'), + ), + migrations.AddField( + model_name='publishlog', + name='published_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='publishlogrecord', + name='entity', + field=models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, to='oel_authoring.publishableentity'), + ), + migrations.AddField( + model_name='publishlogrecord', + name='new_version', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.RESTRICT, to='oel_authoring.publishableentityversion'), + ), + migrations.AddField( + model_name='publishlogrecord', + name='old_version', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.RESTRICT, related_name='+', to='oel_authoring.publishableentityversion'), + ), + migrations.AddField( + model_name='publishlogrecord', + name='publish_log', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='records', to='oel_authoring.publishlog'), + ), + migrations.AddField( + model_name='publishsideeffect', + name='cause', + field=models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, related_name='causes', to='oel_authoring.publishlogrecord'), + ), + migrations.AddField( + model_name='publishsideeffect', + name='effect', + field=models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, related_name='affected_by', to='oel_authoring.publishlogrecord'), + ), + migrations.AddField( + model_name='component', + name='component_type', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='oel_authoring.componenttype'), + ), + migrations.AddField( + model_name='component', + name='learning_package', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='oel_authoring.learningpackage'), + ), + migrations.CreateModel( + name='Section', + fields=[ + ('container', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='oel_authoring.container')), + ], + options={ + 'abstract': False, + 'db_table': "oel_sections_section", + }, + bases=('oel_authoring.container',), + ), + migrations.CreateModel( + name='Subsection', + fields=[ + ('container', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='oel_authoring.container')), + ], + options={ + 'abstract': False, + 'db_table': "oel_subsections_subsection", + }, + bases=('oel_authoring.container',), + ), + migrations.CreateModel( + name='Unit', + fields=[ + ('container', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='oel_authoring.container')), + ], + options={ + 'abstract': False, + 'db_table': "oel_units_unit", + }, + bases=('oel_authoring.container',), + ), + migrations.AddField( + model_name='draft', + name='draft_log_record', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='oel_authoring.draftchangelogrecord'), + ), + migrations.AddField( + model_name='draft', + name='version', + field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.RESTRICT, to='oel_authoring.publishableentityversion'), + ), + migrations.AddField( + model_name='published', + name='publish_log_record', + field=models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, to='oel_authoring.publishlogrecord'), + ), + migrations.AddField( + model_name='published', + name='version', + field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.RESTRICT, to='oel_authoring.publishableentityversion'), + ), + migrations.AddConstraint( + model_name='collectionpublishableentity', + constraint=models.UniqueConstraint(fields=('collection', 'entity'), name='oel_collections_cpe_uniq_col_ent'), + ), + migrations.AddField( + model_name='componentversioncontent', + name='component_version', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='oel_authoring.componentversion'), + ), + migrations.AddField( + model_name='componentversion', + name='component', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='versions', to='oel_authoring.component'), + ), + migrations.AddField( + model_name='componentversion', + name='contents', + field=models.ManyToManyField(related_name='component_versions', through='oel_authoring.ComponentVersionContent', to='oel_authoring.content'), + ), + migrations.CreateModel( + name='SectionVersion', + fields=[ + ('container_version', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='oel_authoring.containerversion')), + ], + options={ + 'abstract': False, + 'db_table': 'oel_sections_sectionversion', + }, + bases=('oel_authoring.containerversion',), + ), + migrations.CreateModel( + name='SubsectionVersion', + fields=[ + ('container_version', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='oel_authoring.containerversion')), + ], + options={ + 'abstract': False, + 'db_table': 'oel_subsections_subsectionversion', + }, + bases=('oel_authoring.containerversion',), + ), + migrations.CreateModel( + name='UnitVersion', + fields=[ + ('container_version', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='oel_authoring.containerversion')), + ], + options={ + 'abstract': False, + 'db_table': 'oel_units_unitversion', + }, + bases=('oel_authoring.containerversion',), + ), + migrations.AddField( + model_name='containerversion', + name='container', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='versions', to='oel_authoring.container'), + ), + migrations.AddField( + model_name='containerversion', + name='entity_list', + field=models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, related_name='container_versions', to='oel_authoring.entitylist'), + ), + migrations.AddIndex( + model_name='draftchangelogrecord', + index=models.Index(fields=['entity', '-draft_change_log'], name='oel_dlr_idx_entity_rdcl'), + ), + migrations.AddConstraint( + model_name='draftchangelogrecord', + constraint=models.UniqueConstraint(fields=('draft_change_log', 'entity'), name='oel_dlr_uniq_dcl'), + ), + migrations.AddConstraint( + model_name='draftsideeffect', + constraint=models.UniqueConstraint(fields=('cause', 'effect'), name='oel_pub_dse_uniq_c_e'), + ), + migrations.AddConstraint( + model_name='entitylistrow', + constraint=models.UniqueConstraint(fields=('entity_list', 'order_num'), name='oel_publishing_elist_row_order'), + ), + migrations.AddIndex( + model_name='publishableentity', + index=models.Index(fields=['key'], name='oel_pub_ent_idx_key'), + ), + migrations.AddIndex( + model_name='publishableentity', + index=models.Index(fields=['learning_package', '-created'], name='oel_pub_ent_idx_lp_rcreated'), + ), + migrations.AddConstraint( + model_name='publishableentity', + constraint=models.UniqueConstraint(fields=('learning_package', 'key'), name='oel_pub_ent_uniq_lp_key'), + ), + migrations.AddIndex( + model_name='collection', + index=models.Index(fields=['learning_package', 'title'], name='oel_collect_learnin_dfaf89_idx'), + ), + migrations.AddConstraint( + model_name='collection', + constraint=models.UniqueConstraint(fields=('learning_package', 'key'), name='oel_coll_uniq_lp_key'), + ), + migrations.AddIndex( + model_name='content', + index=models.Index(fields=['learning_package', '-size'], name='oel_content_idx_lp_rsize'), + ), + migrations.AddConstraint( + model_name='content', + constraint=models.UniqueConstraint(fields=('learning_package', 'media_type', 'hash_digest'), name='oel_content_uniq_lc_media_type_hash_digest'), + ), + migrations.AddConstraint( + model_name='publishableentityversiondependency', + constraint=models.UniqueConstraint(fields=('referring_version', 'referenced_entity'), name='oel_pevd_uniq_rv_re'), + ), + migrations.AddIndex( + model_name='publishableentityversion', + index=models.Index(fields=['entity', '-created'], name='oel_pv_idx_entity_rcreated'), + ), + migrations.AddIndex( + model_name='publishableentityversion', + index=models.Index(fields=['title'], name='oel_pv_idx_title'), + ), + migrations.AddConstraint( + model_name='publishableentityversion', + constraint=models.UniqueConstraint(fields=('entity', 'version_num'), name='oel_pv_uniq_entity_version_num'), + ), + migrations.AddIndex( + model_name='publishlogrecord', + index=models.Index(fields=['entity', '-publish_log'], name='oel_plr_idx_entity_rplr'), + ), + migrations.AddConstraint( + model_name='publishlogrecord', + constraint=models.UniqueConstraint(fields=('publish_log', 'entity'), name='oel_plr_uniq_pl_publishable'), + ), + migrations.AddConstraint( + model_name='publishsideeffect', + constraint=models.UniqueConstraint(fields=('cause', 'effect'), name='oel_pub_pse_uniq_c_e'), + ), + migrations.AddIndex( + model_name='component', + index=models.Index(fields=['component_type', 'local_key'], name='oel_component_idx_ct_lk'), + ), + migrations.AddConstraint( + model_name='component', + constraint=models.UniqueConstraint(fields=('learning_package', 'component_type', 'local_key'), name='oel_component_uniq_lc_ct_lk'), + ), + migrations.AddIndex( + model_name='componentversioncontent', + index=models.Index(fields=['content', 'component_version'], name='oel_cvcontent_c_cv'), + ), + migrations.AddIndex( + model_name='componentversioncontent', + index=models.Index(fields=['component_version', 'content'], name='oel_cvcontent_cv_d'), + ), + migrations.AddConstraint( + model_name='componentversioncontent', + constraint=models.UniqueConstraint(fields=('component_version', 'key'), name='oel_cvcontent_uniq_cv_key'), + ), + ] + ) + ] diff --git a/openedx_learning/apps/authoring/migrations/0002_rename_tables_to_oel_authoring.py b/openedx_learning/apps/authoring/migrations/0002_rename_tables_to_oel_authoring.py new file mode 100644 index 000000000..17cee6d25 --- /dev/null +++ b/openedx_learning/apps/authoring/migrations/0002_rename_tables_to_oel_authoring.py @@ -0,0 +1,138 @@ +# Generated by Django 5.2.9 on 2025-12-29 07:07 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('oel_authoring', '0001_initial'), + ] + + operations = [ + migrations.RenameIndex( + model_name='collection', + new_name='oel_authoring_coll_lp_title', + old_name='oel_collect_learnin_dfaf89_idx', + ), + migrations.AlterModelTable( + name='collection', + table=None, + ), + migrations.AlterModelTable( + name='collectionpublishableentity', + table=None, + ), + migrations.AlterModelTable( + name='content', + table=None, + ), + migrations.AlterModelTable( + name='component', + table=None, + ), + migrations.AlterModelTable( + name='componenttype', + table=None, + ), + migrations.AlterModelTable( + name='componentversion', + table=None, + ), + migrations.AlterModelTable( + name='componentversioncontent', + table=None, + ), + migrations.AlterModelTable( + name='container', + table=None, + ), + migrations.AlterModelTable( + name='containerversion', + table=None, + ), + migrations.AlterModelTable( + name='draft', + table=None, + ), + migrations.AlterModelTable( + name='draftchangelog', + table=None, + ), + migrations.AlterModelTable( + name='draftchangelogrecord', + table=None, + ), + migrations.AlterModelTable( + name='draftsideeffect', + table=None, + ), + migrations.AlterModelTable( + name='entitylist', + table=None, + ), + migrations.AlterModelTable( + name='entitylistrow', + table=None, + ), + migrations.AlterModelTable( + name='learningpackage', + table=None, + ), + migrations.AlterModelTable( + name='mediatype', + table=None, + ), + migrations.AlterModelTable( + name='publishableentity', + table=None, + ), + migrations.AlterModelTable( + name='publishableentityversion', + table=None, + ), + migrations.AlterModelTable( + name='publishableentityversiondependency', + table=None, + ), + migrations.AlterModelTable( + name='published', + table=None, + ), + migrations.AlterModelTable( + name='publishlog', + table=None, + ), + migrations.AlterModelTable( + name='publishlogrecord', + table=None, + ), + migrations.AlterModelTable( + name='publishsideeffect', + table=None, + ), + migrations.AlterModelTable( + name='section', + table=None, + ), + migrations.AlterModelTable( + name='sectionversion', + table=None, + ), + migrations.AlterModelTable( + name='subsection', + table=None, + ), + migrations.AlterModelTable( + name='subsectionversion', + table=None, + ), + migrations.AlterModelTable( + name='unit', + table=None, + ), + migrations.AlterModelTable( + name='unitversion', + table=None, + ), + ] diff --git a/openedx_learning/apps/authoring/migrations/__init__.py b/openedx_learning/apps/authoring/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/openedx_learning/apps/authoring/models.py b/openedx_learning/apps/authoring/models.py new file mode 100644 index 000000000..56f672a67 --- /dev/null +++ b/openedx_learning/apps/authoring/models.py @@ -0,0 +1,17 @@ +""" +This module aggregates all applet model modules. + +I experimented with creating a utility to auto-detect applets and magically +import their modules, but that broke code introspection. +""" + +# pylint: disable=wildcard-import + +from .applets.backup_restore.models import * +from .applets.collections.models import * +from .applets.components.models import * +from .applets.contents.models import * +from .applets.publishing.models import * +from .applets.sections.models import * +from .applets.subsections.models import * +from .applets.units.models import * diff --git a/openedx_learning/apps/authoring/publishing/apps.py b/openedx_learning/apps/authoring/publishing/apps.py deleted file mode 100644 index d3aefd8a6..000000000 --- a/openedx_learning/apps/authoring/publishing/apps.py +++ /dev/null @@ -1,25 +0,0 @@ -""" -publishing Django application initialization. -""" - -from django.apps import AppConfig - - -class PublishingConfig(AppConfig): - """ - Configuration for the publishing Django application. - """ - - name = "openedx_learning.apps.authoring.publishing" - verbose_name = "Learning Core > Authoring > Publishing" - default_auto_field = "django.db.models.BigAutoField" - label = "oel_publishing" - - def ready(self): - """ - Register Container and ContainerVersion. - """ - from .api import register_publishable_models # pylint: disable=import-outside-toplevel - from .models import Container, ContainerVersion # pylint: disable=import-outside-toplevel - - register_publishable_models(Container, ContainerVersion) diff --git a/openedx_learning/apps/authoring/publishing/migrations/0010_backfill_dependencies.py b/openedx_learning/apps/authoring/publishing/migrations/0010_backfill_dependencies.py deleted file mode 100644 index c274a401b..000000000 --- a/openedx_learning/apps/authoring/publishing/migrations/0010_backfill_dependencies.py +++ /dev/null @@ -1,149 +0,0 @@ -""" -Backfill PublishableEntityVersionDependency entries based on ContainerVersions. - -We're introducing a lower-level publishing concept of a dependency that will be -used by Containers, but this means we have to backfill that dependency info for -existing Containers in the system. -""" -from django.db import migrations -from django.db.models import F - - -def create_backfill(apps, schema_editor): - """ - Create dependency entries and update dep hashes for Draft and Published. - """ - _create_dependencies(apps) - _update_drafts(apps) - _update_draft_dependencies_hashes(apps) - _update_published_dependencies_hashes(apps) - - -def _create_dependencies(apps): - """ - Populate the PublishableEntityVersion.dependencies relation. - - The only ones we should have in the system at this point are the ones from - containers, so we query ContainerVersion for that. - """ - PublishableEntityVersionDependency = apps.get_model( - "oel_publishing", "PublishableEntityVersionDependency" - ) - ContainerVersion = apps.get_model("oel_publishing", "ContainerVersion") - - for container_version in ContainerVersion.objects.all(): - # child_entity_ids is a set to de-dupe. This doesn't handle pinned - # child references yet, but you can't actually make those in a real - # library yet, so we shouldn't have that data lying around to migrate. - child_entity_ids = set( - container_version - .entity_list - .entitylistrow_set - .all() - .values_list("entity_id", flat=True) - ) - PublishableEntityVersionDependency.objects.bulk_create( - [ - PublishableEntityVersionDependency( - referring_version_id=container_version.pk, - referenced_entity_id=entity_id - ) - for entity_id in child_entity_ids - ], - ignore_conflicts=True, - ) - - -def _update_drafts(apps): - """ - Update Draft entries to point to their most recent DraftLogRecord. - - This is slow and expensive. - """ - Draft = apps.get_model("oel_publishing", "Draft") - DraftChangeLogRecord = apps.get_model("oel_publishing", "DraftChangeLogRecord") - for draft in Draft.objects.all(): - draft_log_record = ( - # Find the most recent DraftChangeLogRecord related to this Draft, - DraftChangeLogRecord.objects - .filter(entity_id=draft.pk) - .order_by('-pk') - .first() - ) - draft.draft_log_record = draft_log_record - draft.save() - - -def _update_draft_dependencies_hashes(apps): - """ - Update the dependency_hash_digest for all DraftChangeLogRecords. - - Backfill dependency state hashes. The important thing here is that things - without dependencies will have the default (blank) state hash, so we only - need to query for Draft entries for Containers. - - We are only backfilling the current DraftChangeLogRecords pointed to by the - Draft entries now. We are not backfilling all historical - DraftChangeLogRecords. Full historical reconstruction is probably possible, - but it's not really worth the cost and complexity. - """ - from ..api import update_dependencies_hash_digests_for_log - from ..models import DraftChangeLog - - # All DraftChangeLogs that have records that are pointed to by the current - # Draft and have a possibility of having dependencies. - change_logs = DraftChangeLog.objects.filter( - pk=F('records__entity__draft__draft_log_record__draft_change_log'), - records__entity__draft__version__isnull=False, - records__entity__container__isnull=False, - ).distinct() - for change_log in change_logs: - update_dependencies_hash_digests_for_log(change_log, backfill=True) - -def _update_published_dependencies_hashes(apps): - """ - Update all container Published.dependencies_hash_digest - - Backfill dependency state hashes. The important thing here is that things - without dependencies will have the default (blank) state hash, so we only - need to query for Published entries for Containers. - """ - from ..api import update_dependencies_hash_digests_for_log - from ..models import PublishLog - - # All PublishLogs that have records that are pointed to by the current - # Published and have a possibility of having dependencies. - change_logs = PublishLog.objects.filter( - pk=F('records__entity__published__publish_log_record__publish_log'), - records__entity__published__version__isnull=False, - records__entity__container__isnull=False, - ).distinct() - for change_log in change_logs: - update_dependencies_hash_digests_for_log(change_log, backfill=True) - -def remove_backfill(apps, schema_editor): - """ - Reset all dep hash values to default ('') and remove dependencies. - """ - Draft = apps.get_model("oel_publishing", "Draft") - DraftChangeLogRecord = apps.get_model("oel_publishing", "DraftChangeLogRecord") - PublishLogRecord = apps.get_model("oel_publishing", "PublishLogRecord") - PublishableEntityVersionDependency = apps.get_model( - "oel_publishing", "PublishableEntityVersionDependency" - ) - - PublishLogRecord.objects.all().update(dependencies_hash_digest='') - DraftChangeLogRecord.objects.all().update(dependencies_hash_digest='') - PublishableEntityVersionDependency.objects.all().delete() - Draft.objects.all().update(draft_log_record=None) - - -class Migration(migrations.Migration): - - dependencies = [ - ('oel_publishing', '0009_dependencies_and_hashing'), - ] - - operations = [ - migrations.RunPython(create_backfill, reverse_code=remove_backfill) - ] diff --git a/openedx_learning/apps/authoring/sections/apps.py b/openedx_learning/apps/authoring/sections/apps.py deleted file mode 100644 index f506229e2..000000000 --- a/openedx_learning/apps/authoring/sections/apps.py +++ /dev/null @@ -1,25 +0,0 @@ -""" -Sections Django application initialization. -""" - -from django.apps import AppConfig - - -class SectionsConfig(AppConfig): - """ - Configuration for the Sections Django application. - """ - - name = "openedx_learning.apps.authoring.sections" - verbose_name = "Learning Core > Authoring > Sections" - default_auto_field = "django.db.models.BigAutoField" - label = "oel_sections" - - def ready(self): - """ - Register Section and SectionVersion. - """ - from ..publishing.api import register_publishable_models # pylint: disable=import-outside-toplevel - from .models import Section, SectionVersion # pylint: disable=import-outside-toplevel - - register_publishable_models(Section, SectionVersion) diff --git a/openedx_learning/apps/authoring/subsections/apps.py b/openedx_learning/apps/authoring/subsections/apps.py deleted file mode 100644 index 950e14143..000000000 --- a/openedx_learning/apps/authoring/subsections/apps.py +++ /dev/null @@ -1,25 +0,0 @@ -""" -Subsection Django application initialization. -""" - -from django.apps import AppConfig - - -class SubsectionsConfig(AppConfig): - """ - Configuration for the subsections Django application. - """ - - name = "openedx_learning.apps.authoring.subsections" - verbose_name = "Learning Core > Authoring > Subsections" - default_auto_field = "django.db.models.BigAutoField" - label = "oel_subsections" - - def ready(self): - """ - Register Subsection and SubsectionVersion. - """ - from ..publishing.api import register_publishable_models # pylint: disable=import-outside-toplevel - from .models import Subsection, SubsectionVersion # pylint: disable=import-outside-toplevel - - register_publishable_models(Subsection, SubsectionVersion) diff --git a/openedx_learning/apps/authoring/units/apps.py b/openedx_learning/apps/authoring/units/apps.py deleted file mode 100644 index 17affcf25..000000000 --- a/openedx_learning/apps/authoring/units/apps.py +++ /dev/null @@ -1,25 +0,0 @@ -""" -Unit Django application initialization. -""" - -from django.apps import AppConfig - - -class UnitsConfig(AppConfig): - """ - Configuration for the units Django application. - """ - - name = "openedx_learning.apps.authoring.units" - verbose_name = "Learning Core > Authoring > Units" - default_auto_field = "django.db.models.BigAutoField" - label = "oel_units" - - def ready(self): - """ - Register Unit and UnitVersion. - """ - from ..publishing.api import register_publishable_models # pylint: disable=import-outside-toplevel - from .models import Unit, UnitVersion # pylint: disable=import-outside-toplevel - - register_publishable_models(Unit, UnitVersion) diff --git a/openedx_learning/contrib/media_server/views.py b/openedx_learning/contrib/media_server/views.py index d7088879e..929867074 100644 --- a/openedx_learning/contrib/media_server/views.py +++ b/openedx_learning/contrib/media_server/views.py @@ -8,7 +8,7 @@ from django.core.exceptions import ObjectDoesNotExist from django.http import FileResponse, Http404 -from openedx_learning.apps.authoring.components.api import look_up_component_version_content +from openedx_learning.apps.authoring.applets.components.api import look_up_component_version_content def component_asset( diff --git a/projects/dev.py b/projects/dev.py index 1b3b42f47..395c7fd3d 100644 --- a/projects/dev.py +++ b/projects/dev.py @@ -4,6 +4,8 @@ from __future__ import annotations from pathlib import Path +from openedx_learning.api.django import learning_core_apps_to_install + # Build paths inside the project like this: BASE_DIR / {dir_name} / BASE_DIR = Path(__file__).resolve().parents[1] @@ -21,7 +23,7 @@ } } -INSTALLED_APPS = ( +INSTALLED_APPS = [ "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.messages", @@ -31,19 +33,7 @@ "django.contrib.admin", "django.contrib.admindocs", # Learning Core Apps - "openedx_learning.apps.authoring.collections.apps.CollectionsConfig", - "openedx_learning.apps.authoring.components.apps.ComponentsConfig", - "openedx_learning.apps.authoring.contents.apps.ContentsConfig", - "openedx_learning.apps.authoring.publishing.apps.PublishingConfig", - "openedx_learning.apps.authoring.sections.apps.SectionsConfig", - "openedx_learning.apps.authoring.subsections.apps.SubsectionsConfig", - "openedx_learning.apps.authoring.units.apps.UnitsConfig", - "openedx_learning.apps.authoring.backup_restore.apps.BackupRestoreConfig", - # Learning Contrib Apps - "openedx_learning.contrib.media_server.apps.MediaServerConfig", - # Apps that don't belong in this repo in the long term, but are here to make - # testing/iteration easier until the APIs stabilize. - "olx_importer.apps.OLXImporterConfig", + # REST API "rest_framework", @@ -51,10 +41,7 @@ 'rules.apps.AutodiscoverRulesConfig', # Tagging Core Apps "openedx_tagging.core.tagging.apps.TaggingConfig", - - # Debugging - "debug_toolbar", -) +] + learning_core_apps_to_install() AUTHENTICATION_BACKENDS = [ 'rules.permissions.ObjectPermissionBackend', @@ -62,7 +49,7 @@ ] MIDDLEWARE = [ - "debug_toolbar.middleware.DebugToolbarMiddleware", +# "debug_toolbar.middleware.DebugToolbarMiddleware", "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", diff --git a/projects/urls.py b/projects/urls.py index 583b004b4..082ff2113 100644 --- a/projects/urls.py +++ b/projects/urls.py @@ -9,5 +9,5 @@ path("admin/", admin.site.urls), path("media_server/", include("openedx_learning.contrib.media_server.urls")), path("tagging/rest_api/", include("openedx_tagging.core.tagging.urls")), - path('__debug__/', include('debug_toolbar.urls')), + # path('__debug__/', include('debug_toolbar.urls')), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/test_settings.py b/test_settings.py index c4b22926d..d8032d138 100644 --- a/test_settings.py +++ b/test_settings.py @@ -7,6 +7,8 @@ from os.path import abspath, dirname, join +from openedx_learning.api.django import learning_core_apps_to_install + def root(*args): """ @@ -18,18 +20,14 @@ def root(*args): DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", - "NAME": "default.db", - "USER": "", - "PASSWORD": "", - "HOST": "", - "PORT": "", + "NAME": ":memory:", } } # If you provision the 'oel'@'%' with broad permissions on your MySQL instance, # running the tests will auto-generate a database for running tests. This is # slower than the default sqlite3 setup above, but it's sometimes helpful for -# finding things that only break in CI. +# finding things that only break in CI. # # DATABASES = { # "default": { @@ -50,21 +48,11 @@ def root(*args): # Admin 'django.contrib.admin', 'django.contrib.admindocs', - # Debugging - "debug_toolbar", # django-rules based authorization 'rules.apps.AutodiscoverRulesConfig', # Our own apps - "openedx_learning.apps.authoring.collections.apps.CollectionsConfig", - "openedx_learning.apps.authoring.components.apps.ComponentsConfig", - "openedx_learning.apps.authoring.contents.apps.ContentsConfig", - "openedx_learning.apps.authoring.publishing.apps.PublishingConfig", - "openedx_tagging.core.tagging.apps.TaggingConfig", - "openedx_learning.apps.authoring.sections.apps.SectionsConfig", - "openedx_learning.apps.authoring.subsections.apps.SubsectionsConfig", - "openedx_learning.apps.authoring.units.apps.UnitsConfig", - "openedx_learning.apps.authoring.backup_restore.apps.BackupRestoreConfig", -] + "openedx_tagging.core.tagging", +] + learning_core_apps_to_install() AUTHENTICATION_BACKENDS = [ 'rules.permissions.ObjectPermissionBackend', diff --git a/tests/openedx_learning/apps/authoring/applets/__init__.py b/tests/openedx_learning/apps/authoring/applets/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/openedx_learning/apps/authoring/applets/backup_restore/__init__.py b/tests/openedx_learning/apps/authoring/applets/backup_restore/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/collections/collection-test.toml b/tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/collections/collection-test.toml similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/collections/collection-test.toml rename to tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/collections/collection-test.toml diff --git a/tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/section1-8ca126.toml b/tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/section1-8ca126.toml similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/section1-8ca126.toml rename to tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/section1-8ca126.toml diff --git a/tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/subsection1-48afa3.toml b/tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/subsection1-48afa3.toml similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/subsection1-48afa3.toml rename to tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/subsection1-48afa3.toml diff --git a/tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/unit1-b7eafb.toml b/tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/unit1-b7eafb.toml similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/unit1-b7eafb.toml rename to tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/unit1-b7eafb.toml diff --git a/tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/drag-and-drop-v2/4d1b2fac-8b30-42fb-872d-6b10ab580b27.toml b/tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/drag-and-drop-v2/4d1b2fac-8b30-42fb-872d-6b10ab580b27.toml similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/drag-and-drop-v2/4d1b2fac-8b30-42fb-872d-6b10ab580b27.toml rename to tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/drag-and-drop-v2/4d1b2fac-8b30-42fb-872d-6b10ab580b27.toml diff --git a/tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/drag-and-drop-v2/4d1b2fac-8b30-42fb-872d-6b10ab580b27/component_versions/v2/block.xml b/tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/drag-and-drop-v2/4d1b2fac-8b30-42fb-872d-6b10ab580b27/component_versions/v2/block.xml similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/drag-and-drop-v2/4d1b2fac-8b30-42fb-872d-6b10ab580b27/component_versions/v2/block.xml rename to tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/drag-and-drop-v2/4d1b2fac-8b30-42fb-872d-6b10ab580b27/component_versions/v2/block.xml diff --git a/tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/html/c22b9f97-f1e9-4e8f-87f0-d5a3c26083e2.toml b/tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/html/c22b9f97-f1e9-4e8f-87f0-d5a3c26083e2.toml similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/html/c22b9f97-f1e9-4e8f-87f0-d5a3c26083e2.toml rename to tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/html/c22b9f97-f1e9-4e8f-87f0-d5a3c26083e2.toml diff --git a/tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/html/c22b9f97-f1e9-4e8f-87f0-d5a3c26083e2/component_versions/v2/block.xml b/tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/html/c22b9f97-f1e9-4e8f-87f0-d5a3c26083e2/component_versions/v2/block.xml similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/html/c22b9f97-f1e9-4e8f-87f0-d5a3c26083e2/component_versions/v2/block.xml rename to tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/html/c22b9f97-f1e9-4e8f-87f0-d5a3c26083e2/component_versions/v2/block.xml diff --git a/tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/html/e32d5479-9492-41f6-9222-550a7346bc37.toml b/tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/html/e32d5479-9492-41f6-9222-550a7346bc37.toml similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/html/e32d5479-9492-41f6-9222-550a7346bc37.toml rename to tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/html/e32d5479-9492-41f6-9222-550a7346bc37.toml diff --git a/tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/html/e32d5479-9492-41f6-9222-550a7346bc37/component_versions/v4/block.xml b/tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/html/e32d5479-9492-41f6-9222-550a7346bc37/component_versions/v4/block.xml similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/html/e32d5479-9492-41f6-9222-550a7346bc37/component_versions/v4/block.xml rename to tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/html/e32d5479-9492-41f6-9222-550a7346bc37/component_versions/v4/block.xml diff --git a/tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/html/e32d5479-9492-41f6-9222-550a7346bc37/component_versions/v4/static/me.png b/tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/html/e32d5479-9492-41f6-9222-550a7346bc37/component_versions/v4/static/me.png similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/html/e32d5479-9492-41f6-9222-550a7346bc37/component_versions/v4/static/me.png rename to tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/html/e32d5479-9492-41f6-9222-550a7346bc37/component_versions/v4/static/me.png diff --git a/tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/html/e32d5479-9492-41f6-9222-550a7346bc37/component_versions/v5/block.xml b/tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/html/e32d5479-9492-41f6-9222-550a7346bc37/component_versions/v5/block.xml similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/html/e32d5479-9492-41f6-9222-550a7346bc37/component_versions/v5/block.xml rename to tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/html/e32d5479-9492-41f6-9222-550a7346bc37/component_versions/v5/block.xml diff --git a/tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/html/e32d5479-9492-41f6-9222-550a7346bc37/component_versions/v5/static/me.png b/tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/html/e32d5479-9492-41f6-9222-550a7346bc37/component_versions/v5/static/me.png similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/html/e32d5479-9492-41f6-9222-550a7346bc37/component_versions/v5/static/me.png rename to tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/html/e32d5479-9492-41f6-9222-550a7346bc37/component_versions/v5/static/me.png diff --git a/tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/openassessment/1ee38208-a585-4455-a27e-4930aa541f53.toml b/tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/openassessment/1ee38208-a585-4455-a27e-4930aa541f53.toml similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/openassessment/1ee38208-a585-4455-a27e-4930aa541f53.toml rename to tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/openassessment/1ee38208-a585-4455-a27e-4930aa541f53.toml diff --git a/tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/openassessment/1ee38208-a585-4455-a27e-4930aa541f53/component_versions/v2/block.xml b/tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/openassessment/1ee38208-a585-4455-a27e-4930aa541f53/component_versions/v2/block.xml similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/openassessment/1ee38208-a585-4455-a27e-4930aa541f53/component_versions/v2/block.xml rename to tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/openassessment/1ee38208-a585-4455-a27e-4930aa541f53/component_versions/v2/block.xml diff --git a/tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/problem/256739e8-c2df-4ced-bd10-8156f6cfa90b.toml b/tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/problem/256739e8-c2df-4ced-bd10-8156f6cfa90b.toml similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/problem/256739e8-c2df-4ced-bd10-8156f6cfa90b.toml rename to tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/problem/256739e8-c2df-4ced-bd10-8156f6cfa90b.toml diff --git a/tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/problem/256739e8-c2df-4ced-bd10-8156f6cfa90b/component_versions/v2/block.xml b/tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/problem/256739e8-c2df-4ced-bd10-8156f6cfa90b/component_versions/v2/block.xml similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/problem/256739e8-c2df-4ced-bd10-8156f6cfa90b/component_versions/v2/block.xml rename to tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/problem/256739e8-c2df-4ced-bd10-8156f6cfa90b/component_versions/v2/block.xml diff --git a/tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/survey/6681da3f-b056-4c6e-a8f9-040967907471.toml b/tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/survey/6681da3f-b056-4c6e-a8f9-040967907471.toml similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/survey/6681da3f-b056-4c6e-a8f9-040967907471.toml rename to tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/survey/6681da3f-b056-4c6e-a8f9-040967907471.toml diff --git a/tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/survey/6681da3f-b056-4c6e-a8f9-040967907471/component_versions/v1/block.xml b/tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/survey/6681da3f-b056-4c6e-a8f9-040967907471/component_versions/v1/block.xml similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/survey/6681da3f-b056-4c6e-a8f9-040967907471/component_versions/v1/block.xml rename to tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/survey/6681da3f-b056-4c6e-a8f9-040967907471/component_versions/v1/block.xml diff --git a/tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/video/22601ebd-9da8-430b-9778-cfe059a98568.toml b/tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/video/22601ebd-9da8-430b-9778-cfe059a98568.toml similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/video/22601ebd-9da8-430b-9778-cfe059a98568.toml rename to tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/video/22601ebd-9da8-430b-9778-cfe059a98568.toml diff --git a/tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/video/22601ebd-9da8-430b-9778-cfe059a98568/component_versions/v3/block.xml b/tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/video/22601ebd-9da8-430b-9778-cfe059a98568/component_versions/v3/block.xml similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/entities/xblock.v1/video/22601ebd-9da8-430b-9778-cfe059a98568/component_versions/v3/block.xml rename to tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/entities/xblock.v1/video/22601ebd-9da8-430b-9778-cfe059a98568/component_versions/v3/block.xml diff --git a/tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/package.toml b/tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/package.toml similarity index 100% rename from tests/openedx_learning/apps/authoring/backup_restore/fixtures/library_backup/package.toml rename to tests/openedx_learning/apps/authoring/applets/backup_restore/fixtures/library_backup/package.toml diff --git a/tests/openedx_learning/apps/authoring/backup_restore/test_backup.py b/tests/openedx_learning/apps/authoring/applets/backup_restore/test_backup.py similarity index 99% rename from tests/openedx_learning/apps/authoring/backup_restore/test_backup.py rename to tests/openedx_learning/apps/authoring/applets/backup_restore/test_backup.py index 312f289f1..0a23bb4db 100644 --- a/tests/openedx_learning/apps/authoring/backup_restore/test_backup.py +++ b/tests/openedx_learning/apps/authoring/applets/backup_restore/test_backup.py @@ -12,7 +12,7 @@ from openedx_learning.api import authoring as api from openedx_learning.api.authoring_models import Collection, Component, Content, LearningPackage, PublishableEntity -from openedx_learning.apps.authoring.backup_restore.zipper import LearningPackageZipper +from openedx_learning.apps.authoring.applets.backup_restore.zipper import LearningPackageZipper from openedx_learning.lib.test_utils import TestCase User = get_user_model() diff --git a/tests/openedx_learning/apps/authoring/backup_restore/test_restore.py b/tests/openedx_learning/apps/authoring/applets/backup_restore/test_restore.py similarity index 96% rename from tests/openedx_learning/apps/authoring/backup_restore/test_restore.py rename to tests/openedx_learning/apps/authoring/applets/backup_restore/test_restore.py index e34a0b5f7..f785b7127 100644 --- a/tests/openedx_learning/apps/authoring/backup_restore/test_restore.py +++ b/tests/openedx_learning/apps/authoring/applets/backup_restore/test_restore.py @@ -7,10 +7,13 @@ from django.contrib.auth import get_user_model from django.core.management import call_command -from openedx_learning.apps.authoring.backup_restore.zipper import LearningPackageUnzipper, generate_staged_lp_key -from openedx_learning.apps.authoring.collections import api as collections_api -from openedx_learning.apps.authoring.components import api as components_api -from openedx_learning.apps.authoring.publishing import api as publishing_api +from openedx_learning.apps.authoring.applets.backup_restore.zipper import ( + LearningPackageUnzipper, + generate_staged_lp_key, +) +from openedx_learning.apps.authoring.applets.collections import api as collections_api +from openedx_learning.apps.authoring.applets.components import api as components_api +from openedx_learning.apps.authoring.applets.publishing import api as publishing_api from openedx_learning.lib.test_utils import TestCase from test_utils.zip_file_utils import folder_to_inmemory_zip @@ -31,7 +34,7 @@ def setUp(self): class RestoreLearningPackageCommandTest(RestoreTestCase): """Tests for the lp_load management command.""" - @patch("openedx_learning.apps.authoring.backup_restore.api.load_learning_package") + @patch("openedx_learning.apps.authoring.applets.backup_restore.api.load_learning_package") def test_restore_command(self, mock_load_learning_package): # Mock load_learning_package to return our in-memory zip file restore_result = LearningPackageUnzipper(self.zip_file, user=self.user).load() @@ -284,7 +287,7 @@ def test_error_learning_package_missing_key(self): # Mock parse_learning_package_toml to return a dict without 'key' with patch( - "openedx_learning.apps.authoring.backup_restore.zipper.parse_learning_package_toml", + "openedx_learning.apps.authoring.applets.backup_restore.zipper.parse_learning_package_toml", return_value={ "learning_package": { "title": "Library test", @@ -315,7 +318,7 @@ def test_error_no_metadata_section(self): # Mock parse_learning_package_toml to return a dict without 'meta' with patch( - "openedx_learning.apps.authoring.backup_restore.zipper.parse_learning_package_toml", + "openedx_learning.apps.authoring.applets.backup_restore.zipper.parse_learning_package_toml", return_value={ "learning_package": { "title": "Library test", diff --git a/tests/openedx_learning/apps/authoring/backup_restore/test_slug_hash.py b/tests/openedx_learning/apps/authoring/applets/backup_restore/test_slug_hash.py similarity index 97% rename from tests/openedx_learning/apps/authoring/backup_restore/test_slug_hash.py rename to tests/openedx_learning/apps/authoring/applets/backup_restore/test_slug_hash.py index 292d08af1..d1073dc81 100644 --- a/tests/openedx_learning/apps/authoring/backup_restore/test_slug_hash.py +++ b/tests/openedx_learning/apps/authoring/applets/backup_restore/test_slug_hash.py @@ -5,7 +5,7 @@ generating slugified, hash-based filenames. """ -from openedx_learning.apps.authoring.backup_restore.zipper import slugify_hashed_filename +from openedx_learning.apps.authoring.applets.backup_restore.zipper import slugify_hashed_filename from openedx_learning.lib.test_utils import TestCase diff --git a/tests/openedx_learning/apps/authoring/applets/collections/__init__.py b/tests/openedx_learning/apps/authoring/applets/collections/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/openedx_learning/apps/authoring/collections/test_api.py b/tests/openedx_learning/apps/authoring/applets/collections/test_api.py similarity index 99% rename from tests/openedx_learning/apps/authoring/collections/test_api.py rename to tests/openedx_learning/apps/authoring/applets/collections/test_api.py index b70a8abd3..97b2f2ba4 100644 --- a/tests/openedx_learning/apps/authoring/collections/test_api.py +++ b/tests/openedx_learning/apps/authoring/applets/collections/test_api.py @@ -17,8 +17,8 @@ ComponentType, LearningPackage, PublishableEntity, + Unit, ) -from openedx_learning.apps.authoring.units.models import Unit from openedx_learning.lib.test_utils import TestCase User = get_user_model() diff --git a/tests/openedx_learning/apps/authoring/applets/components/__init__.py b/tests/openedx_learning/apps/authoring/applets/components/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/openedx_learning/apps/authoring/components/test_api.py b/tests/openedx_learning/apps/authoring/applets/components/test_api.py similarity index 97% rename from tests/openedx_learning/apps/authoring/components/test_api.py rename to tests/openedx_learning/apps/authoring/applets/components/test_api.py index 8a4dd44c5..e28e34d0f 100644 --- a/tests/openedx_learning/apps/authoring/components/test_api.py +++ b/tests/openedx_learning/apps/authoring/applets/components/test_api.py @@ -7,14 +7,14 @@ from django.contrib.auth.models import User as UserType # pylint: disable=imported-auth-user from django.core.exceptions import ObjectDoesNotExist -from openedx_learning.apps.authoring.collections import api as collection_api -from openedx_learning.apps.authoring.collections.models import Collection -from openedx_learning.apps.authoring.components import api as components_api -from openedx_learning.apps.authoring.components.models import Component, ComponentType -from openedx_learning.apps.authoring.contents import api as contents_api -from openedx_learning.apps.authoring.contents.models import MediaType -from openedx_learning.apps.authoring.publishing import api as publishing_api -from openedx_learning.apps.authoring.publishing.models import LearningPackage +from openedx_learning.apps.authoring.applets.collections import api as collection_api +from openedx_learning.apps.authoring.applets.collections.models import Collection +from openedx_learning.apps.authoring.applets.components import api as components_api +from openedx_learning.apps.authoring.applets.components.models import Component, ComponentType +from openedx_learning.apps.authoring.applets.contents import api as contents_api +from openedx_learning.apps.authoring.applets.contents.models import MediaType +from openedx_learning.apps.authoring.applets.publishing import api as publishing_api +from openedx_learning.apps.authoring.applets.publishing.models import LearningPackage from openedx_learning.lib.test_utils import TestCase User = get_user_model() diff --git a/tests/openedx_learning/apps/authoring/components/test_assets.py b/tests/openedx_learning/apps/authoring/applets/components/test_assets.py similarity index 94% rename from tests/openedx_learning/apps/authoring/components/test_assets.py rename to tests/openedx_learning/apps/authoring/applets/components/test_assets.py index f9cbf0643..d9f841d0e 100644 --- a/tests/openedx_learning/apps/authoring/components/test_assets.py +++ b/tests/openedx_learning/apps/authoring/applets/components/test_assets.py @@ -5,11 +5,11 @@ from pathlib import Path from uuid import uuid4 -from openedx_learning.apps.authoring.components import api as components_api -from openedx_learning.apps.authoring.components.api import AssetError -from openedx_learning.apps.authoring.contents import api as contents_api -from openedx_learning.apps.authoring.publishing import api as publishing_api -from openedx_learning.apps.authoring.publishing.models import LearningPackage +from openedx_learning.apps.authoring.applets.components import api as components_api +from openedx_learning.apps.authoring.applets.components.api import AssetError +from openedx_learning.apps.authoring.applets.contents import api as contents_api +from openedx_learning.apps.authoring.applets.publishing import api as publishing_api +from openedx_learning.apps.authoring.applets.publishing.models import LearningPackage from openedx_learning.lib.test_utils import TestCase diff --git a/tests/openedx_learning/apps/authoring/components/test_models.py b/tests/openedx_learning/apps/authoring/applets/components/test_models.py similarity index 96% rename from tests/openedx_learning/apps/authoring/components/test_models.py rename to tests/openedx_learning/apps/authoring/applets/components/test_models.py index ca786d281..ca6edaaa9 100644 --- a/tests/openedx_learning/apps/authoring/components/test_models.py +++ b/tests/openedx_learning/apps/authoring/applets/components/test_models.py @@ -6,13 +6,13 @@ from freezegun import freeze_time -from openedx_learning.apps.authoring.components.api import ( +from openedx_learning.apps.authoring.applets.components.api import ( create_component_and_version, get_component, get_or_create_component_type, ) -from openedx_learning.apps.authoring.components.models import Component, ComponentType, ComponentVersion -from openedx_learning.apps.authoring.publishing.api import ( +from openedx_learning.apps.authoring.applets.components.models import Component, ComponentType, ComponentVersion +from openedx_learning.apps.authoring.applets.publishing.api import ( LearningPackage, create_learning_package, create_publishable_entity_version, diff --git a/tests/openedx_learning/apps/authoring/applets/contents/__init__.py b/tests/openedx_learning/apps/authoring/applets/contents/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/openedx_learning/apps/authoring/contents/test_file_storage.py b/tests/openedx_learning/apps/authoring/applets/contents/test_file_storage.py similarity index 92% rename from tests/openedx_learning/apps/authoring/contents/test_file_storage.py rename to tests/openedx_learning/apps/authoring/applets/contents/test_file_storage.py index a94b31571..47dc356da 100644 --- a/tests/openedx_learning/apps/authoring/contents/test_file_storage.py +++ b/tests/openedx_learning/apps/authoring/applets/contents/test_file_storage.py @@ -7,9 +7,9 @@ from django.core.exceptions import ImproperlyConfigured from django.test import override_settings -from openedx_learning.apps.authoring.contents import api as contents_api -from openedx_learning.apps.authoring.contents.models import get_storage -from openedx_learning.apps.authoring.publishing import api as publishing_api +from openedx_learning.apps.authoring.applets.contents import api as contents_api +from openedx_learning.apps.authoring.applets.contents.models import get_storage +from openedx_learning.apps.authoring.applets.publishing import api as publishing_api from openedx_learning.lib.test_utils import TestCase diff --git a/tests/openedx_learning/apps/authoring/contents/test_media_types.py b/tests/openedx_learning/apps/authoring/applets/contents/test_media_types.py similarity index 91% rename from tests/openedx_learning/apps/authoring/contents/test_media_types.py rename to tests/openedx_learning/apps/authoring/applets/contents/test_media_types.py index 6f9b16b30..0216ce7bc 100644 --- a/tests/openedx_learning/apps/authoring/contents/test_media_types.py +++ b/tests/openedx_learning/apps/authoring/applets/contents/test_media_types.py @@ -1,7 +1,7 @@ """ A few tests to make sure our MediaType lookups are working as expected. """ -from openedx_learning.apps.authoring.contents import api as contents_api +from openedx_learning.apps.authoring.applets.contents import api as contents_api from openedx_learning.lib.test_utils import TestCase diff --git a/tests/openedx_learning/apps/authoring/applets/publishing/__init__.py b/tests/openedx_learning/apps/authoring/applets/publishing/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/openedx_learning/apps/authoring/publishing/test_api.py b/tests/openedx_learning/apps/authoring/applets/publishing/test_api.py similarity index 99% rename from tests/openedx_learning/apps/authoring/publishing/test_api.py rename to tests/openedx_learning/apps/authoring/applets/publishing/test_api.py index f44667178..d204c244e 100644 --- a/tests/openedx_learning/apps/authoring/publishing/test_api.py +++ b/tests/openedx_learning/apps/authoring/applets/publishing/test_api.py @@ -10,8 +10,8 @@ from django.contrib.auth import get_user_model from django.core.exceptions import ValidationError -from openedx_learning.apps.authoring.publishing import api as publishing_api -from openedx_learning.apps.authoring.publishing.models import ( +from openedx_learning.apps.authoring.applets.publishing import api as publishing_api +from openedx_learning.apps.authoring.applets.publishing.models import ( Container, ContainerVersion, Draft, diff --git a/tests/openedx_learning/apps/authoring/publishing/test_models.py b/tests/openedx_learning/apps/authoring/applets/publishing/test_models.py similarity index 82% rename from tests/openedx_learning/apps/authoring/publishing/test_models.py rename to tests/openedx_learning/apps/authoring/applets/publishing/test_models.py index 60b2d0840..3c37a567c 100644 --- a/tests/openedx_learning/apps/authoring/publishing/test_models.py +++ b/tests/openedx_learning/apps/authoring/applets/publishing/test_models.py @@ -3,7 +3,10 @@ """ from typing import TYPE_CHECKING, assert_type -from openedx_learning.apps.authoring.publishing.models import PublishableEntityMixin, PublishableEntityVersionMixin +from openedx_learning.apps.authoring.applets.publishing.models import ( + PublishableEntityMixin, + PublishableEntityVersionMixin, +) from openedx_learning.lib.managers import WithRelationsManager if TYPE_CHECKING: diff --git a/tests/openedx_learning/apps/authoring/applets/sections/__init__.py b/tests/openedx_learning/apps/authoring/applets/sections/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/openedx_learning/apps/authoring/sections/test_api.py b/tests/openedx_learning/apps/authoring/applets/sections/test_api.py similarity index 100% rename from tests/openedx_learning/apps/authoring/sections/test_api.py rename to tests/openedx_learning/apps/authoring/applets/sections/test_api.py diff --git a/tests/openedx_learning/apps/authoring/applets/subsections/__init__.py b/tests/openedx_learning/apps/authoring/applets/subsections/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/openedx_learning/apps/authoring/subsections/test_api.py b/tests/openedx_learning/apps/authoring/applets/subsections/test_api.py similarity index 99% rename from tests/openedx_learning/apps/authoring/subsections/test_api.py rename to tests/openedx_learning/apps/authoring/applets/subsections/test_api.py index 577881a9a..6bf4fb5f9 100644 --- a/tests/openedx_learning/apps/authoring/subsections/test_api.py +++ b/tests/openedx_learning/apps/authoring/applets/subsections/test_api.py @@ -262,7 +262,7 @@ def test_adding_external_units(self): created_by=None, ) - @patch('openedx_learning.apps.authoring.subsections.api._pub_entities_for_units') + @patch('openedx_learning.apps.authoring.applets.subsections.api._pub_entities_for_units') def test_adding_mismatched_versions(self, mock_entities_for_units): # pylint: disable=arguments-renamed """ Test that versioned units must match their entities. diff --git a/tests/openedx_learning/apps/authoring/applets/units/__init__.py b/tests/openedx_learning/apps/authoring/applets/units/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/openedx_learning/apps/authoring/units/test_api.py b/tests/openedx_learning/apps/authoring/applets/units/test_api.py similarity index 99% rename from tests/openedx_learning/apps/authoring/units/test_api.py rename to tests/openedx_learning/apps/authoring/applets/units/test_api.py index ebef4810a..f327e37f2 100644 --- a/tests/openedx_learning/apps/authoring/units/test_api.py +++ b/tests/openedx_learning/apps/authoring/applets/units/test_api.py @@ -250,7 +250,7 @@ def test_adding_external_components(self): created_by=None, ) - @patch('openedx_learning.apps.authoring.units.api._pub_entities_for_components') + @patch('openedx_learning.apps.authoring.applets.units.api._pub_entities_for_components') def test_adding_mismatched_versions(self, mock_entities_for_components): """ Test that versioned components must match their entities. diff --git a/tests/openedx_tagging/core/tagging/test_system_defined_models.py b/tests/openedx_tagging/core/tagging/test_system_defined_models.py index f771d82ee..ccd30b336 100644 --- a/tests/openedx_tagging/core/tagging/test_system_defined_models.py +++ b/tests/openedx_tagging/core/tagging/test_system_defined_models.py @@ -9,7 +9,7 @@ import pytest from django.test import TestCase, override_settings -from openedx_learning.apps.authoring.publishing.models import LearningPackage +from openedx_learning.apps.authoring.applets.publishing.models import LearningPackage from openedx_tagging.core.tagging import api from openedx_tagging.core.tagging.models import Taxonomy from openedx_tagging.core.tagging.models.system_defined import ModelSystemDefinedTaxonomy, UserSystemDefinedTaxonomy