Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .importlinter
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ layers=
# 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_content.applets.components
# The "containers" app is built on top of publishing, and is a peer to
# "components" but they do not depend on each other.
openedx_content.applets.components | openedx_content.applets.containers
Comment on lines +51 to +53
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did not know about this syntax. cool!


# The "media" applet stores the simplest pieces of binary and text data,
# without versioning information. These belong to a single Learning Package.
Expand Down
4 changes: 1 addition & 3 deletions src/openedx_content/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
from .applets.backup_restore.admin import *
from .applets.collections.admin import *
from .applets.components.admin import *
from .applets.containers.admin import *
from .applets.media.admin import *
from .applets.publishing.admin import *
from .applets.sections.admin import *
from .applets.subsections.admin import *
from .applets.units.admin import *
1 change: 1 addition & 0 deletions src/openedx_content/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from .applets.backup_restore.api import *
from .applets.collections.api import *
from .applets.components.api import *
from .applets.containers.api import *
from .applets.media.api import *
from .applets.publishing.api import *
from .applets.sections.api import *
Expand Down
4 changes: 2 additions & 2 deletions src/openedx_content/applets/backup_restore/toml.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from django.contrib.auth.models import User as UserType # pylint: disable=imported-auth-user

from ..collections.models import Collection
from ..publishing import api as publishing_api
from ..containers import api as containers_api
from ..publishing.models import PublishableEntity, PublishableEntityVersion
from ..publishing.models.learning_package import LearningPackage

Expand Down Expand Up @@ -191,7 +191,7 @@ def toml_publishable_entity_version(version: PublishableEntityVersion) -> tomlki
if hasattr(version, 'containerversion'):
# If the version has a container version, add its children
container_table = tomlkit.table()
children = publishing_api.get_container_children_entities_keys(version.containerversion)
children = containers_api.get_container_children_entities_keys(version.containerversion)
container_table.add("children", children)
version_table.add("container", container_table)
Comment on lines 192 to 196
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed that this if branch of toml_publishable_entity_version is not covered by any tests. I think we need a lot more test coverage of the backup/restore format.

return version_table
Expand Down
169 changes: 76 additions & 93 deletions src/openedx_content/applets/backup_restore/zipper.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,12 @@

from ..collections import api as collections_api
from ..components import api as components_api
from ..containers import api as containers_api
from ..media import api as media_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
from ..sections.models import Section
from ..subsections.models import Subsection
from ..units.models import Unit
from .serializers import (
CollectionSerializer,
ComponentSerializer,
Expand Down Expand Up @@ -804,70 +805,70 @@ def _save_components(self, learning_package, components, component_static_files)
**valid_published
)

def _save_units(self, learning_package, containers):
"""Save units and published unit versions."""
for valid_unit in containers.get("unit", []):
entity_key = valid_unit.get("key")
unit = units_api.create_unit(learning_package.id, created_by=self.user_id, **valid_unit)
self.units_map_by_key[entity_key] = unit
def _save_container(
self,
learning_package,
containers,
*,
container_cls: containers_api.ContainerSubclass,
container_map: dict,
children_map: dict,
):
"""Internal logic for _save_units, _save_subsections, and _save_sections"""
type_code = container_cls.type_code # e.g. "unit"
for data in containers.get(type_code, []):
entity_key = data.get("key")
container = containers_api.create_container(
learning_package.id,
**data, # should this be allowed to override any of the following fields?
created_by=self.user_id,
container_cls=container_cls,
)
container_map[entity_key] = container # e.g. `self.units_map_by_key[entity_key] = unit`

