Skip to content

Commit f5e419d

Browse files
kdmccormickclaude
andcommitted
feat!: Package and Entity keys are now opaque refs
The `{LearningPackage,PublishableEntity}.key` fields were always meant to be externally-supplied refs whose format does not matter. However, they were effectively being used as parseable psuedo-keys for identifying containers and components, with more assumptions to their structure being baked in over time, both within and outside openedx-core. This commit renames them to `{package,entity}_ref`, and removes all parsing of them from the API and from the internals (except for where required for backcompat with Ulmo ZIP backups). Instead, the pairings of (component_type,component_code) and (container_type,container_code) are used to identify components and containers; their respective entity_refs are derived from those *by conventional default* but also are customizable by callers. BREAKING CHANGE: Renamed key_field(...) -> ref_field(...) in openedx_django_lib. BREAKING CHANGE: Renamed PublishableEntity.key -> PublishableEntityKey.entity_ref BREAKING CHANGE: Renamed PublishableEntityMixin.key -> PublishableEntityMixin.entity_ref BREAKING CHANGE: In create_publishable_entity(...), renamed param key -> entity_ref BREAKING CHANGE: Renamed get_publishable_entity_by_key(...) -> get_publishable_entity_by_ref(...), and renamed param key -> entity_ref BREAKING CHANGE: In get_entity_collections(...), renamed param entity_key -> entity_ref BREAKING CHANGE: Removed function get_or_create_component_type_by_entity_key(). BREAKING CHANGE: Rename get_container_children_entities_keys(...) -> get_container_children_entity_refs(...) BREAKING CHANGE: Renamed get_container_by_key(...) -> get_container_by_ref(..) and renamed param key -> entity_ref. BREAKING CHANGE: Renamed get_container_children_entities_keys(...) -> get_container_children_entity_refs(...) BRAEKING CHANGE: Renamed LearningPackage.key -> LearningPackage.package_ref. BREAKING CHANGE: In create_learning_package(...), renamed param key -> package_ref BREAKING CHANGE: In update_learning_package(...), renamed param key -> package_ref BREAKING CHANGE: Renamed get_learning_package_by_key(...) -> get_learning_package_by_ref(...), and renamed param key -> package_ref BREAKING CHANGE: In learning_package_exists(...), renamed param key -> package_ref BREAKING CHANGE: In look_up_component_version_media(...): * Renamed param learning_package_key -> learning_package_ref * Renamed param component_key -> entity_ref BREAKING CHANGE: In add_assets_to_component management command: * Renamed argument learning_package_key -> learning_package_ref * Renamed argument component_key -> entity_ref BREAKING CHANGE In load_learning_package: * Renamed param key -> package_ref * In return dict, within sub-dict ["lp_restored_data"]: * Renamed ["key"] -> ["package_ref"] * Renamed ["archive_lp_key"] -> ["archive_package_ref"] * Renamed ["archive_org_key"] -> ["archive_org_code"] * Renamed ["archive_slug"] -> ["archive_package_code"] * If archive_package_ref cannot be parsed into "{_prefix}:{org_code}:{package_code}", then archvie_org_code and archive_package_code will both be None. Callers should handle this case. (Previously, a ValueError would be raised by backup-restore code.) Backup/Restore format changes (non-breaking): * In [learning_package], key field is renamed -> package_ref. * In [entity], key field is renamed -> entity_ref. * For compatibility with Ulmo instances, both key and {package,entity}_ref will be written in all new archives. * For compatibility with Ulmo archives, both key and {package,entity}_ref will be read, with priority given to the latter. Part of: #322 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent ae73faf commit f5e419d

28 files changed

Lines changed: 465 additions & 398 deletions

File tree

olx_importer/management/commands/load_components.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -61,33 +61,33 @@ def init_known_types(self):
6161

