Skip to content

Commit 5c635f8

Browse files
authored
Merge branch 'main' into kdmccormick/collection-code
2 parents 03adb07 + 02908db commit 5c635f8

63 files changed

Lines changed: 5761 additions & 5700 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.importlinter

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ layers=
4848
# Problems, Videos, and blocks of HTML text. This is also the type we would
4949
# associate with a single "leaf" XBlock–one that is not a container type and
5050
# has no child elements.
51-
openedx_content.applets.components
51+
# The "containers" app is built on top of publishing, and is a peer to
52+
# "components" but they do not depend on each other.
53+
openedx_content.applets.components | openedx_content.applets.containers
5254

5355
# The "media" applet stores the simplest pieces of binary and text data,
5456
# without versioning information. These belong to a single Learning Package.

docs/decisions/0023-competency-criteria-model.rst

Lines changed: 314 additions & 0 deletions
Large diffs are not rendered by default.
124 KB
Loading

src/openedx_content/admin.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
from .applets.backup_restore.admin import *
77
from .applets.collections.admin import *
88
from .applets.components.admin import *
9+
from .applets.containers.admin import *
910
from .applets.media.admin import *
1011
from .applets.publishing.admin import *
11-
from .applets.sections.admin import *
12-
from .applets.subsections.admin import *
13-
from .applets.units.admin import *

src/openedx_content/api.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from .applets.backup_restore.api import *
1414
from .applets.collections.api import *
1515
from .applets.components.api import *
16+
from .applets.containers.api import *
1617
from .applets.media.api import *
1718
from .applets.publishing.api import *
1819
from .applets.sections.api import *

src/openedx_content/applets/backup_restore/toml.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from django.contrib.auth.models import User as UserType # pylint: disable=imported-auth-user
1010

1111
from ..collections.models import Collection
12-
from ..publishing import api as publishing_api
12+
from ..containers import api as containers_api
1313
from ..publishing.models import PublishableEntity, PublishableEntityVersion
1414
from ..publishing.models.learning_package import LearningPackage
1515

@@ -191,7 +191,7 @@ def toml_publishable_entity_version(version: PublishableEntityVersion) -> tomlki
191191
if hasattr(version, 'containerversion'):
192192
# If the version has a container version, add its children
193193
container_table = tomlkit.table()
194-
children = publishing_api.get_container_children_entities_keys(version.containerversion)
194+
children = containers_api.get_container_children_entities_keys(version.containerversion)
195195
container_table.add("children", children)
196196
version_table.add("container", container_table)
197197
return version_table

src/openedx_content/applets/backup_restore/zipper.py

Lines changed: 76 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,12 @@
3131

3232
from ..collections import api as collections_api
3333
from ..components import api as components_api
34+
from ..containers import api as containers_api
3435
from ..media import api as media_api
3536
from ..publishing import api as publishing_api
36-
from ..sections import api as sections_api
37-
from ..subsections import api as subsections_api
38-
from ..units import api as units_api
37+
from ..sections.models import Section
38+
from ..subsections.models import Subsection
39+
from ..units.models import Unit
3940
from .serializers import (
4041
CollectionSerializer,
4142
ComponentSerializer,
@@ -804,70 +805,70 @@ def _save_components(self, learning_package, components, component_static_files)
804805
**valid_published
805806
)
806807

807-
def _save_units(self, learning_package, containers):
808-
"""Save units and published unit versions."""
809-
for valid_unit in containers.get("unit", []):
810-
entity_key = valid_unit.get("key")
811-
unit = units_api.create_unit(learning_package.id, created_by=self.user_id, **valid_unit)
812-
self.units_map_by_key[entity_key] = unit
808+
def _save_container(
809+
self,
810+
learning_package,
811+
containers,
812+
*,
813+
container_cls: containers_api.ContainerSubclass,
814+
container_map: dict,
815+
children_map: dict,
816+
):
817+
"""Internal logic for _save_units, _save_subsections, and _save_sections"""
818+
type_code = container_cls.type_code # e.g. "unit"
819+
for data in containers.get(type_code, []):
820+
entity_key = data.get("key")
821+
container = containers_api.create_container(
822+
learning_package.id,
823+
**data, # should this be allowed to override any of the following fields?
824+
created_by=self.user_id,
825+
container_cls=container_cls,
826+
)
827+
container_map[entity_key] = container # e.g. `self.units_map_by_key[entity_key] = unit`
813828

