openedx-learning 0.30.1__py2.py3-none-any.whl → 0.31.0__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openedx_learning/__init__.py +1 -1
- openedx_learning/api/authoring.py +2 -9
- openedx_learning/api/authoring_models.py +7 -7
- openedx_learning/api/django.py +24 -0
- openedx_learning/apps/openedx_content/admin.py +13 -0
- openedx_learning/apps/openedx_content/api.py +16 -0
- openedx_learning/apps/{authoring → openedx_content/applets}/backup_restore/api.py +2 -2
- openedx_learning/apps/{authoring → openedx_content/applets}/backup_restore/serializers.py +1 -4
- openedx_learning/apps/{authoring → openedx_content/applets}/backup_restore/toml.py +4 -4
- openedx_learning/apps/{authoring → openedx_content/applets}/backup_restore/zipper.py +28 -14
- openedx_learning/apps/{authoring → openedx_content/applets}/collections/api.py +1 -1
- openedx_learning/apps/{authoring → openedx_content/applets}/collections/models.py +4 -1
- openedx_learning/apps/{authoring → openedx_content/applets}/components/admin.py +1 -1
- openedx_learning/apps/{authoring → openedx_content/applets}/components/api.py +1 -1
- openedx_learning/apps/{authoring → openedx_content/applets}/components/models.py +4 -2
- openedx_learning/apps/{authoring → openedx_content/applets}/contents/api.py +3 -2
- openedx_learning/apps/{authoring → openedx_content/applets}/contents/models.py +8 -2
- openedx_learning/apps/{authoring → openedx_content/applets}/publishing/api.py +1 -1
- openedx_learning/apps/{authoring → openedx_content/applets}/publishing/models/entity_list.py +1 -0
- openedx_learning/apps/{authoring → openedx_content/applets}/sections/api.py +1 -2
- openedx_learning/apps/{authoring → openedx_content/applets}/subsections/api.py +1 -2
- openedx_learning/apps/{authoring → openedx_content/applets}/units/api.py +1 -2
- openedx_learning/apps/openedx_content/apps.py +52 -0
- openedx_learning/apps/{authoring → openedx_content/backcompat}/backup_restore/apps.py +1 -1
- openedx_learning/apps/{authoring → openedx_content/backcompat}/collections/apps.py +1 -1
- openedx_learning/apps/openedx_content/backcompat/collections/migrations/0006_remove_all_field_state_for_move_to_applet.py +82 -0
- openedx_learning/apps/openedx_content/backcompat/collections/models.py +9 -0
- openedx_learning/apps/openedx_content/backcompat/components/apps.py +15 -0
- openedx_learning/apps/openedx_content/backcompat/components/migrations/0005_remove_all_field_state_for_move_to_applet.py +72 -0
- openedx_learning/apps/openedx_content/backcompat/components/models.py +11 -0
- openedx_learning/apps/{authoring → openedx_content/backcompat}/contents/apps.py +1 -1
- openedx_learning/apps/openedx_content/backcompat/contents/migrations/0002_remove_all_field_state_for_move_to_applet.py +26 -0
- openedx_learning/apps/openedx_content/backcompat/publishing/apps.py +15 -0
- openedx_learning/apps/openedx_content/backcompat/publishing/migrations/0010_backfill_dependencies.py +432 -0
- openedx_learning/apps/openedx_content/backcompat/publishing/migrations/0011_remove_all_field_state_for_move_to_applet.py +288 -0
- openedx_learning/apps/openedx_content/backcompat/publishing/models.py +27 -0
- openedx_learning/apps/openedx_content/backcompat/sections/__init__.py +0 -0
- openedx_learning/apps/openedx_content/backcompat/sections/apps.py +16 -0
- openedx_learning/apps/openedx_content/backcompat/sections/migrations/0002_remove_all_field_state_for_move_to_applet.py +29 -0
- openedx_learning/apps/openedx_content/backcompat/sections/migrations/__init__.py +0 -0
- openedx_learning/apps/openedx_content/backcompat/subsections/__init__.py +0 -0
- openedx_learning/apps/openedx_content/backcompat/subsections/apps.py +16 -0
- openedx_learning/apps/openedx_content/backcompat/subsections/migrations/0002_remove_all_field_state_for_move_to_applet.py +29 -0
- openedx_learning/apps/openedx_content/backcompat/subsections/migrations/__init__.py +0 -0
- openedx_learning/apps/openedx_content/backcompat/units/__init__.py +0 -0
- openedx_learning/apps/openedx_content/backcompat/units/apps.py +16 -0
- openedx_learning/apps/openedx_content/backcompat/units/migrations/0002_remove_all_field_state_for_move_to_applet.py +29 -0
- openedx_learning/apps/openedx_content/backcompat/units/migrations/__init__.py +0 -0
- openedx_learning/apps/openedx_content/management/__init__.py +0 -0
- openedx_learning/apps/openedx_content/management/commands/__init__.py +0 -0
- openedx_learning/apps/{authoring/components → openedx_content}/management/commands/add_assets_to_component.py +1 -2
- openedx_learning/apps/{authoring/backup_restore → openedx_content}/management/commands/lp_dump.py +2 -2
- openedx_learning/apps/{authoring/backup_restore → openedx_content}/management/commands/lp_load.py +1 -1
- openedx_learning/apps/openedx_content/migrations/0001_initial.py +654 -0
- openedx_learning/apps/openedx_content/migrations/0002_rename_tables_to_openedx_content.py +138 -0
- openedx_learning/apps/openedx_content/migrations/__init__.py +0 -0
- openedx_learning/apps/openedx_content/models.py +17 -0
- openedx_learning/contrib/media_server/views.py +1 -1
- {openedx_learning-0.30.1.dist-info → openedx_learning-0.31.0.dist-info}/METADATA +6 -6
- openedx_learning-0.31.0.dist-info/RECORD +194 -0
- {openedx_learning-0.30.1.dist-info → openedx_learning-0.31.0.dist-info}/WHEEL +1 -1
- openedx_learning/apps/authoring/components/apps.py +0 -24
- openedx_learning/apps/authoring/publishing/apps.py +0 -25
- openedx_learning/apps/authoring/publishing/migrations/0010_backfill_dependencies.py +0 -149
- openedx_learning/apps/authoring/sections/apps.py +0 -25
- openedx_learning/apps/authoring/subsections/apps.py +0 -25
- openedx_learning/apps/authoring/units/apps.py +0 -25
- openedx_learning-0.30.1.dist-info/RECORD +0 -168
- /openedx_learning/apps/{authoring → openedx_content}/__init__.py +0 -0
- /openedx_learning/apps/{authoring/backup_restore → openedx_content/applets}/__init__.py +0 -0
- /openedx_learning/apps/{authoring/backup_restore/management → openedx_content/applets/backup_restore}/__init__.py +0 -0
- /openedx_learning/apps/{authoring → openedx_content/applets}/backup_restore/admin.py +0 -0
- /openedx_learning/apps/{authoring → openedx_content/applets}/backup_restore/models.py +0 -0
- /openedx_learning/apps/{authoring/backup_restore/management/commands → openedx_content/applets/collections}/__init__.py +0 -0
- /openedx_learning/apps/{authoring → openedx_content/applets}/collections/admin.py +0 -0
- /openedx_learning/apps/{authoring/backup_restore/migrations → openedx_content/applets/components}/__init__.py +0 -0
- /openedx_learning/apps/{authoring/collections → openedx_content/applets/contents}/__init__.py +0 -0
- /openedx_learning/apps/{authoring → openedx_content/applets}/contents/admin.py +0 -0
- /openedx_learning/apps/{authoring/collections/migrations → openedx_content/applets/publishing}/__init__.py +0 -0
- /openedx_learning/apps/{authoring → openedx_content/applets}/publishing/admin.py +0 -0
- /openedx_learning/apps/{authoring → openedx_content/applets}/publishing/contextmanagers.py +0 -0
- /openedx_learning/apps/{authoring → openedx_content/applets}/publishing/models/__init__.py +0 -0
- /openedx_learning/apps/{authoring → openedx_content/applets}/publishing/models/container.py +0 -0
- /openedx_learning/apps/{authoring → openedx_content/applets}/publishing/models/draft_log.py +0 -0
- /openedx_learning/apps/{authoring → openedx_content/applets}/publishing/models/learning_package.py +0 -0
- /openedx_learning/apps/{authoring → openedx_content/applets}/publishing/models/publish_log.py +0 -0
- /openedx_learning/apps/{authoring → openedx_content/applets}/publishing/models/publishable_entity.py +0 -0
- /openedx_learning/apps/{authoring/components → openedx_content/applets/sections}/__init__.py +0 -0
- /openedx_learning/apps/{authoring → openedx_content/applets}/sections/admin.py +0 -0
- /openedx_learning/apps/{authoring → openedx_content/applets}/sections/models.py +0 -0
- /openedx_learning/apps/{authoring/components/management → openedx_content/applets/subsections}/__init__.py +0 -0
- /openedx_learning/apps/{authoring → openedx_content/applets}/subsections/admin.py +0 -0
- /openedx_learning/apps/{authoring → openedx_content/applets}/subsections/models.py +0 -0
- /openedx_learning/apps/{authoring/components/management/commands → openedx_content/applets/units}/__init__.py +0 -0
- /openedx_learning/apps/{authoring → openedx_content/applets}/units/admin.py +0 -0
- /openedx_learning/apps/{authoring → openedx_content/applets}/units/models.py +0 -0
- /openedx_learning/apps/{authoring/components/migrations → openedx_content/backcompat}/__init__.py +0 -0
- /openedx_learning/apps/{authoring/contents → openedx_content/backcompat/backup_restore}/__init__.py +0 -0
- /openedx_learning/apps/{authoring/contents → openedx_content/backcompat/backup_restore}/migrations/__init__.py +0 -0
- /openedx_learning/apps/{authoring/publishing → openedx_content/backcompat/collections}/__init__.py +0 -0
- /openedx_learning/apps/{authoring → openedx_content/backcompat}/collections/migrations/0001_initial.py +0 -0
- /openedx_learning/apps/{authoring → openedx_content/backcompat}/collections/migrations/0002_remove_collection_name_collection_created_by_and_more.py +0 -0
- /openedx_learning/apps/{authoring → openedx_content/backcompat}/collections/migrations/0003_collection_entities.py +0 -0
- /openedx_learning/apps/{authoring → openedx_content/backcompat}/collections/migrations/0004_collection_key.py +0 -0
- /openedx_learning/apps/{authoring → openedx_content/backcompat}/collections/migrations/0005_alter_collection_options_alter_collection_enabled.py +0 -0
- /openedx_learning/apps/{authoring/publishing → openedx_content/backcompat/collections}/migrations/__init__.py +0 -0
- /openedx_learning/apps/{authoring/sections → openedx_content/backcompat/components}/__init__.py +0 -0
- /openedx_learning/apps/{authoring → openedx_content/backcompat}/components/migrations/0001_initial.py +0 -0
- /openedx_learning/apps/{authoring → openedx_content/backcompat}/components/migrations/0002_alter_componentversioncontent_key.py +0 -0
- /openedx_learning/apps/{authoring → openedx_content/backcompat}/components/migrations/0003_remove_componentversioncontent_learner_downloadable.py +0 -0
- /openedx_learning/apps/{authoring → openedx_content/backcompat}/components/migrations/0004_remove_componentversioncontent_uuid.py +0 -0
- /openedx_learning/apps/{authoring/sections → openedx_content/backcompat/components}/migrations/__init__.py +0 -0
- /openedx_learning/apps/{authoring/subsections → openedx_content/backcompat/contents}/__init__.py +0 -0
- /openedx_learning/apps/{authoring → openedx_content/backcompat}/contents/migrations/0001_initial.py +0 -0
- /openedx_learning/apps/{authoring/subsections → openedx_content/backcompat/contents}/migrations/__init__.py +0 -0
- /openedx_learning/apps/{authoring/units → openedx_content/backcompat/publishing}/__init__.py +0 -0
- /openedx_learning/apps/{authoring → openedx_content/backcompat}/publishing/migrations/0001_initial.py +0 -0
- /openedx_learning/apps/{authoring → openedx_content/backcompat}/publishing/migrations/0002_alter_learningpackage_key_and_more.py +0 -0
- /openedx_learning/apps/{authoring → openedx_content/backcompat}/publishing/migrations/0003_containers.py +0 -0
- /openedx_learning/apps/{authoring → openedx_content/backcompat}/publishing/migrations/0004_publishableentity_can_stand_alone.py +0 -0
- /openedx_learning/apps/{authoring → openedx_content/backcompat}/publishing/migrations/0005_alter_entitylistrow_options.py +0 -0
- /openedx_learning/apps/{authoring → openedx_content/backcompat}/publishing/migrations/0006_draftchangelog.py +0 -0
- /openedx_learning/apps/{authoring → openedx_content/backcompat}/publishing/migrations/0007_bootstrap_draftchangelog.py +0 -0
- /openedx_learning/apps/{authoring → openedx_content/backcompat}/publishing/migrations/0008_alter_draftchangelogrecord_options_and_more.py +0 -0
- /openedx_learning/apps/{authoring → openedx_content/backcompat}/publishing/migrations/0009_dependencies_and_hashing.py +0 -0
- /openedx_learning/apps/{authoring/units → openedx_content/backcompat/publishing}/migrations/__init__.py +0 -0
- /openedx_learning/apps/{authoring → openedx_content/backcompat}/sections/migrations/0001_initial.py +0 -0
- /openedx_learning/apps/{authoring → openedx_content/backcompat}/subsections/migrations/0001_initial.py +0 -0
- /openedx_learning/apps/{authoring → openedx_content/backcompat}/units/migrations/0001_initial.py +0 -0
- {openedx_learning-0.30.1.dist-info → openedx_learning-0.31.0.dist-info}/licenses/LICENSE.txt +0 -0
- {openedx_learning-0.30.1.dist-info → openedx_learning-0.31.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Django metadata for the Components Django application.
|
|
3
|
+
"""
|
|
4
|
+
from django.apps import AppConfig
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ComponentsConfig(AppConfig):
|
|
8
|
+
"""
|
|
9
|
+
Configuration for the Components Django application.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
name = "openedx_learning.apps.openedx_content.backcompat.components"
|
|
13
|
+
verbose_name = "Learning Core > Authoring > Components"
|
|
14
|
+
default_auto_field = "django.db.models.BigAutoField"
|
|
15
|
+
label = "oel_components"
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Generated by Django 5.2.10 on 2026-01-30 00:36
|
|
2
|
+
|
|
3
|
+
from django.db import migrations
|
|
4
|
+
from django.db.migrations.operations.special import SeparateDatabaseAndState
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
|
|
9
|
+
dependencies = [
|
|
10
|
+
('oel_components', '0004_remove_componentversioncontent_uuid'),
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
operations = [
|
|
14
|
+
SeparateDatabaseAndState(
|
|
15
|
+
database_operations=[],
|
|
16
|
+
state_operations=[
|
|
17
|
+
migrations.RemoveField(
|
|
18
|
+
model_name='component',
|
|
19
|
+
name='component_type',
|
|
20
|
+
),
|
|
21
|
+
migrations.RemoveField(
|
|
22
|
+
model_name='componentversion',
|
|
23
|
+
name='component',
|
|
24
|
+
),
|
|
25
|
+
migrations.RemoveField(
|
|
26
|
+
model_name='componentversion',
|
|
27
|
+
name='contents',
|
|
28
|
+
),
|
|
29
|
+
migrations.RemoveField(
|
|
30
|
+
model_name='componentversion',
|
|
31
|
+
name='publishable_entity_version',
|
|
32
|
+
),
|
|
33
|
+
migrations.RemoveField(
|
|
34
|
+
model_name='componentversioncontent',
|
|
35
|
+
name='component_version',
|
|
36
|
+
),
|
|
37
|
+
migrations.RemoveField(
|
|
38
|
+
model_name='componentversioncontent',
|
|
39
|
+
name='content',
|
|
40
|
+
),
|
|
41
|
+
migrations.AlterModelOptions(
|
|
42
|
+
name='component',
|
|
43
|
+
options={},
|
|
44
|
+
),
|
|
45
|
+
migrations.RemoveConstraint(
|
|
46
|
+
model_name='component',
|
|
47
|
+
name='oel_component_uniq_lc_ct_lk',
|
|
48
|
+
),
|
|
49
|
+
migrations.RemoveIndex(
|
|
50
|
+
model_name='component',
|
|
51
|
+
name='oel_component_idx_ct_lk',
|
|
52
|
+
),
|
|
53
|
+
migrations.RemoveField(
|
|
54
|
+
model_name='component',
|
|
55
|
+
name='learning_package',
|
|
56
|
+
),
|
|
57
|
+
migrations.RemoveField(
|
|
58
|
+
model_name='component',
|
|
59
|
+
name='local_key',
|
|
60
|
+
),
|
|
61
|
+
migrations.DeleteModel(
|
|
62
|
+
name='ComponentType',
|
|
63
|
+
),
|
|
64
|
+
migrations.DeleteModel(
|
|
65
|
+
name='ComponentVersion',
|
|
66
|
+
),
|
|
67
|
+
migrations.DeleteModel(
|
|
68
|
+
name='ComponentVersionContent',
|
|
69
|
+
),
|
|
70
|
+
]
|
|
71
|
+
),
|
|
72
|
+
]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""
|
|
2
|
+
These are stub models necessary to ensure a smooth migration for
|
|
3
|
+
openedx-platform apps that were built to have foreign keys to these models.
|
|
4
|
+
"""
|
|
5
|
+
from django.db import models
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Component(models.Model):
|
|
9
|
+
publishable_entity = models.OneToOneField(
|
|
10
|
+
'oel_publishing.PublishableEntity', on_delete=models.CASCADE, primary_key=True
|
|
11
|
+
)
|
|
@@ -9,7 +9,7 @@ class ContentsConfig(AppConfig):
|
|
|
9
9
|
Configuration for the Contents Django application.
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
|
-
name = "openedx_learning.apps.
|
|
12
|
+
name = "openedx_learning.apps.openedx_content.backcompat.contents"
|
|
13
13
|
verbose_name = "Learning Core > Authoring > Contents"
|
|
14
14
|
default_auto_field = "django.db.models.BigAutoField"
|
|
15
15
|
label = "oel_contents"
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Generated by Django 5.2.10 on 2026-01-30 00:36
|
|
2
|
+
|
|
3
|
+
from django.db import migrations
|
|
4
|
+
from django.db.migrations.operations.special import SeparateDatabaseAndState
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
|
|
9
|
+
dependencies = [
|
|
10
|
+
('oel_components', '0005_remove_all_field_state_for_move_to_applet'),
|
|
11
|
+
('oel_contents', '0001_initial'),
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
operations = [
|
|
15
|
+
SeparateDatabaseAndState(
|
|
16
|
+
database_operations=[],
|
|
17
|
+
state_operations=[
|
|
18
|
+
migrations.DeleteModel(
|
|
19
|
+
name='Content',
|
|
20
|
+
),
|
|
21
|
+
migrations.DeleteModel(
|
|
22
|
+
name='MediaType',
|
|
23
|
+
),
|
|
24
|
+
]
|
|
25
|
+
),
|
|
26
|
+
]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Publishing Django application initialization.
|
|
3
|
+
"""
|
|
4
|
+
from django.apps import AppConfig
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class PublishingConfig(AppConfig):
|
|
8
|
+
"""
|
|
9
|
+
Configuration for the publishing Django application.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
name = "openedx_learning.apps.openedx_content.backcompat.publishing"
|
|
13
|
+
verbose_name = "Learning Core > Authoring > Publishing"
|
|
14
|
+
default_auto_field = "django.db.models.BigAutoField"
|
|
15
|
+
label = "oel_publishing"
|
openedx_learning/apps/openedx_content/backcompat/publishing/migrations/0010_backfill_dependencies.py
ADDED
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Backfill PublishableEntityVersionDependency entries based on ContainerVersions.
|
|
3
|
+
|
|
4
|
+
We're introducing a lower-level publishing concept of a dependency that will be
|
|
5
|
+
used by Containers, but this means we have to backfill that dependency info for
|
|
6
|
+
existing Containers in the system.
|
|
7
|
+
|
|
8
|
+
For more information, please see the PublishableEntityVersionDependency model.
|
|
9
|
+
|
|
10
|
+
Note that we copy a *lot* of API code here because we want to make sure that
|
|
11
|
+
this migration continues to work on the historical version of the models at this
|
|
12
|
+
point in time, regardless of any future changes that might be made to the API
|
|
13
|
+
for calculating dependencies. Most of the API code here is copy-pasted from the
|
|
14
|
+
publishing API, with very minor changes to grab the historical models using
|
|
15
|
+
apps.get_model(), rather than importing the models directly.
|
|
16
|
+
"""
|
|
17
|
+
from django.db import migrations
|
|
18
|
+
from django.db.models import F, Prefetch
|
|
19
|
+
|
|
20
|
+
from openedx_learning.lib.fields import create_hash_digest
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def create_backfill(apps, schema_editor):
|
|
24
|
+
"""
|
|
25
|
+
Create dependency entries and update dep hashes for Draft and Published.
|
|
26
|
+
"""
|
|
27
|
+
_create_dependencies(apps)
|
|
28
|
+
_update_drafts(apps)
|
|
29
|
+
_update_draft_dependencies_hashes(apps)
|
|
30
|
+
_update_published_dependencies_hashes(apps)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _create_dependencies(apps):
|
|
34
|
+
"""
|
|
35
|
+
Populate the PublishableEntityVersion.dependencies relation.
|
|
36
|
+
|
|
37
|
+
The only ones we should have in the system at this point are the ones from
|
|
38
|
+
containers, so we query ContainerVersion for that.
|
|
39
|
+
"""
|
|
40
|
+
PublishableEntityVersionDependency = apps.get_model(
|
|
41
|
+
"oel_publishing", "PublishableEntityVersionDependency"
|
|
42
|
+
)
|
|
43
|
+
ContainerVersion = apps.get_model("oel_publishing", "ContainerVersion")
|
|
44
|
+
|
|
45
|
+
for container_version in ContainerVersion.objects.all():
|
|
46
|
+
# child_entity_ids is a set to de-dupe. This doesn't handle pinned
|
|
47
|
+
# child references yet, but you can't actually make those in a real
|
|
48
|
+
# library yet, so we shouldn't have that data lying around to migrate.
|
|
49
|
+
child_entity_ids = set(
|
|
50
|
+
container_version
|
|
51
|
+
.entity_list
|
|
52
|
+
.entitylistrow_set
|
|
53
|
+
.all()
|
|
54
|
+
.values_list("entity_id", flat=True)
|
|
55
|
+
)
|
|
56
|
+
PublishableEntityVersionDependency.objects.bulk_create(
|
|
57
|
+
[
|
|
58
|
+
PublishableEntityVersionDependency(
|
|
59
|
+
referring_version_id=container_version.pk,
|
|
60
|
+
referenced_entity_id=entity_id
|
|
61
|
+
)
|
|
62
|
+
for entity_id in child_entity_ids
|
|
63
|
+
],
|
|
64
|
+
ignore_conflicts=True,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _update_drafts(apps):
|
|
69
|
+
"""
|
|
70
|
+
Update Draft entries to point to their most recent DraftLogRecord.
|
|
71
|
+
|
|
72
|
+
This is slow and expensive.
|
|
73
|
+
"""
|
|
74
|
+
Draft = apps.get_model("oel_publishing", "Draft")
|
|
75
|
+
DraftChangeLogRecord = apps.get_model("oel_publishing", "DraftChangeLogRecord")
|
|
76
|
+
for draft in Draft.objects.all():
|
|
77
|
+
draft_log_record = (
|
|
78
|
+
# Find the most recent DraftChangeLogRecord related to this Draft,
|
|
79
|
+
DraftChangeLogRecord.objects
|
|
80
|
+
.filter(entity_id=draft.pk)
|
|
81
|
+
.order_by('-pk')
|
|
82
|
+
.first()
|
|
83
|
+
)
|
|
84
|
+
draft.draft_log_record = draft_log_record
|
|
85
|
+
draft.save()
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _update_draft_dependencies_hashes(apps):
|
|
89
|
+
"""
|
|
90
|
+
Update the dependency_hash_digest for all DraftChangeLogRecords.
|
|
91
|
+
|
|
92
|
+
Backfill dependency state hashes. The important thing here is that things
|
|
93
|
+
without dependencies will have the default (blank) state hash, so we only
|
|
94
|
+
need to query for Draft entries for Containers.
|
|
95
|
+
|
|
96
|
+
We are only backfilling the current DraftChangeLogRecords pointed to by the
|
|
97
|
+
Draft entries now. We are not backfilling all historical
|
|
98
|
+
DraftChangeLogRecords. Full historical reconstruction is probably possible,
|
|
99
|
+
but it's not really worth the cost and complexity.
|
|
100
|
+
"""
|
|
101
|
+
DraftChangeLog = apps.get_model("oel_publishing", "DraftChangeLog")
|
|
102
|
+
|
|
103
|
+
# All DraftChangeLogs that have records that are pointed to by the current
|
|
104
|
+
# Draft and have a possibility of having dependencies.
|
|
105
|
+
change_logs = DraftChangeLog.objects.filter(
|
|
106
|
+
pk=F('records__entity__draft__draft_log_record__draft_change_log'),
|
|
107
|
+
records__entity__draft__version__isnull=False,
|
|
108
|
+
records__entity__container__isnull=False,
|
|
109
|
+
).distinct()
|
|
110
|
+
for change_log in change_logs:
|
|
111
|
+
update_dependencies_hash_digests_for_log(change_log, apps)
|
|
112
|
+
|
|
113
|
+
def _update_published_dependencies_hashes(apps):
|
|
114
|
+
"""
|
|
115
|
+
Update all container Published.dependencies_hash_digest
|
|
116
|
+
|
|
117
|
+
Backfill dependency state hashes. The important thing here is that things
|
|
118
|
+
without dependencies will have the default (blank) state hash, so we only
|
|
119
|
+
need to query for Published entries for Containers.
|
|
120
|
+
"""
|
|
121
|
+
PublishLog = apps.get_model("oel_publishing", "PublishLog")
|
|
122
|
+
|
|
123
|
+
# All PublishLogs that have records that are pointed to by the current
|
|
124
|
+
# Published and have a possibility of having dependencies.
|
|
125
|
+
change_logs = PublishLog.objects.filter(
|
|
126
|
+
pk=F('records__entity__published__publish_log_record__publish_log'),
|
|
127
|
+
records__entity__published__version__isnull=False,
|
|
128
|
+
records__entity__container__isnull=False,
|
|
129
|
+
).distinct()
|
|
130
|
+
for change_log in change_logs:
|
|
131
|
+
update_dependencies_hash_digests_for_log(change_log, apps)
|
|
132
|
+
|
|
133
|
+
def remove_backfill(apps, schema_editor):
|
|
134
|
+
"""
|
|
135
|
+
Reset all dep hash values to default ('') and remove dependencies.
|
|
136
|
+
"""
|
|
137
|
+
Draft = apps.get_model("oel_publishing", "Draft")
|
|
138
|
+
DraftChangeLogRecord = apps.get_model("oel_publishing", "DraftChangeLogRecord")
|
|
139
|
+
PublishLogRecord = apps.get_model("oel_publishing", "PublishLogRecord")
|
|
140
|
+
PublishableEntityVersionDependency = apps.get_model(
|
|
141
|
+
"oel_publishing", "PublishableEntityVersionDependency"
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
PublishLogRecord.objects.all().update(dependencies_hash_digest='')
|
|
145
|
+
DraftChangeLogRecord.objects.all().update(dependencies_hash_digest='')
|
|
146
|
+
PublishableEntityVersionDependency.objects.all().delete()
|
|
147
|
+
Draft.objects.all().update(draft_log_record=None)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def update_dependencies_hash_digests_for_log(
|
|
151
|
+
change_log, # this is a historical DraftChangeLog or PublishLog
|
|
152
|
+
apps,
|
|
153
|
+
backfill=True,
|
|
154
|
+
) -> None:
|
|
155
|
+
"""
|
|
156
|
+
Update dependencies_hash_digest for Drafts or Published in a change log.
|
|
157
|
+
|
|
158
|
+
This is copied from the publishing API to make sure we don't accidentally
|
|
159
|
+
break it with future changes as the data model evolves. It has also been
|
|
160
|
+
modified to use historical models, rather than having references to the new
|
|
161
|
+
ones that have been moved to the centralized authoring app. It has also been
|
|
162
|
+
modified to assume that it's being used as a backfill (the original makes it
|
|
163
|
+
optional).
|
|
164
|
+
|
|
165
|
+
All the data for Draft/Published, DraftChangeLog/PublishLog, and
|
|
166
|
+
DraftChangeLogRecord/PublishLogRecord have been set at this point *except*
|
|
167
|
+
the dependencies_hash_digest of DraftChangeLogRecord/PublishLogRecord. Those
|
|
168
|
+
log records are newly created at this point, so dependencies_hash_digest are
|
|
169
|
+
set to their default values.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
change_log: A DraftChangeLog or PublishLog that already has all
|
|
173
|
+
side-effects added to it. The Draft and Published models should
|
|
174
|
+
already be updated to point to the post-change versions.
|
|
175
|
+
backfill: If this is true, we will not trust the hash values stored on
|
|
176
|
+
log records outside of our log, i.e. things that we would normally
|
|
177
|
+
expect to be pre-calculated. This will be important for the initial
|
|
178
|
+
data migration.
|
|
179
|
+
"""
|
|
180
|
+
DraftChangeLog = apps.get_model("oel_publishing", "DraftChangeLog")
|
|
181
|
+
DraftChangeLogRecord = apps.get_model("oel_publishing", "DraftChangeLogRecord")
|
|
182
|
+
PublishLog = apps.get_model("oel_publishing", "PublishLog")
|
|
183
|
+
PublishLogRecord = apps.get_model("oel_publishing", "PublishLogRecord")
|
|
184
|
+
PublishableEntity = apps.get_model("oel_publishing", "PublishableEntity")
|
|
185
|
+
|
|
186
|
+
if isinstance(change_log, DraftChangeLog):
|
|
187
|
+
branch = "draft"
|
|
188
|
+
log_record_relation = "draft_log_record"
|
|
189
|
+
record_cls = DraftChangeLogRecord
|
|
190
|
+
elif isinstance(change_log, PublishLog):
|
|
191
|
+
branch = "published"
|
|
192
|
+
log_record_relation = "publish_log_record"
|
|
193
|
+
record_cls = PublishLogRecord # type: ignore[assignment]
|
|
194
|
+
else:
|
|
195
|
+
raise TypeError(
|
|
196
|
+
f"expected DraftChangeLog or PublishLog, not {type(change_log)}"
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
dependencies_prefetch = Prefetch(
|
|
200
|
+
"new_version__dependencies",
|
|
201
|
+
queryset=PublishableEntity.objects
|
|
202
|
+
.select_related(
|
|
203
|
+
f"{branch}__version",
|
|
204
|
+
f"{branch}__{log_record_relation}",
|
|
205
|
+
)
|
|
206
|
+
.order_by(f"{branch}__version__uuid")
|
|
207
|
+
)
|
|
208
|
+
changed_records = (
|
|
209
|
+
change_log.records
|
|
210
|
+
.select_related("new_version", f"entity__{branch}")
|
|
211
|
+
.prefetch_related(dependencies_prefetch)
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
record_ids_to_hash_digests: dict[int, str | None] = {}
|
|
215
|
+
record_ids_to_live_deps: dict[int, list] = {}
|
|
216
|
+
records_that_need_hashes = []
|
|
217
|
+
|
|
218
|
+
for record in changed_records:
|
|
219
|
+
# This is a soft-deletion, so the dependency hash is default/blank. We
|
|
220
|
+
# set this value in our record_ids_to_hash_digests cache, but we don't
|
|
221
|
+
# need to write it to the database because it's just the default value.
|
|
222
|
+
if record.new_version is None:
|
|
223
|
+
record_ids_to_hash_digests[record.id] = ''
|
|
224
|
+
continue
|
|
225
|
+
|
|
226
|
+
# Now check to see if the new version has "live" dependencies, i.e.
|
|
227
|
+
# dependencies that have not been deleted.
|
|
228
|
+
deps = list(
|
|
229
|
+
entity for entity in record.new_version.dependencies.all()
|
|
230
|
+
if hasattr(entity, branch) and getattr(entity, branch).version
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
# If there are no live dependencies, this log record also gets the
|
|
234
|
+
# default/blank value.
|
|
235
|
+
if not deps:
|
|
236
|
+
record_ids_to_hash_digests[record.id] = ''
|
|
237
|
+
continue
|
|
238
|
+
|
|
239
|
+
# If we've gotten this far, it means that this record has dependencies
|
|
240
|
+
# and does need to get a hash computed for it.
|
|
241
|
+
records_that_need_hashes.append(record)
|
|
242
|
+
record_ids_to_live_deps[record.id] = deps
|
|
243
|
+
|
|
244
|
+
if backfill:
|
|
245
|
+
untrusted_record_id_set = None
|
|
246
|
+
else:
|
|
247
|
+
untrusted_record_id_set = set(rec.id for rec in records_that_need_hashes)
|
|
248
|
+
|
|
249
|
+
for record in records_that_need_hashes:
|
|
250
|
+
record.dependencies_hash_digest = hash_for_log_record(
|
|
251
|
+
apps,
|
|
252
|
+
record,
|
|
253
|
+
record_ids_to_hash_digests,
|
|
254
|
+
record_ids_to_live_deps,
|
|
255
|
+
untrusted_record_id_set,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
_bulk_update_hashes(record_cls, records_that_need_hashes)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _bulk_update_hashes(model_cls, records):
|
|
262
|
+
"""
|
|
263
|
+
bulk_update using the model class (PublishLogRecord or DraftChangeLogRecord)
|
|
264
|
+
|
|
265
|
+
This is copied from the publishing API to make sure we don't accidentally
|
|
266
|
+
break it with future changes as the data model evolves.
|
|
267
|
+
"""
|
|
268
|
+
model_cls.objects.bulk_update(records, ['dependencies_hash_digest'])
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def hash_for_log_record(
|
|
272
|
+
apps,
|
|
273
|
+
record, # historical DraftChangeLogRecord | PublishLogRecord,
|
|
274
|
+
record_ids_to_hash_digests: dict,
|
|
275
|
+
record_ids_to_live_deps: dict,
|
|
276
|
+
untrusted_record_id_set: set | None,
|
|
277
|
+
) -> str:
|
|
278
|
+
"""
|
|
279
|
+
The hash digest for a given change log record.
|
|
280
|
+
|
|
281
|
+
This is copied from the publishing API to make sure we don't accidentally
|
|
282
|
+
break it with future changes as the data model evolves. It has also been
|
|
283
|
+
modified to use historical models, rather than having references to the new
|
|
284
|
+
ones that have been moved to the centralized authoring app.
|
|
285
|
+
|
|
286
|
+
Note that this code is a little convoluted because we're working hard to
|
|
287
|
+
minimize the number of database requests. All the data we really need could
|
|
288
|
+
be derived from querying various relations off the record that's passed in
|
|
289
|
+
as the first parameter, but at a far higher cost.
|
|
290
|
+
|
|
291
|
+
The hash calculated here will be used for the dependencies_hash_digest
|
|
292
|
+
attribute of DraftChangeLogRecord and PublishLogRecord. The hash is intended
|
|
293
|
+
to calculate the currently "live" (current draft or published) state of all
|
|
294
|
+
dependencies (and transitive dependencies) of the PublishableEntityVersion
|
|
295
|
+
pointed to by DraftChangeLogRecord.new_version/PublishLogRecord.new_version.
|
|
296
|
+
|
|
297
|
+
The common case we have at the moment is when a container type like a Unit
|
|
298
|
+
has unpinned child Components as dependencies. In the data model, those
|
|
299
|
+
dependency relationships are represented by the "dependencies" M:M relation
|
|
300
|
+
on PublishableEntityVersion. Since the Unit version's references to its
|
|
301
|
+
child Components are unpinned, the draft Unit is always pointing to the
|
|
302
|
+
latest draft versions of those Components and the published Unit is always
|
|
303
|
+
pointing to the latest published versions of those Components.
|
|
304
|
+
|
|
305
|
+
This means that the total draft or published state of any PublishableEntity
|
|
306
|
+
depends on the combination of:
|
|
307
|
+
|
|
308
|
+
1. The definition of the current draft/published version of that entity.
|
|
309
|
+
Example: Version 1 of a Unit would define that it had children [C1, C2].
|
|
310
|
+
Version 2 of the same Unit might have children [C1, C2, C3].
|
|
311
|
+
2. The current draft/published versions of all dependencies. Example: What
|
|
312
|
+
are the current draft and published versions of C1, C2, and C3.
|
|
313
|
+
|
|
314
|
+
This is why it makes sense to capture in a log record, since
|
|
315
|
+
PublishLogRecords or DraftChangeLogRecords are created whenever one of the
|
|
316
|
+
above two things changes.
|
|
317
|
+
|
|
318
|
+
Here are the possible scenarios, including edge cases:
|
|
319
|
+
|
|
320
|
+
EntityVersions with no dependencies
|
|
321
|
+
If record.new_version has no dependencies, dependencies_hash_digest is
|
|
322
|
+
set to the default value of ''. This will be the most common case.
|
|
323
|
+
|
|
324
|
+
EntityVersions with dependencies
|
|
325
|
+
If an EntityVersion has dependencies, then its draft/published state
|
|
326
|
+
hash is based on the concatenation of, for each non-deleted dependency:
|
|
327
|
+
(i) the dependency's draft/published EntityVersion primary key, and
|
|
328
|
+
(ii) the dependency's own draft/published state hash, recursively re-
|
|
329
|
+
calculated if necessary.
|
|
330
|
+
|
|
331
|
+
Soft-deletions
|
|
332
|
+
If the record.new_version is None, that means we've just soft-deleted
|
|
333
|
+
something (or published the soft-delete of something). We adopt the
|
|
334
|
+
convention that if something is soft-deleted, its dependencies_hash_digest
|
|
335
|
+
is reset to the default value of ''. This is not strictly necessary for
|
|
336
|
+
the recursive hash calculation, but deleted entities will not have their
|
|
337
|
+
hash updated even as their non-deleted dependencies are updated underneath
|
|
338
|
+
them, so we set to '' to avoid falsely implying that the deleted entity's
|
|
339
|
+
dep hash is up to date.
|
|
340
|
+
|
|
341
|
+
EntityVersions with soft-deleted dependencies
|
|
342
|
+
A soft-deleted dependency isn't counted (it's as if the dependency were
|
|
343
|
+
removed). If all of an EntityVersion's dependencies are soft-deleted,
|
|
344
|
+
then it will go back to having to having the default blank string for its
|
|
345
|
+
dependencies_hash_digest.
|
|
346
|
+
"""
|
|
347
|
+
DraftChangeLogRecord = apps.get_model("oel_publishing", "DraftChangeLogRecord")
|
|
348
|
+
PublishLogRecord = apps.get_model("oel_publishing", "PublishLogRecord")
|
|
349
|
+
|
|
350
|
+
# Case #1: We've already computed this, or it was bootstrapped for us in the
|
|
351
|
+
# cache because the record is a deletion or doesn't have dependencies.
|
|
352
|
+
if record.id in record_ids_to_hash_digests:
|
|
353
|
+
return record_ids_to_hash_digests[record.id]
|
|
354
|
+
|
|
355
|
+
# Case #2: The log_record is a dependency of something that was affected by
|
|
356
|
+
# a change, but the dependency itself did not change in any way (neither
|
|
357
|
+
# directly, nor as a side-effect).
|
|
358
|
+
#
|
|
359
|
+
# Example: A Unit has two Components. One of the Components changed, forcing
|
|
360
|
+
# us to recalculate the dependencies_hash_digest for that Unit. Doing that
|
|
361
|
+
# recalculation requires us to fetch the dependencies_hash_digest of the
|
|
362
|
+
# unchanged child Component as well.
|
|
363
|
+
#
|
|
364
|
+
# If we aren't given an explicit untrusted_record_id_set, it means we can't
|
|
365
|
+
# trust anything. This would happen when we're bootstrapping things with an
|
|
366
|
+
# initial data migration.
|
|
367
|
+
if (untrusted_record_id_set is not None) and (record.id not in untrusted_record_id_set):
|
|
368
|
+
return record.dependencies_hash_digest
|
|
369
|
+
|
|
370
|
+
# Normal recursive case starts here:
|
|
371
|
+
if isinstance(record, DraftChangeLogRecord):
|
|
372
|
+
branch = "draft"
|
|
373
|
+
elif isinstance(record, PublishLogRecord):
|
|
374
|
+
branch = "published"
|
|
375
|
+
else:
|
|
376
|
+
raise TypeError(
|
|
377
|
+
f"expected DraftChangeLogRecord or PublishLogRecord, not {type(record)}"
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
# This is extra work that only happens in case of a backfill, where we might
|
|
381
|
+
# need to compute dependency hashes for things outside of our log (because
|
|
382
|
+
# we don't trust them).
|
|
383
|
+
if record.id not in record_ids_to_live_deps:
|
|
384
|
+
if record.new_version is None:
|
|
385
|
+
record_ids_to_hash_digests[record.id] = ''
|
|
386
|
+
return ''
|
|
387
|
+
deps = list(
|
|
388
|
+
entity for entity in record.new_version.dependencies.all()
|
|
389
|
+
if hasattr(entity, branch) and getattr(entity, branch).version
|
|
390
|
+
)
|
|
391
|
+
# If there are no live dependencies, this log record also gets the
|
|
392
|
+
# default/blank value.
|
|
393
|
+
if not deps:
|
|
394
|
+
record_ids_to_hash_digests[record.id] = ''
|
|
395
|
+
return ''
|
|
396
|
+
|
|
397
|
+
record_ids_to_live_deps[record.id] = deps
|
|
398
|
+
# End special handling for backfill.
|
|
399
|
+
|
|
400
|
+
# Begin normal
|
|
401
|
+
dependencies = sorted(
|
|
402
|
+
record_ids_to_live_deps[record.id],
|
|
403
|
+
key=lambda entity: getattr(entity, branch).log_record.new_version_id,
|
|
404
|
+
)
|
|
405
|
+
dep_state_entries = []
|
|
406
|
+
for dep_entity in dependencies:
|
|
407
|
+
new_version_id = getattr(dep_entity, branch).log_record.new_version_id
|
|
408
|
+
hash_digest = hash_for_log_record(
|
|
409
|
+
apps,
|
|
410
|
+
getattr(dep_entity, branch).log_record,
|
|
411
|
+
record_ids_to_hash_digests,
|
|
412
|
+
record_ids_to_live_deps,
|
|
413
|
+
untrusted_record_id_set,
|
|
414
|
+
)
|
|
415
|
+
dep_state_entries.append(f"{new_version_id}:{hash_digest}")
|
|
416
|
+
summary_text = "\n".join(dep_state_entries)
|
|
417
|
+
|
|
418
|
+
digest = create_hash_digest(summary_text.encode(), num_bytes=4)
|
|
419
|
+
record_ids_to_hash_digests[record.id] = digest
|
|
420
|
+
|
|
421
|
+
return digest
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
class Migration(migrations.Migration):
|
|
425
|
+
|
|
426
|
+
dependencies = [
|
|
427
|
+
('oel_publishing', '0009_dependencies_and_hashing'),
|
|
428
|
+
]
|
|
429
|
+
|
|
430
|
+
operations = [
|
|
431
|
+
migrations.RunPython(create_backfill, reverse_code=remove_backfill)
|
|
432
|
+
]
|