6262
def add_arguments(self, parser):
6363
parser.add_argument("course_data_path", type=pathlib.Path)
64-
parser.add_argument("learning_package_key", type=str)
64+
parser.add_argument("learning_package_ref", type=str)
6565

66-
def handle(self, course_data_path, learning_package_key, **options):
66+
def handle(self, course_data_path, learning_package_ref, **options):
6767
self.course_data_path = course_data_path
68-
self.learning_package_key = learning_package_key
69-
self.load_course_data(learning_package_key)
68+
self.learning_package_ref = learning_package_ref
69+
self.load_course_data(learning_package_ref)
7070

7171
def get_course_title(self):
7272
course_type_dir = self.course_data_path / "course"
7373
course_xml_file = next(course_type_dir.glob("*.xml"))
7474
course_root = ET.parse(course_xml_file).getroot()
7575
return course_root.attrib.get("display_name", "Unknown Course")
7676

77-
def load_course_data(self, learning_package_key):
77+
def load_course_data(self, learning_package_ref):
7878
print(f"Importing course from: {self.course_data_path}")
7979
now = datetime.now(timezone.utc)
8080
title = self.get_course_title()
8181

82-
if content_api.learning_package_exists(learning_package_key):
82+
if content_api.learning_package_exists(learning_package_ref):
8383
raise CommandError(
84-
f"{learning_package_key} already exists. "
84+
f"{learning_package_ref} already exists. "
8585
"This command currently only supports initial import."
8686
)
8787

8888
with transaction.atomic():
8989
self.learning_package = content_api.create_learning_package(
90-
learning_package_key, title, created=now,
90+
learning_package_ref, title, created=now,
9191
)
9292
for block_type in SUPPORTED_TYPES:
9393
self.import_block_type(block_type, now) #, publish_log_entry)

src/openedx_content/applets/backup_restore/api.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,28 @@
55

66
from django.contrib.auth.models import User as UserType # pylint: disable=imported-auth-user
77

8-
from ..publishing.api import get_learning_package_by_key
8+
from ..publishing.api import get_learning_package_by_ref
99
from .zipper import LearningPackageUnzipper, LearningPackageZipper
1010

1111

12-
def create_zip_file(lp_key: str, path: str, user: UserType | None = None, origin_server: str | None = None) -> None:
12+
def create_zip_file(
13+
package_ref: str, path: str, user: UserType | None = None, origin_server: str | None = None
14+
) -> None:
1315
"""
1416
Creates a dump zip file for the given learning package key at the given path.
1517
The zip file contains a TOML representation of the learning package and its contents.
1618
17-
Can throw a NotFoundError at get_learning_package_by_key
19+
Can throw a NotFoundError at get_learning_package_by_ref
1820
"""
19-
learning_package = get_learning_package_by_key(lp_key)
21+
learning_package = get_learning_package_by_ref(package_ref)
2022
LearningPackageZipper(learning_package, user, origin_server).create_zip(path)
2123

2224

23-
def load_learning_package(path: str, key: str | None = None, user: UserType | None = None) -> dict:
25+
def load_learning_package(path: str, package_ref: str | None = None, user: UserType | None = None) -> dict:
2426
"""
2527
Loads a learning package from a zip file at the given path.
2628
Restores the learning package and its contents to the database.
2729
Returns a dictionary with the status of the operation and any errors encountered.
2830
"""
2931
with zipfile.ZipFile(path, "r") as zipf:
30-
return LearningPackageUnzipper(zipf, key, user).load()
32+
return LearningPackageUnzipper(zipf, package_ref, user).load()