814-
for valid_published in containers.get("unit_published", []):
829+
for valid_published in containers.get(f"{type_code}_published", []):
815830
entity_key = valid_published.pop("entity_key")
816-
children = self._resolve_children(valid_published, self.components_map_by_key)
831+
children = self._resolve_children(valid_published, children_map)
817832
self.all_published_entities_versions.add(
818833
(entity_key, valid_published.get('version_num'))
819834
) # Track published version
820-
units_api.create_next_unit_version(
821-
self.units_map_by_key[entity_key],
835+
containers_api.create_next_container_version(
836+
container_map[entity_key],
837+
**valid_published, # should this be allowed to override any of the following fields?
822838
force_version_num=valid_published.pop("version_num", None),
823-
components=children,
839+
entities=children,
824840
created_by=self.user_id,
825-
**valid_published
826841
)
827842

843+
def _save_units(self, learning_package, containers):
844+
"""Save units and published unit versions."""
845+
self._save_container(
846+
learning_package,
847+
containers,
848+
container_cls=Unit,
849+
container_map=self.units_map_by_key,
850+
children_map=self.components_map_by_key,
851+
)
852+
828853
def _save_subsections(self, learning_package, containers):
829854
"""Save subsections and published subsection versions."""
830-
for valid_subsection in containers.get("subsection", []):
831-
entity_key = valid_subsection.get("key")
832-
subsection = subsections_api.create_subsection(
833-
learning_package.id, created_by=self.user_id, **valid_subsection
834-
)
835-
self.subsections_map_by_key[entity_key] = subsection
836-
837-
for valid_published in containers.get("subsection_published", []):
838-
entity_key = valid_published.pop("entity_key")
839-
children = self._resolve_children(valid_published, self.units_map_by_key)
840-
self.all_published_entities_versions.add(
841-
(entity_key, valid_published.get('version_num'))
842-
) # Track published version
843-
subsections_api.create_next_subsection_version(
844-
self.subsections_map_by_key[entity_key],
845-
units=children,
846-
force_version_num=valid_published.pop("version_num", None),
847-
created_by=self.user_id,
848-
**valid_published
849-
)
855+
self._save_container(
856+
learning_package,
857+
containers,
858+
container_cls=Subsection,
859+
container_map=self.subsections_map_by_key,
860+
children_map=self.units_map_by_key,
861+
)
850862

851863
def _save_sections(self, learning_package, containers):
852864
"""Save sections and published section versions."""
853-
for valid_section in containers.get("section", []):
854-
entity_key = valid_section.get("key")
855-
section = sections_api.create_section(learning_package.id, created_by=self.user_id, **valid_section)
856-
self.sections_map_by_key[entity_key] = section
857-
858-
for valid_published in containers.get("section_published", []):
859-
entity_key = valid_published.pop("entity_key")
860-
children = self._resolve_children(valid_published, self.subsections_map_by_key)
861-
self.all_published_entities_versions.add(
862-
(entity_key, valid_published.get('version_num'))
863-
) # Track published version
864-
sections_api.create_next_section_version(
865-
self.sections_map_by_key[entity_key],
866-
subsections=children,
867-
force_version_num=valid_published.pop("version_num", None),
868-
created_by=self.user_id,
869-
**valid_published
870-
)
865+
self._save_container(
866+
learning_package,
867+
containers,
868+
container_cls=Section,
869+
container_map=self.sections_map_by_key,
870+
children_map=self.subsections_map_by_key,
871+
)
871872

872873
def _save_draft_versions(self, components, containers, component_static_files):
873874
"""Save draft versions for all entity types."""
@@ -888,47 +889,29 @@ def _save_draft_versions(self, components, containers, component_static_files):
888889
**valid_draft
889890
)
890891

