Skip to content

Commit 0c72e8e

Browse files
authored
feat: add can_stand_alone flag to publishable entity [FC-0083] (#289)
feat: add can_stand_alone flag to publishable entity It allows us to track components that were created independently and components that were created under a container like unit or subsection.
1 parent bf74b09 commit 0c72e8e

8 files changed

Lines changed: 84 additions & 5 deletions

File tree

openedx_learning/apps/authoring/components/api.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,14 +83,20 @@ def create_component(
8383
local_key: str,
8484
created: datetime,
8585
created_by: int | None,
86+
*,
87+
can_stand_alone: bool = True,
8688
) -> Component:
8789
"""
8890
Create a new Component (an entity like a Problem or Video)
8991
"""
9092
key = f"{component_type.namespace}:{component_type.name}:{local_key}"
9193
with atomic():
9294
publishable_entity = publishing_api.create_publishable_entity(
93-
learning_package_id, key, created, created_by
95+
learning_package_id,
96+
key,
97+
created,
98+
created_by,
99+
can_stand_alone=can_stand_alone
94100
)
95101
component = Component.objects.create(
96102
publishable_entity=publishable_entity,
@@ -239,13 +245,20 @@ def create_component_and_version( # pylint: disable=too-many-positional-argumen
239245
title: str,
240246
created: datetime,
241247
created_by: int | None = None,
248+
*,
249+
can_stand_alone: bool = True,
242250
) -> tuple[Component, ComponentVersion]:
243251
"""
244252
Create a Component and associated ComponentVersion atomically
245253
"""
246254
with atomic():
247255
component = create_component(
248-
learning_package_id, component_type, local_key, created, created_by
256+
learning_package_id,
257+
component_type,
258+
local_key,
259+
created,
260+
created_by,
261+
can_stand_alone=can_stand_alone,
249262
)
250263
component_version = create_component_version(
251264
component.pk,

openedx_learning/apps/authoring/publishing/admin.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ class PublishableEntityAdmin(ReadOnlyModelAdmin):
8585
"learning_package",
8686
"created",
8787
"created_by",
88+
"can_stand_alone",
8889
]
8990
list_filter = ["learning_package"]
9091
search_fields = ["key", "uuid"]
@@ -98,6 +99,7 @@ class PublishableEntityAdmin(ReadOnlyModelAdmin):
9899
"created",
99100
"created_by",
100101
"see_also",
102+
"can_stand_alone",
101103
]
102104
readonly_fields = [
103105
"key",
@@ -108,6 +110,7 @@ class PublishableEntityAdmin(ReadOnlyModelAdmin):
108110
"created",
109111
"created_by",
110112
"see_also",
113+
"can_stand_alone",
111114
]
112115

113116
def get_queryset(self, request):

openedx_learning/apps/authoring/publishing/api.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,8 @@ def create_publishable_entity(
173173
created: datetime,
174174
# User ID who created this
175175
created_by: int | None,
176+
*,
177+
can_stand_alone: bool = True,
176178
) -> PublishableEntity:
177179
"""
178180
Create a PublishableEntity.
@@ -185,6 +187,7 @@ def create_publishable_entity(
185187
key=key,
186188
created=created,
187189
created_by_id=created_by,
190+
can_stand_alone=can_stand_alone,
188191
)
189192

190193

@@ -583,6 +586,8 @@ def create_container(
583586
key: str,
584587
created: datetime,
585588
created_by: int | None,
589+
*,
590+
can_stand_alone: bool = True,
586591
# The types on the following line are correct, but mypy will complain - https://github.com/python/mypy/issues/3737
587592
container_cls: type[ContainerModel] = Container, # type: ignore[assignment]
588593
) -> ContainerModel:
@@ -595,6 +600,7 @@ def create_container(
595600
key: The key of the container.
596601
created: The date and time the container was created.
597602
created_by: The ID of the user who created the container
603+
can_stand_alone: Set to False when created as part of containers
598604
container_cls: The subclass of Container to use, if applicable
599605
600606
Returns:
@@ -603,7 +609,11 @@ def create_container(
603609
assert issubclass(container_cls, Container)
604610
with atomic():
605611
publishable_entity = create_publishable_entity(
606-
learning_package_id, key, created, created_by
612+
learning_package_id,
613+
key,
614+
created,
615+
created_by,
616+
can_stand_alone=can_stand_alone,
607617
)
608618
container = container_cls.objects.create(
609619
publishable_entity=publishable_entity,
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Generated by Django 4.2.19 on 2025-03-17 11:07
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('oel_publishing', '0003_containers'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='publishableentity',
15+
name='can_stand_alone',
16+
field=models.BooleanField(
17+
default=True,
18+
help_text="Set to True when created independently, False when created as part of a container.",
19+
),
20+
),
21+
]

openedx_learning/apps/authoring/publishing/models/publishable_entity.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from django.core.exceptions import ImproperlyConfigured
1010
from django.core.validators import MinValueValidator
1111
from django.db import models
12+
from django.utils.translation import gettext as _
1213

1314
from openedx_learning.lib.fields import (
1415
case_insensitive_char_field,
@@ -126,6 +127,10 @@ class PublishableEntity(models.Model):
126127
null=True,
127128
blank=True,
128129
)
130+
can_stand_alone = models.BooleanField(
131+
default=True,
132+
help_text=_("Set to True when created independently, False when created as part of a container."),
133+
)
129134

130135
class Meta:
131136
constraints = [

openedx_learning/apps/authoring/units/api.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,12 @@
3030

3131

3232
def create_unit(
33-
learning_package_id: int, key: str, created: datetime, created_by: int | None
33+
learning_package_id: int,
34+
key: str,
35+
created: datetime,
36+
created_by: int | None,
37+
*,
38+
can_stand_alone: bool = True,
3439
) -> Unit:
3540
"""
3641
[ 🛑 UNSTABLE ] Create a new unit.
@@ -40,12 +45,14 @@ def create_unit(
4045
key: The key.
4146
created: The creation date.
4247
created_by: The user who created the unit.
48+
can_stand_alone: Set to False when created as part of containers
4349
"""
4450
return publishing_api.create_container(
4551
learning_package_id,
4652
key,
4753
created,
4854
created_by,
55+
can_stand_alone=can_stand_alone,
4956
container_cls=Unit,
5057
)
5158

@@ -156,6 +163,7 @@ def create_unit_and_version(
156163
components: list[Component | ComponentVersion] | None = None,
157164
created: datetime,
158165
created_by: int | None = None,
166+
can_stand_alone: bool = True,
159167
) -> tuple[Unit, UnitVersion]:
160168
"""
161169
[ 🛑 UNSTABLE ] Create a new unit and its version.
@@ -165,10 +173,17 @@ def create_unit_and_version(
165173
key: The key.
166174
created: The creation date.
167175
created_by: The user who created the unit.
176+
can_stand_alone: Set to False when created as part of containers
168177
"""
169178
publishable_entities_pks, entity_version_pks = _pub_entities_for_components(components)
170179
with atomic():
171-
unit = create_unit(learning_package_id, key, created, created_by)
180+
unit = create_unit(
181+
learning_package_id,
182+
key,
183+
created,
184+
created_by,
185+
can_stand_alone=can_stand_alone,
186+
)
172187
unit_version = create_unit_version(
173188
unit,
174189
1,

tests/openedx_learning/apps/authoring/components/test_api.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,7 @@ def setUpTestData(cls) -> None:
322322
local_key='my_component',
323323
created=cls.now,
324324
created_by=None,
325+
can_stand_alone=False,
325326
)
326327

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

337+
def test_stand_alone_flag(self):
338+
"""Check if can_stand_alone flag is set"""
339+
component = components_api.get_component_by_key(
340+
self.learning_package.id,
341+
namespace='xblock.v1',
342+
type_name='html',
343+
local_key='my_component',
344+
)
345+
assert not component.publishable_entity.can_stand_alone
346+
336347
def test_get_by_key(self):
337348
assert self.html == components_api.get_component_by_key(
338349
self.learning_package.id,

tests/openedx_learning/apps/authoring/units/test_api.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ def test_create_empty_unit_and_version(self):
222222
assert unit.versioning.has_unpublished_changes
223223
assert unit.versioning.draft == unit_version
224224
assert unit.versioning.published is None
225+
assert unit.publishable_entity.can_stand_alone
225226

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

0 commit comments

Comments
 (0)