Skip to content
Merged
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
17 changes: 15 additions & 2 deletions openedx_learning/apps/authoring/components/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,14 +83,20 @@ def create_component(
local_key: str,
created: datetime,
created_by: int | None,
*,
can_stand_alone: bool = True,
) -> Component:
"""
Create a new Component (an entity like a Problem or Video)
"""
key = f"{component_type.namespace}:{component_type.name}:{local_key}"
with atomic():
publishable_entity = publishing_api.create_publishable_entity(
learning_package_id, key, created, created_by
learning_package_id,
key,
created,
created_by,
can_stand_alone=can_stand_alone
)
component = Component.objects.create(
publishable_entity=publishable_entity,
Expand Down Expand Up @@ -239,13 +245,20 @@ def create_component_and_version( # pylint: disable=too-many-positional-argumen
title: str,
created: datetime,
created_by: int | None = None,
*,
can_stand_alone: bool = True,
) -> tuple[Component, ComponentVersion]:
"""
Create a Component and associated ComponentVersion atomically
"""
with atomic():
component = create_component(
learning_package_id, component_type, local_key, created, created_by
learning_package_id,
component_type,
local_key,
created,
created_by,
can_stand_alone=can_stand_alone,
)
component_version = create_component_version(
component.pk,
Expand Down
3 changes: 3 additions & 0 deletions openedx_learning/apps/authoring/publishing/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ class PublishableEntityAdmin(ReadOnlyModelAdmin):
"learning_package",
"created",
"created_by",
"can_stand_alone",
]
list_filter = ["learning_package"]
search_fields = ["key", "uuid"]
Expand All @@ -98,6 +99,7 @@ class PublishableEntityAdmin(ReadOnlyModelAdmin):
"created",
"created_by",
"see_also",
"can_stand_alone",
]
readonly_fields = [
"key",
Expand All @@ -108,6 +110,7 @@ class PublishableEntityAdmin(ReadOnlyModelAdmin):
"created",
"created_by",
"see_also",
"can_stand_alone",
]

def get_queryset(self, request):
Expand Down
12 changes: 11 additions & 1 deletion openedx_learning/apps/authoring/publishing/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@ def create_publishable_entity(
created: datetime,
# User ID who created this
created_by: int | None,
*,
can_stand_alone: bool = True,
) -> PublishableEntity:
"""
Create a PublishableEntity.
Expand All @@ -185,6 +187,7 @@ def create_publishable_entity(
key=key,
created=created,
created_by_id=created_by,
can_stand_alone=can_stand_alone,
)


Expand Down Expand Up @@ -583,6 +586,8 @@ def create_container(
key: str,
created: datetime,
created_by: int | None,
*,
can_stand_alone: bool = True,
# The types on the following line are correct, but mypy will complain - https://github.com/python/mypy/issues/3737
container_cls: type[ContainerModel] = Container, # type: ignore[assignment]
) -> ContainerModel:
Expand All @@ -595,6 +600,7 @@ def create_container(
key: The key of the container.
created: The date and time the container was created.
created_by: The ID of the user who created the container
can_stand_alone: Set to False when created as part of containers
container_cls: The subclass of Container to use, if applicable

Returns:
Expand All @@ -603,7 +609,11 @@ def create_container(
assert issubclass(container_cls, Container)
with atomic():
publishable_entity = create_publishable_entity(
learning_package_id, key, created, created_by
learning_package_id,
key,
created,
created_by,
can_stand_alone=can_stand_alone,
)
container = container_cls.objects.create(
publishable_entity=publishable_entity,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Generated by Django 4.2.19 on 2025-03-17 11:07

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('oel_publishing', '0003_containers'),
]

operations = [
migrations.AddField(
model_name='publishableentity',
name='can_stand_alone',
field=models.BooleanField(
default=True,
help_text="Set to True when created independently, False when created as part of a container.",
),
),
]
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from django.core.exceptions import ImproperlyConfigured
from django.core.validators import MinValueValidator
from django.db import models
from django.utils.translation import gettext as _

from openedx_learning.lib.fields import (
case_insensitive_char_field,
Expand Down Expand Up @@ -126,6 +127,10 @@ class PublishableEntity(models.Model):
null=True,
blank=True,
)
can_stand_alone = models.BooleanField(
default=True,
help_text=_("Set to True when created independently, False when created as part of a container."),
)

class Meta:
constraints = [
Expand Down
19 changes: 17 additions & 2 deletions openedx_learning/apps/authoring/units/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,12 @@


def create_unit(
learning_package_id: int, key: str, created: datetime, created_by: int | None
learning_package_id: int,
key: str,
created: datetime,
created_by: int | None,
*,
can_stand_alone: bool = True,
) -> Unit:
"""
[ 🛑 UNSTABLE ] Create a new unit.
Expand All @@ -40,12 +45,14 @@ def create_unit(
key: The key.
created: The creation date.
created_by: The user who created the unit.
can_stand_alone: Set to False when created as part of containers
"""
return publishing_api.create_container(
learning_package_id,
key,
created,
created_by,
can_stand_alone=can_stand_alone,
container_cls=Unit,
)

Expand Down Expand Up @@ -156,6 +163,7 @@ def create_unit_and_version(
components: list[Component | ComponentVersion] | None = None,
created: datetime,
created_by: int | None = None,
can_stand_alone: bool = True,
) -> tuple[Unit, UnitVersion]:
"""
[ 🛑 UNSTABLE ] Create a new unit and its version.
Expand All @@ -165,10 +173,17 @@ def create_unit_and_version(
key: The key.
created: The creation date.
created_by: The user who created the unit.
can_stand_alone: Set to False when created as part of containers
"""
publishable_entities_pks, entity_version_pks = _pub_entities_for_components(components)
with atomic():
unit = create_unit(learning_package_id, key, created, created_by)
unit = create_unit(
learning_package_id,
key,
created,
created_by,
can_stand_alone=can_stand_alone,
)
unit_version = create_unit_version(
unit,
1,
Expand Down
11 changes: 11 additions & 0 deletions tests/openedx_learning/apps/authoring/components/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@ def setUpTestData(cls) -> None:
local_key='my_component',
created=cls.now,
created_by=None,
can_stand_alone=False,
)

def test_simple_get(self):
Expand All @@ -333,6 +334,16 @@ def test_publishing_entity_key_convention(self):
"""Our mapping convention is {namespace}:{component_type}:{local_key}"""
assert self.problem.key == "xblock.v1:problem:my_component"

def test_stand_alone_flag(self):
"""Check if can_stand_alone flag is set"""
component = components_api.get_component_by_key(
self.learning_package.id,
namespace='xblock.v1',
type_name='html',
local_key='my_component',
)
assert not component.publishable_entity.can_stand_alone

def test_get_by_key(self):
assert self.html == components_api.get_component_by_key(
self.learning_package.id,
Expand Down
1 change: 1 addition & 0 deletions tests/openedx_learning/apps/authoring/units/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ def test_create_empty_unit_and_version(self):
assert unit.versioning.has_unpublished_changes
assert unit.versioning.draft == unit_version
assert unit.versioning.published is None
assert unit.publishable_entity.can_stand_alone

def test_create_next_unit_version_with_two_unpinned_components(self):
"""Test creating a unit version with two unpinned components.
Expand Down