891-
for valid_draft in containers.get("unit_drafts", []):
892-
entity_key = valid_draft.pop("entity_key")
893-
version_num = valid_draft["version_num"] # Should exist, validated earlier
894-
if self._is_version_already_exists(entity_key, version_num):
895-
continue
896-
children = self._resolve_children(valid_draft, self.components_map_by_key)
897-
units_api.create_next_unit_version(
898-
self.units_map_by_key[entity_key],
899-
components=children,
900-
force_version_num=valid_draft.pop("version_num", None),
901-
created_by=self.user_id,
902-
**valid_draft
903-
)
904-
905-
for valid_draft in containers.get("subsection_drafts", []):
906-
entity_key = valid_draft.pop("entity_key")
907-
version_num = valid_draft["version_num"] # Should exist, validated earlier
908-
if self._is_version_already_exists(entity_key, version_num):
909-
continue
910-
children = self._resolve_children(valid_draft, self.units_map_by_key)
911-
subsections_api.create_next_subsection_version(
912-
self.subsections_map_by_key[entity_key],
913-
units=children,
914-
force_version_num=valid_draft.pop("version_num", None),
915-
created_by=self.user_id,
916-
**valid_draft
917-
)
892+
def _process_draft_containers(
893+
container_cls: containers_api.ContainerSubclass,
894+
container_map: dict,
895+
children_map: dict,
896+
):
897+
for valid_draft in containers.get(f"{container_cls.type_code}_drafts", []):
898+
entity_key = valid_draft.pop("entity_key")
899+
version_num = valid_draft["version_num"] # Should exist, validated earlier
900+
if self._is_version_already_exists(entity_key, version_num):
901+
continue
902+
children = self._resolve_children(valid_draft, children_map)
903+
del valid_draft["version_num"]
904+
containers_api.create_next_container_version(
905+
container_map[entity_key],
906+
**valid_draft, # should this be allowed to override any of the following fields?
907+
entities=children,
908+
force_version_num=version_num,
909+
created_by=self.user_id,
910+
)
918911

919-
for valid_draft in containers.get("section_drafts", []):
920-
entity_key = valid_draft.pop("entity_key")
921-
version_num = valid_draft["version_num"] # Should exist, validated earlier
922-
if self._is_version_already_exists(entity_key, version_num):
923-
continue
924-
children = self._resolve_children(valid_draft, self.subsections_map_by_key)
925-
sections_api.create_next_section_version(
926-
self.sections_map_by_key[entity_key],
927-
subsections=children,
928-
force_version_num=valid_draft.pop("version_num", None),
929-
created_by=self.user_id,
930-
**valid_draft
931-
)
912+
_process_draft_containers(Unit, self.units_map_by_key, children_map=self.components_map_by_key)
913+
_process_draft_containers(Subsection, self.subsections_map_by_key, children_map=self.units_map_by_key)
914+
_process_draft_containers(Section, self.sections_map_by_key, children_map=self.subsections_map_by_key)
932915

933916
# --------------------------
934917
# Utilities

src/openedx_content/applets/collections/api.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"get_collection",
2525
"get_collections",
2626
"get_entity_collections",
27+
"get_collection_entities",
2728
"remove_from_collection",
2829
"restore_collection",
2930
"update_collection",
@@ -197,6 +198,18 @@ def get_entity_collections(learning_package_id: int, entity_key: str) -> QuerySe
197198
return entity.collections.filter(enabled=True).order_by("pk")
198199

199200

201+
def get_collection_entities(learning_package_id: int, collection_key: str) -> QuerySet[PublishableEntity]:
202+
"""
203+
Returns a QuerySet of PublishableEntities in a Collection.
204+
205+
This is the same as `collection.entities.all()`
206+
"""
207+
return PublishableEntity.objects.filter(
208+
learning_package_id=learning_package_id,
209+
collections__key=collection_key,
210+
).order_by("pk")
211+
212+
200213
def get_collections(learning_package_id: int, enabled: bool | None = True) -> QuerySet[Collection]:
201214
"""
202215
Get all collections for a given learning package

src/openedx_content/applets/components/models.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -64,16 +64,16 @@ class ComponentType(models.Model):
6464
# the UsageKey.
6565
name = case_sensitive_char_field(max_length=100, blank=True)
6666

67-
# TODO: this needs to go into a class Meta
68-
constraints = [
69-
models.UniqueConstraint(
70-
fields=[
71-
"namespace",
72-
"name",
73-
],
74-
name="oel_component_type_uniq_ns_n",
75-
),
76-
]
67+
class Meta:
68+
constraints = [
69+
models.UniqueConstraint(
70+
fields=[
71+
"namespace",
72+
"name",
73+
],
74+
name="oel_component_type_uniq_ns_n",
75+
),
76+
]
7777

7878
def __str__(self) -> str:
7979
return f"{self.namespace}:{self.name}"

src/openedx_content/applets/containers/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)