src/openedx_content/applets/backup_restore/serializers.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,14 @@ class LearningPackageSerializer(serializers.Serializer): # pylint: disable=abst
1414
Serializer for learning packages.
1515
1616
Note:
17-
The `key` field is serialized, but it is generally not trustworthy for restoration.
18-
During restore, a new key may be generated or overridden.
17+
The ref/key field is serialized but is generally not trustworthy for
18+
restoration. During restore, a new ref may be generated or overridden.
1919
"""
20+
2021
title = serializers.CharField(required=True)
21-
key = serializers.CharField(required=True)
22+
# The model field is now LearningPackage.package_ref, but the archive format
23+
# still uses "key". A future v2 format may align the name.
24+
key = serializers.CharField(required=True, source="package_ref")
2225
description = serializers.CharField(required=True, allow_blank=True)
2326
created = serializers.DateTimeField(required=True, default_timezone=timezone.utc)
2427

@@ -42,8 +45,11 @@ class EntitySerializer(serializers.Serializer): # pylint: disable=abstract-meth
4245
"""
4346
Serializer for publishable entities.
4447
"""
48+
4549
can_stand_alone = serializers.BooleanField(required=True)
46-
key = serializers.CharField(required=True)
50+
# The model field is now PublishableEntity.entity_ref, but the archive format
51+
# still uses "key". A future v2 format may align the name.
52+
key = serializers.CharField(required=True, source="entity_ref")
4753
created = serializers.DateTimeField(required=True, default_timezone=timezone.utc)
4854

4955

@@ -52,7 +58,7 @@ class EntityVersionSerializer(serializers.Serializer): # pylint: disable=abstra
5258
Serializer for publishable entity versions.
5359
"""
5460
title = serializers.CharField(required=True)
55-
entity_key = serializers.CharField(required=True)
61+
entity_ref = serializers.CharField(required=True)
5662
created = serializers.DateTimeField(required=True, default_timezone=timezone.utc)
5763
version_num = serializers.IntegerField(required=True)
5864

src/openedx_content/applets/backup_restore/toml.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ def toml_learning_package(
4343
# Learning package main info
4444
section = tomlkit.table()
4545
section.add("title", learning_package.title)
46-
section.add("key", learning_package.key)
46+
# The model field is now LearningPackage.package_ref, but the archive format
47+
# still uses "key". A future v2 format may align the name.
48+
section.add("key", learning_package.package_ref)
4749
section.add("description", learning_package.description)
4850
section.add("created", learning_package.created)
4951
section.add("updated", learning_package.updated)
@@ -89,8 +91,9 @@ def _get_toml_publishable_entity_table(
8991
"""
9092
entity_table = tomlkit.table()
9193
entity_table.add("can_stand_alone", entity.can_stand_alone)
92-
# Add key since the toml filename doesn't show the real key
93-
entity_table.add("key", entity.key)
94+
# The model field is now PublishableEntity.entity_ref, but the archive format
95+
# still uses "key". A future v2 format may align the name.
96+
entity_table.add("key", entity.entity_ref)
9497
entity_table.add("created", entity.created)
9598

9699
if not include_versions:
@@ -191,13 +194,13 @@ def toml_publishable_entity_version(version: PublishableEntityVersion) -> tomlki
191194
if hasattr(version, 'containerversion'):
192195
# If the version has a container version, add its children
193196
container_table = tomlkit.table()
194-
children = containers_api.get_container_children_entities_keys(version.containerversion)
197+
children = containers_api.get_container_children_entity_refs(version.containerversion)
195198
container_table.add("children", children)
196199
version_table.add("container", container_table)
197200
return version_table
198201

199202

200-
def toml_collection(collection: Collection, entity_keys: list[str]) -> str:
203+
def toml_collection(collection: Collection, entity_refs: list[str]) -> str:
201204
"""
202205
Create a TOML representation of a collection.
203206
@@ -215,7 +218,7 @@ def toml_collection(collection: Collection, entity_keys: list[str]) -> str:
215218
doc = tomlkit.document()
216219

217220
entities_array = tomlkit.array()
218-
entities_array.extend(entity_keys)
221+
entities_array.extend(entity_refs)
219222
entities_array.multiline(True)
220223

221224
collection_table = tomlkit.table()

0 commit comments

Comments
 (0)