for valid_published in containers.get("unit_published", []):
for valid_published in containers.get(f"{type_code}_published", []):
entity_key = valid_published.pop("entity_key")
children = self._resolve_children(valid_published, self.components_map_by_key)
children = self._resolve_children(valid_published, children_map)
self.all_published_entities_versions.add(
(entity_key, valid_published.get('version_num'))
) # Track published version
units_api.create_next_unit_version(
self.units_map_by_key[entity_key],
containers_api.create_next_container_version(
container_map[entity_key],
**valid_published, # should this be allowed to override any of the following fields?
force_version_num=valid_published.pop("version_num", None),
components=children,
entities=children,
created_by=self.user_id,
**valid_published
)

def _save_units(self, learning_package, containers):
"""Save units and published unit versions."""
self._save_container(
learning_package,
containers,
container_cls=Unit,
container_map=self.units_map_by_key,
children_map=self.components_map_by_key,
)

def _save_subsections(self, learning_package, containers):
"""Save subsections and published subsection versions."""
for valid_subsection in containers.get("subsection", []):
entity_key = valid_subsection.get("key")
subsection = subsections_api.create_subsection(
learning_package.id, created_by=self.user_id, **valid_subsection
)
self.subsections_map_by_key[entity_key] = subsection

for valid_published in containers.get("subsection_published", []):
entity_key = valid_published.pop("entity_key")
children = self._resolve_children(valid_published, self.units_map_by_key)
self.all_published_entities_versions.add(
(entity_key, valid_published.get('version_num'))
) # Track published version
subsections_api.create_next_subsection_version(
self.subsections_map_by_key[entity_key],
units=children,
force_version_num=valid_published.pop("version_num", None),
created_by=self.user_id,
**valid_published
)
self._save_container(
learning_package,
containers,
container_cls=Subsection,
container_map=self.subsections_map_by_key,
children_map=self.units_map_by_key,
)

def _save_sections(self, learning_package, containers):
"""Save sections and published section versions."""
for valid_section in containers.get("section", []):
entity_key = valid_section.get("key")
section = sections_api.create_section(learning_package.id, created_by=self.user_id, **valid_section)
self.sections_map_by_key[entity_key] = section

for valid_published in containers.get("section_published", []):
entity_key = valid_published.pop("entity_key")
children = self._resolve_children(valid_published, self.subsections_map_by_key)
self.all_published_entities_versions.add(
(entity_key, valid_published.get('version_num'))
) # Track published version
sections_api.create_next_section_version(
self.sections_map_by_key[entity_key],
subsections=children,
force_version_num=valid_published.pop("version_num", None),
created_by=self.user_id,
**valid_published
)
self._save_container(
learning_package,
containers,
container_cls=Section,
container_map=self.sections_map_by_key,
children_map=self.subsections_map_by_key,
)

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

for valid_draft in containers.get("unit_drafts", []):
entity_key = valid_draft.pop("entity_key")
version_num = valid_draft["version_num"] # Should exist, validated earlier
if self._is_version_already_exists(entity_key, version_num):
continue
children = self._resolve_children(valid_draft, self.components_map_by_key)
units_api.create_next_unit_version(
self.units_map_by_key[entity_key],
components=children,
force_version_num=valid_draft.pop("version_num", None),
created_by=self.user_id,
**valid_draft
)

for valid_draft in containers.get("subsection_drafts", []):
entity_key = valid_draft.pop("entity_key")
version_num = valid_draft["version_num"] # Should exist, validated earlier
if self._is_version_already_exists(entity_key, version_num):
continue
children = self._resolve_children(valid_draft, self.units_map_by_key)
subsections_api.create_next_subsection_version(
self.subsections_map_by_key[entity_key],
units=children,
force_version_num=valid_draft.pop("version_num", None),
created_by=self.user_id,
**valid_draft
)
def _process_draft_containers(
container_cls: containers_api.ContainerSubclass,
container_map: dict,
children_map: dict,
):
for valid_draft in containers.get(f"{container_cls.type_code}_drafts", []):
entity_key = valid_draft.pop("entity_key")
version_num = valid_draft["version_num"] # Should exist, validated earlier
if self._is_version_already_exists(entity_key, version_num):
continue
children = self._resolve_children(valid_draft, children_map)
del valid_draft["version_num"]
containers_api.create_next_container_version(
container_map[entity_key],
**valid_draft, # should this be allowed to override any of the following fields?
entities=children,
force_version_num=version_num,
created_by=self.user_id,
)

for valid_draft in containers.get("section_drafts", []):
entity_key = valid_draft.pop("entity_key")
version_num = valid_draft["version_num"] # Should exist, validated earlier
if self._is_version_already_exists(entity_key, version_num):
continue
children = self._resolve_children(valid_draft, self.subsections_map_by_key)
sections_api.create_next_section_version(
self.sections_map_by_key[entity_key],
subsections=children,
force_version_num=valid_draft.pop("version_num", None),
created_by=self.user_id,
**valid_draft
)
_process_draft_containers(Unit, self.units_map_by_key, children_map=self.components_map_by_key)
_process_draft_containers(Subsection, self.subsections_map_by_key, children_map=self.units_map_by_key)
_process_draft_containers(Section, self.sections_map_by_key, children_map=self.subsections_map_by_key)

# --------------------------
# Utilities
Expand Down
13 changes: 13 additions & 0 deletions src/openedx_content/applets/collections/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"get_collection",
"get_collections",
"get_entity_collections",
"get_collection_entities",
"remove_from_collection",
"restore_collection",
"update_collection",
Expand Down Expand Up @@ -195,6 +196,18 @@ def get_entity_collections(learning_package_id: int, entity_key: str) -> QuerySe
return entity.collections.filter(enabled=True).order_by("pk")


def get_collection_entities(learning_package_id: int, collection_key: str) -> QuerySet[PublishableEntity]:
"""
Returns a QuerySet of PublishableEntities in a Collection.

This is the same as `collection.entities.all()`
"""
return PublishableEntity.objects.filter(
learning_package_id=learning_package_id,
collections__key=collection_key,
).order_by("pk")


def get_collections(learning_package_id: int, enabled: bool | None = True) -> QuerySet[Collection]:
"""
Get all collections for a given learning package
Expand Down
20 changes: 10 additions & 10 deletions src/openedx_content/applets/components/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,16 +64,16 @@ 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=[
"namespace",
"name",
],
name="oel_component_type_uniq_ns_n",
),
]
class Meta:
constraints = [
models.UniqueConstraint(
fields=[
"namespace",
"name",
],
name="oel_component_type_uniq_ns_n",
),
]

def __str__(self) -> str:
return f"{self.namespace}:{self.name}"
Expand Down
Empty file.
Loading
Loading