openedx-learning 0.18.3__py2.py3-none-any.whl → 0.19.1__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openedx_learning/__init__.py +1 -1
- openedx_learning/api/authoring.py +1 -0
- openedx_learning/api/authoring_models.py +1 -1
- openedx_learning/apps/authoring/components/api.py +18 -5
- openedx_learning/apps/authoring/components/apps.py +1 -1
- openedx_learning/apps/authoring/components/models.py +9 -14
- openedx_learning/apps/authoring/contents/models.py +1 -1
- openedx_learning/apps/authoring/publishing/admin.py +3 -0
- openedx_learning/apps/authoring/publishing/api.py +508 -3
- openedx_learning/apps/authoring/publishing/apps.py +9 -0
- openedx_learning/apps/authoring/publishing/migrations/0003_containers.py +54 -0
- openedx_learning/apps/authoring/publishing/migrations/0004_publishableentity_can_stand_alone.py +21 -0
- openedx_learning/apps/authoring/publishing/models/__init__.py +27 -0
- openedx_learning/apps/authoring/publishing/models/container.py +70 -0
- openedx_learning/apps/authoring/publishing/models/draft_published.py +95 -0
- openedx_learning/apps/authoring/publishing/models/entity_list.py +69 -0
- openedx_learning/apps/authoring/publishing/models/learning_package.py +75 -0
- openedx_learning/apps/authoring/publishing/models/publish_log.py +106 -0
- openedx_learning/apps/authoring/publishing/{model_mixins.py → models/publishable_entity.py} +289 -41
- openedx_learning/apps/authoring/units/__init__.py +0 -0
- openedx_learning/apps/authoring/units/api.py +305 -0
- openedx_learning/apps/authoring/units/apps.py +25 -0
- openedx_learning/apps/authoring/units/migrations/0001_initial.py +36 -0
- openedx_learning/apps/authoring/units/migrations/__init__.py +0 -0
- openedx_learning/apps/authoring/units/models.py +50 -0
- openedx_learning/contrib/media_server/apps.py +1 -1
- openedx_learning/lib/managers.py +7 -1
- openedx_learning/lib/validators.py +1 -1
- {openedx_learning-0.18.3.dist-info → openedx_learning-0.19.1.dist-info}/METADATA +3 -3
- {openedx_learning-0.18.3.dist-info → openedx_learning-0.19.1.dist-info}/RECORD +37 -24
- {openedx_learning-0.18.3.dist-info → openedx_learning-0.19.1.dist-info}/WHEEL +1 -1
- openedx_tagging/core/tagging/api.py +4 -4
- openedx_tagging/core/tagging/models/base.py +1 -1
- openedx_tagging/core/tagging/rest_api/v1/permissions.py +1 -1
- openedx_tagging/core/tagging/rules.py +1 -2
- openedx_learning/apps/authoring/publishing/models.py +0 -517
- {openedx_learning-0.18.3.dist-info → openedx_learning-0.19.1.dist-info}/LICENSE.txt +0 -0
- {openedx_learning-0.18.3.dist-info → openedx_learning-0.19.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
"""Units API.
|
|
2
|
+
|
|
3
|
+
This module provides functions to manage units.
|
|
4
|
+
"""
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
|
|
8
|
+
from django.db.transaction import atomic
|
|
9
|
+
|
|
10
|
+
from openedx_learning.apps.authoring.components.models import Component, ComponentVersion
|
|
11
|
+
|
|
12
|
+
from ..publishing import api as publishing_api
|
|
13
|
+
from .models import Unit, UnitVersion
|
|
14
|
+
|
|
15
|
+
# 🛑 UNSTABLE: All APIs related to containers are unstable until we've figured
|
|
16
|
+
# out our approach to dynamic content (randomized, A/B tests, etc.)
|
|
17
|
+
__all__ = [
|
|
18
|
+
"create_unit",
|
|
19
|
+
"create_unit_version",
|
|
20
|
+
"create_next_unit_version",
|
|
21
|
+
"create_unit_and_version",
|
|
22
|
+
"get_unit",
|
|
23
|
+
"get_unit_version",
|
|
24
|
+
"get_latest_unit_version",
|
|
25
|
+
"UnitListEntry",
|
|
26
|
+
"get_components_in_unit",
|
|
27
|
+
"get_components_in_unit",
|
|
28
|
+
"get_components_in_published_unit_as_of",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def create_unit(
|
|
33
|
+
learning_package_id: int,
|
|
34
|
+
key: str,
|
|
35
|
+
created: datetime,
|
|
36
|
+
created_by: int | None,
|
|
37
|
+
*,
|
|
38
|
+
can_stand_alone: bool = True,
|
|
39
|
+
) -> Unit:
|
|
40
|
+
"""
|
|
41
|
+
[ 🛑 UNSTABLE ] Create a new unit.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
learning_package_id: The learning package ID.
|
|
45
|
+
key: The key.
|
|
46
|
+
created: The creation date.
|
|
47
|
+
created_by: The user who created the unit.
|
|
48
|
+
can_stand_alone: Set to False when created as part of containers
|
|
49
|
+
"""
|
|
50
|
+
return publishing_api.create_container(
|
|
51
|
+
learning_package_id,
|
|
52
|
+
key,
|
|
53
|
+
created,
|
|
54
|
+
created_by,
|
|
55
|
+
can_stand_alone=can_stand_alone,
|
|
56
|
+
container_cls=Unit,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def create_unit_version(
|
|
61
|
+
unit: Unit,
|
|
62
|
+
version_num: int,
|
|
63
|
+
*,
|
|
64
|
+
title: str,
|
|
65
|
+
publishable_entities_pks: list[int],
|
|
66
|
+
entity_version_pks: list[int | None],
|
|
67
|
+
created: datetime,
|
|
68
|
+
created_by: int | None = None,
|
|
69
|
+
) -> UnitVersion:
|
|
70
|
+
"""
|
|
71
|
+
[ 🛑 UNSTABLE ] Create a new unit version.
|
|
72
|
+
|
|
73
|
+
This is a very low-level API, likely only needed for import/export. In
|
|
74
|
+
general, you will use `create_unit_and_version()` and
|
|
75
|
+
`create_next_unit_version()` instead.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
unit_pk: The unit ID.
|
|
79
|
+
version_num: The version number.
|
|
80
|
+
title: The title.
|
|
81
|
+
publishable_entities_pk: The publishable entities.
|
|
82
|
+
entity: The entity.
|
|
83
|
+
created: The creation date.
|
|
84
|
+
created_by: The user who created the unit.
|
|
85
|
+
"""
|
|
86
|
+
return publishing_api.create_container_version(
|
|
87
|
+
unit.pk,
|
|
88
|
+
version_num,
|
|
89
|
+
title=title,
|
|
90
|
+
publishable_entities_pks=publishable_entities_pks,
|
|
91
|
+
entity_version_pks=entity_version_pks,
|
|
92
|
+
created=created,
|
|
93
|
+
created_by=created_by,
|
|
94
|
+
container_version_cls=UnitVersion,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _pub_entities_for_components(
|
|
99
|
+
components: list[Component | ComponentVersion] | None,
|
|
100
|
+
) -> tuple[list[int], list[int | None]] | tuple[None, None]:
|
|
101
|
+
"""
|
|
102
|
+
Helper method: given a list of Component | ComponentVersion, return the
|
|
103
|
+
lists of publishable_entities_pks and entity_version_pks needed for the
|
|
104
|
+
base container APIs.
|
|
105
|
+
|
|
106
|
+
ComponentVersion is passed when we want to pin a specific version, otherwise
|
|
107
|
+
Component is used for unpinned.
|
|
108
|
+
"""
|
|
109
|
+
if components is None:
|
|
110
|
+
# When these are None, that means don't change the entities in the list.
|
|
111
|
+
return None, None
|
|
112
|
+
for c in components:
|
|
113
|
+
if not isinstance(c, (Component, ComponentVersion)):
|
|
114
|
+
raise TypeError("Unit components must be either Component or ComponentVersion.")
|
|
115
|
+
publishable_entities_pks = [
|
|
116
|
+
(c.publishable_entity_id if isinstance(c, Component) else c.component.publishable_entity_id)
|
|
117
|
+
for c in components
|
|
118
|
+
]
|
|
119
|
+
entity_version_pks = [
|
|
120
|
+
(cv.pk if isinstance(cv, ComponentVersion) else None)
|
|
121
|
+
for cv in components
|
|
122
|
+
]
|
|
123
|
+
return publishable_entities_pks, entity_version_pks
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def create_next_unit_version(
|
|
127
|
+
unit: Unit,
|
|
128
|
+
*,
|
|
129
|
+
title: str | None = None,
|
|
130
|
+
components: list[Component | ComponentVersion] | None = None,
|
|
131
|
+
created: datetime,
|
|
132
|
+
created_by: int | None = None,
|
|
133
|
+
) -> UnitVersion:
|
|
134
|
+
"""
|
|
135
|
+
[ 🛑 UNSTABLE ] Create the next unit version.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
unit_pk: The unit ID.
|
|
139
|
+
title: The title. Leave as None to keep the current title.
|
|
140
|
+
components: The components, as a list of Components (unpinned) and/or ComponentVersions (pinned). Passing None
|
|
141
|
+
will leave the existing components unchanged.
|
|
142
|
+
created: The creation date.
|
|
143
|
+
created_by: The user who created the unit.
|
|
144
|
+
"""
|
|
145
|
+
publishable_entities_pks, entity_version_pks = _pub_entities_for_components(components)
|
|
146
|
+
unit_version = publishing_api.create_next_container_version(
|
|
147
|
+
unit.pk,
|
|
148
|
+
title=title,
|
|
149
|
+
publishable_entities_pks=publishable_entities_pks,
|
|
150
|
+
entity_version_pks=entity_version_pks,
|
|
151
|
+
created=created,
|
|
152
|
+
created_by=created_by,
|
|
153
|
+
container_version_cls=UnitVersion,
|
|
154
|
+
)
|
|
155
|
+
return unit_version
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def create_unit_and_version(
|
|
159
|
+
learning_package_id: int,
|
|
160
|
+
key: str,
|
|
161
|
+
*,
|
|
162
|
+
title: str,
|
|
163
|
+
components: list[Component | ComponentVersion] | None = None,
|
|
164
|
+
created: datetime,
|
|
165
|
+
created_by: int | None = None,
|
|
166
|
+
can_stand_alone: bool = True,
|
|
167
|
+
) -> tuple[Unit, UnitVersion]:
|
|
168
|
+
"""
|
|
169
|
+
[ 🛑 UNSTABLE ] Create a new unit and its version.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
learning_package_id: The learning package ID.
|
|
173
|
+
key: The key.
|
|
174
|
+
created: The creation date.
|
|
175
|
+
created_by: The user who created the unit.
|
|
176
|
+
can_stand_alone: Set to False when created as part of containers
|
|
177
|
+
"""
|
|
178
|
+
publishable_entities_pks, entity_version_pks = _pub_entities_for_components(components)
|
|
179
|
+
with atomic():
|
|
180
|
+
unit = create_unit(
|
|
181
|
+
learning_package_id,
|
|
182
|
+
key,
|
|
183
|
+
created,
|
|
184
|
+
created_by,
|
|
185
|
+
can_stand_alone=can_stand_alone,
|
|
186
|
+
)
|
|
187
|
+
unit_version = create_unit_version(
|
|
188
|
+
unit,
|
|
189
|
+
1,
|
|
190
|
+
title=title,
|
|
191
|
+
publishable_entities_pks=publishable_entities_pks or [],
|
|
192
|
+
entity_version_pks=entity_version_pks or [],
|
|
193
|
+
created=created,
|
|
194
|
+
created_by=created_by,
|
|
195
|
+
)
|
|
196
|
+
return unit, unit_version
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def get_unit(unit_pk: int) -> Unit:
|
|
200
|
+
"""
|
|
201
|
+
[ 🛑 UNSTABLE ] Get a unit.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
unit_pk: The unit ID.
|
|
205
|
+
"""
|
|
206
|
+
return Unit.objects.get(pk=unit_pk)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def get_unit_version(unit_version_pk: int) -> UnitVersion:
|
|
210
|
+
"""
|
|
211
|
+
[ 🛑 UNSTABLE ] Get a unit version.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
unit_version_pk: The unit version ID.
|
|
215
|
+
"""
|
|
216
|
+
return UnitVersion.objects.get(pk=unit_version_pk)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def get_latest_unit_version(unit_pk: int) -> UnitVersion:
|
|
220
|
+
"""
|
|
221
|
+
[ 🛑 UNSTABLE ] Get the latest unit version.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
unit_pk: The unit ID.
|
|
225
|
+
"""
|
|
226
|
+
return Unit.objects.get(pk=unit_pk).versioning.latest
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
@dataclass(frozen=True)
|
|
230
|
+
class UnitListEntry:
|
|
231
|
+
"""
|
|
232
|
+
[ 🛑 UNSTABLE ]
|
|
233
|
+
Data about a single entity in a container, e.g. a component in a unit.
|
|
234
|
+
"""
|
|
235
|
+
component_version: ComponentVersion
|
|
236
|
+
pinned: bool = False
|
|
237
|
+
|
|
238
|
+
@property
|
|
239
|
+
def component(self):
|
|
240
|
+
return self.component_version.component
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def get_components_in_unit(
|
|
244
|
+
unit: Unit,
|
|
245
|
+
*,
|
|
246
|
+
published: bool,
|
|
247
|
+
) -> list[UnitListEntry]:
|
|
248
|
+
"""
|
|
249
|
+
[ 🛑 UNSTABLE ]
|
|
250
|
+
Get the list of entities and their versions in the draft or published
|
|
251
|
+
version of the given Unit.
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
unit: The Unit, e.g. returned by `get_unit()`
|
|
255
|
+
published: `True` if we want the published version of the unit, or
|
|
256
|
+
`False` for the draft version.
|
|
257
|
+
"""
|
|
258
|
+
assert isinstance(unit, Unit)
|
|
259
|
+
components = []
|
|
260
|
+
for entry in publishing_api.get_entities_in_container(unit, published=published):
|
|
261
|
+
# Convert from generic PublishableEntityVersion to ComponentVersion:
|
|
262
|
+
component_version = entry.entity_version.componentversion
|
|
263
|
+
assert isinstance(component_version, ComponentVersion)
|
|
264
|
+
components.append(UnitListEntry(component_version=component_version, pinned=entry.pinned))
|
|
265
|
+
return components
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def get_components_in_published_unit_as_of(
|
|
269
|
+
unit: Unit,
|
|
270
|
+
publish_log_id: int,
|
|
271
|
+
) -> list[UnitListEntry] | None:
|
|
272
|
+
"""
|
|
273
|
+
[ 🛑 UNSTABLE ]
|
|
274
|
+
Get the list of entities and their versions in the published version of the
|
|
275
|
+
given container as of the given PublishLog version (which is essentially a
|
|
276
|
+
version for the entire learning package).
|
|
277
|
+
|
|
278
|
+
TODO: This API should be updated to also return the UnitVersion so we can
|
|
279
|
+
see the unit title and any other metadata from that point in time.
|
|
280
|
+
TODO: accept a publish log UUID, not just int ID?
|
|
281
|
+
TODO: move the implementation to be a generic 'containers' implementation
|
|
282
|
+
that this units function merely wraps.
|
|
283
|
+
TODO: optimize, perhaps by having the publishlog store a record of all
|
|
284
|
+
ancestors of every modified PublishableEntity in the publish.
|
|
285
|
+
"""
|
|
286
|
+
assert isinstance(unit, Unit)
|
|
287
|
+
unit_pub_entity_version = publishing_api.get_published_version_as_of(unit.publishable_entity_id, publish_log_id)
|
|
288
|
+
if unit_pub_entity_version is None:
|
|
289
|
+
return None # This unit was not published as of the given PublishLog ID.
|
|
290
|
+
container_version = unit_pub_entity_version.containerversion
|
|
291
|
+
|
|
292
|
+
entity_list = []
|
|
293
|
+
rows = container_version.entity_list.entitylistrow_set.order_by("order_num")
|
|
294
|
+
for row in rows:
|
|
295
|
+
if row.entity_version is not None:
|
|
296
|
+
component_version = row.entity_version.componentversion
|
|
297
|
+
assert isinstance(component_version, ComponentVersion)
|
|
298
|
+
entity_list.append(UnitListEntry(component_version=component_version, pinned=True))
|
|
299
|
+
else:
|
|
300
|
+
# Unpinned component - figure out what its latest published version was.
|
|
301
|
+
# This is not optimized. It could be done in one query per unit rather than one query per component.
|
|
302
|
+
pub_entity_version = publishing_api.get_published_version_as_of(row.entity_id, publish_log_id)
|
|
303
|
+
if pub_entity_version:
|
|
304
|
+
entity_list.append(UnitListEntry(component_version=pub_entity_version.componentversion, pinned=False))
|
|
305
|
+
return entity_list
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit Django application initialization.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from django.apps import AppConfig
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class UnitsConfig(AppConfig):
|
|
9
|
+
"""
|
|
10
|
+
Configuration for the units Django application.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
name = "openedx_learning.apps.authoring.units"
|
|
14
|
+
verbose_name = "Learning Core > Authoring > Units"
|
|
15
|
+
default_auto_field = "django.db.models.BigAutoField"
|
|
16
|
+
label = "oel_units"
|
|
17
|
+
|
|
18
|
+
def ready(self):
|
|
19
|
+
"""
|
|
20
|
+
Register Unit and UnitVersion.
|
|
21
|
+
"""
|
|
22
|
+
from ..publishing.api import register_content_models # pylint: disable=import-outside-toplevel
|
|
23
|
+
from .models import Unit, UnitVersion # pylint: disable=import-outside-toplevel
|
|
24
|
+
|
|
25
|
+
register_content_models(Unit, UnitVersion)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Generated by Django 4.2.19 on 2025-03-11 04:31
|
|
2
|
+
|
|
3
|
+
import django.db.models.deletion
|
|
4
|
+
from django.db import migrations, models
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
|
|
9
|
+
initial = True
|
|
10
|
+
|
|
11
|
+
dependencies = [
|
|
12
|
+
('oel_publishing', '0003_containers'),
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
operations = [
|
|
16
|
+
migrations.CreateModel(
|
|
17
|
+
name='Unit',
|
|
18
|
+
fields=[
|
|
19
|
+
('container', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='oel_publishing.container')),
|
|
20
|
+
],
|
|
21
|
+
options={
|
|
22
|
+
'abstract': False,
|
|
23
|
+
},
|
|
24
|
+
bases=('oel_publishing.container',),
|
|
25
|
+
),
|
|
26
|
+
migrations.CreateModel(
|
|
27
|
+
name='UnitVersion',
|
|
28
|
+
fields=[
|
|
29
|
+
('container_version', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='oel_publishing.containerversion')),
|
|
30
|
+
],
|
|
31
|
+
options={
|
|
32
|
+
'abstract': False,
|
|
33
|
+
},
|
|
34
|
+
bases=('oel_publishing.containerversion',),
|
|
35
|
+
),
|
|
36
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Models that implement units
|
|
3
|
+
"""
|
|
4
|
+
from django.db import models
|
|
5
|
+
|
|
6
|
+
from ..publishing.models import Container, ContainerVersion
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"Unit",
|
|
10
|
+
"UnitVersion",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Unit(Container):
|
|
15
|
+
"""
|
|
16
|
+
A Unit is type of Container that holds Components.
|
|
17
|
+
|
|
18
|
+
Via Container and its PublishableEntityMixin, Units are also publishable
|
|
19
|
+
entities and can be added to other containers.
|
|
20
|
+
"""
|
|
21
|
+
container = models.OneToOneField(
|
|
22
|
+
Container,
|
|
23
|
+
on_delete=models.CASCADE,
|
|
24
|
+
parent_link=True,
|
|
25
|
+
primary_key=True,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class UnitVersion(ContainerVersion):
|
|
30
|
+
"""
|
|
31
|
+
A UnitVersion is a specific version of a Unit.
|
|
32
|
+
|
|
33
|
+
Via ContainerVersion and its EntityList, it defines the list of Components
|
|
34
|
+
in this version of the Unit.
|
|
35
|
+
"""
|
|
36
|
+
container_version = models.OneToOneField(
|
|
37
|
+
ContainerVersion,
|
|
38
|
+
on_delete=models.CASCADE,
|
|
39
|
+
parent_link=True,
|
|
40
|
+
primary_key=True,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def unit(self):
|
|
45
|
+
""" Convenience accessor to the Unit this version is associated with """
|
|
46
|
+
return self.container_version.container.unit # pylint: disable=no-member
|
|
47
|
+
|
|
48
|
+
# Note: the 'publishable_entity_version' field is inherited and will appear on this model, but does not exist
|
|
49
|
+
# in the underlying database table. It only exists in the ContainerVersion table.
|
|
50
|
+
# You can verify this by running 'python manage.py sqlmigrate oel_units 0001_initial'
|
|
@@ -15,7 +15,7 @@ class MediaServerConfig(AppConfig):
|
|
|
15
15
|
verbose_name = "Learning Core: Media Server"
|
|
16
16
|
default_auto_field = "django.db.models.BigAutoField"
|
|
17
17
|
|
|
18
|
-
def ready(self):
|
|
18
|
+
def ready(self) -> None:
|
|
19
19
|
if not settings.DEBUG:
|
|
20
20
|
# Until we get proper security and support for running this app
|
|
21
21
|
# under a separate domain, just don't allow it to be run in
|
openedx_learning/lib/managers.py
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Custom Django ORM Managers.
|
|
3
3
|
"""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from typing import TypeVar
|
|
7
|
+
|
|
4
8
|
from django.db import models
|
|
5
9
|
from django.db.models.query import QuerySet
|
|
6
10
|
|
|
11
|
+
M = TypeVar('M', bound=models.Model)
|
|
12
|
+
|
|
7
13
|
|
|
8
|
-
class WithRelationsManager(models.Manager):
|
|
14
|
+
class WithRelationsManager(models.Manager[M]):
|
|
9
15
|
"""
|
|
10
16
|
Custom Manager that adds select_related to the default queryset.
|
|
11
17
|
|
|
@@ -7,7 +7,7 @@ from django.core.exceptions import ValidationError
|
|
|
7
7
|
from django.utils.translation import gettext_lazy as _
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
def validate_utc_datetime(dt: datetime):
|
|
10
|
+
def validate_utc_datetime(dt: datetime) -> None:
|
|
11
11
|
if dt.tzinfo != timezone.utc:
|
|
12
12
|
raise ValidationError(
|
|
13
13
|
_("The timezone for %(datetime)s is not UTC."),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: openedx-learning
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.19.1
|
|
4
4
|
Summary: Open edX Learning Core and Tagging.
|
|
5
5
|
Home-page: https://github.com/openedx/openedx-learning
|
|
6
6
|
Author: David Ormsbee
|
|
@@ -18,11 +18,11 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
19
|
Requires-Python: >=3.11
|
|
20
20
|
License-File: LICENSE.txt
|
|
21
|
-
Requires-Dist: rules<4.0
|
|
22
|
-
Requires-Dist: attrs
|
|
23
21
|
Requires-Dist: edx-drf-extensions
|
|
24
22
|
Requires-Dist: Django<5.0
|
|
23
|
+
Requires-Dist: rules<4.0
|
|
25
24
|
Requires-Dist: celery
|
|
25
|
+
Requires-Dist: attrs
|
|
26
26
|
Requires-Dist: djangorestframework<4.0
|
|
27
27
|
Dynamic: author
|
|
28
28
|
Dynamic: author-email
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
openedx_learning/__init__.py,sha256=
|
|
1
|
+
openedx_learning/__init__.py,sha256=px6MoC3pWrrXjiOIJXCYYjh1zVYJmN1_36ERKhfnEOQ,69
|
|
2
2
|
openedx_learning/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
3
|
openedx_learning/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
openedx_learning/api/authoring.py,sha256=
|
|
5
|
-
openedx_learning/api/authoring_models.py,sha256=
|
|
4
|
+
openedx_learning/api/authoring.py,sha256=AuEegFTT3tAer_3zSxcxqLq3Yd_eilk-Hxt4ESYearA,970
|
|
5
|
+
openedx_learning/api/authoring_models.py,sha256=oXwqeMAnXBWgVIUcJa0he5QjHvG296AJE-1hDpjd8JM,681
|
|
6
6
|
openedx_learning/apps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
7
|
openedx_learning/apps/authoring/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
8
|
openedx_learning/apps/authoring/collections/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -18,9 +18,9 @@ openedx_learning/apps/authoring/collections/migrations/0005_alter_collection_opt
|
|
|
18
18
|
openedx_learning/apps/authoring/collections/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
19
|
openedx_learning/apps/authoring/components/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
20
|
openedx_learning/apps/authoring/components/admin.py,sha256=zfEpuBEySMYpUZzygaE2MDoI8SH-2H3xIL20YCSCMLo,4582
|
|
21
|
-
openedx_learning/apps/authoring/components/api.py,sha256=
|
|
22
|
-
openedx_learning/apps/authoring/components/apps.py,sha256=
|
|
23
|
-
openedx_learning/apps/authoring/components/models.py,sha256=
|
|
21
|
+
openedx_learning/apps/authoring/components/api.py,sha256=PmCtJiyw0Vy4PGSRt9JCStJQv9OJ4FchtOyiY1sfJ-s,25254
|
|
22
|
+
openedx_learning/apps/authoring/components/apps.py,sha256=Rcydv_FH-rVvuWIFqUezBNOh8DtrZHyozZM2yqX2JKE,778
|
|
23
|
+
openedx_learning/apps/authoring/components/models.py,sha256=ttZzVnMZTa14-qudrLb4CFuCanEQJT8cuC_iVPH8XTA,10887
|
|
24
24
|
openedx_learning/apps/authoring/components/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
25
25
|
openedx_learning/apps/authoring/components/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
26
|
openedx_learning/apps/authoring/components/management/commands/add_assets_to_component.py,sha256=0dJ77NZZoNzYheOdFPXtJrjdL_Z-pCNg3l1rbEGnMCY,3175
|
|
@@ -32,21 +32,34 @@ openedx_learning/apps/authoring/contents/__init__.py,sha256=47DEQpj8HBSa-_TImW-5
|
|
|
32
32
|
openedx_learning/apps/authoring/contents/admin.py,sha256=9Njd_lje1emcd168KBWUTGf0mVJ6K-dMYMcqHNjRU4k,1761
|
|
33
33
|
openedx_learning/apps/authoring/contents/api.py,sha256=bXb9yQjPfoP1Ynf1aAYz3BEPffK7H5cnba6KdPFSiG0,8818
|
|
34
34
|
openedx_learning/apps/authoring/contents/apps.py,sha256=EEUZEnww7TcYcyxMovZthG2muNxd7j7nxBIf21gKrp4,398
|
|
35
|
-
openedx_learning/apps/authoring/contents/models.py,sha256=
|
|
35
|
+
openedx_learning/apps/authoring/contents/models.py,sha256=8CYrHK7CZ4U3F7Den4ZV_al7zdoi5ba6X2C0LRinA68,17606
|
|
36
36
|
openedx_learning/apps/authoring/contents/migrations/0001_initial.py,sha256=FtOTmIGX2KHpjw-PHbfRjxkFEomI5CEDhNKCZ7IpFeE,3060
|
|
37
37
|
openedx_learning/apps/authoring/contents/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
38
38
|
openedx_learning/apps/authoring/publishing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
39
|
-
openedx_learning/apps/authoring/publishing/admin.py,sha256=
|
|
40
|
-
openedx_learning/apps/authoring/publishing/api.py,sha256=
|
|
41
|
-
openedx_learning/apps/authoring/publishing/apps.py,sha256=
|
|
42
|
-
openedx_learning/apps/authoring/publishing/model_mixins.py,sha256=DGM6cZSNX4KZHy5xWtNy6yO4bRT04VDus8yZJ4v3vI0,14447
|
|
43
|
-
openedx_learning/apps/authoring/publishing/models.py,sha256=ImMAujPDc2-CECZw_yvVlUOdtGYwmt99TJ2r1HJkkV8,20488
|
|
39
|
+
openedx_learning/apps/authoring/publishing/admin.py,sha256=yPtqznDzlWHj0xpJ_FGFRviyftgPmuql5EoqKLeZQFo,4911
|
|
40
|
+
openedx_learning/apps/authoring/publishing/api.py,sha256=OregsRNQev41Nm1Uq6B_zoezJgV-EVsYaf8_iJ3sbxI,37430
|
|
41
|
+
openedx_learning/apps/authoring/publishing/apps.py,sha256=v9PTe3YoICaYT9wfu268ZkVAlnZFvxi-DqYdbRi25bY,750
|
|
44
42
|
openedx_learning/apps/authoring/publishing/migrations/0001_initial.py,sha256=wvekNV19YRSdxRmQaFnLSn_nCsQlHIucPDVMmgKf_OE,9272
|
|
45
43
|
openedx_learning/apps/authoring/publishing/migrations/0002_alter_learningpackage_key_and_more.py,sha256=toI7qJhNukk6hirKfFx9EpqTpzF2O2Yq1VpFJusDn2M,806
|
|
44
|
+
openedx_learning/apps/authoring/publishing/migrations/0003_containers.py,sha256=k5P1LFkjQT-42yyHE2f57dAMQKafLuJCTRaCBMKWcjc,2519
|
|
45
|
+
openedx_learning/apps/authoring/publishing/migrations/0004_publishableentity_can_stand_alone.py,sha256=RapDZKcjTp5QGgob9DUaf-zl_pQgg-5nz8PZWoXMlVA,549
|
|
46
46
|
openedx_learning/apps/authoring/publishing/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
47
|
+
openedx_learning/apps/authoring/publishing/models/__init__.py,sha256=Ou6Ks3qxEhel9uHLpzi77jlkb_IHc9_WIfqxOZJuqaE,1059
|
|
48
|
+
openedx_learning/apps/authoring/publishing/models/container.py,sha256=GCUD3WTlgvgZSQOKcoOsfhNAfc5pz3Wbs9ClE9mhtB0,2594
|
|
49
|
+
openedx_learning/apps/authoring/publishing/models/draft_published.py,sha256=dDZfbTcqLmnOhgIG-H8mhPFLRks_c03VE-ynGD_CNeo,3632
|
|
50
|
+
openedx_learning/apps/authoring/publishing/models/entity_list.py,sha256=EWpkUHUqJr5xKGJCRQ494xugdULxMXxYviFOot9ypUU,3009
|
|
51
|
+
openedx_learning/apps/authoring/publishing/models/learning_package.py,sha256=1fuNLHD6k0qGuL0jXYGf4-TA5WczgxJrXUdAIM_JNBI,2688
|
|
52
|
+
openedx_learning/apps/authoring/publishing/models/publish_log.py,sha256=-uMj1X8umqRM259ucOqNt6POeKDpY5RLdA8XnWXt7Go,4109
|
|
53
|
+
openedx_learning/apps/authoring/publishing/models/publishable_entity.py,sha256=-6Fde_sYqaq1AUC4sw4Rf1X9Hh4jun2Py7Xhsl4VWuY,25419
|
|
54
|
+
openedx_learning/apps/authoring/units/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
55
|
+
openedx_learning/apps/authoring/units/api.py,sha256=WjeM9E_5yH-zawlCzBY0BwCywRVeNxoojq4IDN9gOds,9957
|
|
56
|
+
openedx_learning/apps/authoring/units/apps.py,sha256=cIzphjDw5sjIZ3NLE911N7IMUa8JQSXMReNl03uI7jg,701
|
|
57
|
+
openedx_learning/apps/authoring/units/models.py,sha256=eTOwFWC9coQLf0ovx08Mj7zi8mPAWCw9QOznybajRk0,1418
|
|
58
|
+
openedx_learning/apps/authoring/units/migrations/0001_initial.py,sha256=qM_0JGffxECVgXzncHXfgSE-g8u3L3a14R0M1Bnj_Ys,1129
|
|
59
|
+
openedx_learning/apps/authoring/units/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
47
60
|
openedx_learning/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
48
61
|
openedx_learning/contrib/media_server/__init__.py,sha256=iYijWFCl5RNR9omSu22kMl49EfponoqXBqXr0HMp4QI,56
|
|
49
|
-
openedx_learning/contrib/media_server/apps.py,sha256=
|
|
62
|
+
openedx_learning/contrib/media_server/apps.py,sha256=GicFBN3N6wzVs5i3RgrQFJZeMlS55gyf4Iq_4cSdI9U,824
|
|
50
63
|
openedx_learning/contrib/media_server/urls.py,sha256=newNjV41sM9A9Oy_rgnZSXdkTFxSHiupIiAsVIGE2CE,365
|
|
51
64
|
openedx_learning/contrib/media_server/views.py,sha256=qZPhdEW_oYj1MEdgLVP6Cq3tRiZtp7dTb7ASaSKZ2HY,1350
|
|
52
65
|
openedx_learning/lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -54,18 +67,18 @@ openedx_learning/lib/admin_utils.py,sha256=5z9NrXxmT5j8azx9u1t0AgxV5PIDTc2jPyM5z
|
|
|
54
67
|
openedx_learning/lib/cache.py,sha256=ppT36KiPLdsAF3GfZCF0IdiHodckd2gLiF1sNhjSJuk,958
|
|
55
68
|
openedx_learning/lib/collations.py,sha256=f65575r3BfAvFWU6pdBMsqrxPwFijB2SbJtDXq4UVc4,2401
|
|
56
69
|
openedx_learning/lib/fields.py,sha256=eiGoXMPhRuq25EH2qf6BAODshAQE3DBVdIYAMIUAXW0,7522
|
|
57
|
-
openedx_learning/lib/managers.py,sha256
|
|
70
|
+
openedx_learning/lib/managers.py,sha256=-Q3gxalSqyPZ9Im4DTROW5tF8wVTZLlmfTe62_xmowY,1643
|
|
58
71
|
openedx_learning/lib/test_utils.py,sha256=g3KLuepIZbaDBCsaj9711YuqyUx7LD4gXDcfNC-mWdc,527
|
|
59
|
-
openedx_learning/lib/validators.py,sha256=
|
|
72
|
+
openedx_learning/lib/validators.py,sha256=iqEdEAvFV2tC7Ecssx69kjecpdU8nE87AlDJYrqrsnc,404
|
|
60
73
|
openedx_tagging/__init__.py,sha256=V9N8M7f9LYlAbA_DdPUsHzTnWjYRXKGa5qHw9P1JnNI,30
|
|
61
74
|
openedx_tagging/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
62
75
|
openedx_tagging/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
63
76
|
openedx_tagging/core/tagging/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
64
77
|
openedx_tagging/core/tagging/admin.py,sha256=Ngc2l9Mf6gkzmqu7aOwq-d0mgV8szx0GzSeuWFX7Kyg,1080
|
|
65
|
-
openedx_tagging/core/tagging/api.py,sha256=
|
|
78
|
+
openedx_tagging/core/tagging/api.py,sha256=zOYBy5hm4Sza6vcXRjMZt0DsVOIxZqfo_tilaFLqIJs,20095
|
|
66
79
|
openedx_tagging/core/tagging/apps.py,sha256=-gp0VYqX4XQzwjjd-G68Ev2Op0INLh9Byz5UOqF5_7k,345
|
|
67
80
|
openedx_tagging/core/tagging/data.py,sha256=421EvmDzdM7H523dBVQk4J0W_UwTT4U5syqPRXUYK4g,1353
|
|
68
|
-
openedx_tagging/core/tagging/rules.py,sha256=
|
|
81
|
+
openedx_tagging/core/tagging/rules.py,sha256=4XpwAoqnAG08rjpxK0G9-PyA7F4Wmkn8Navd9EnKRxo,6605
|
|
69
82
|
openedx_tagging/core/tagging/urls.py,sha256=-0Nzmh0mlF9-GgEuocwBdSJn6n8EINnxR4m4pmPou44,159
|
|
70
83
|
openedx_tagging/core/tagging/import_export/__init__.py,sha256=q5K4JalFQlJxAFUFyqhLY5zQtAskDnRM1H_aVuP_E3Q,83
|
|
71
84
|
openedx_tagging/core/tagging/import_export/actions.py,sha256=n007-M59D0mXYcwi9CDldDDy5JHAaGCVv9dQbiY4pZ4,13275
|
|
@@ -98,7 +111,7 @@ openedx_tagging/core/tagging/migrations/0017_alter_tagimporttask_status.py,sha25
|
|
|
98
111
|
openedx_tagging/core/tagging/migrations/0018_objecttag_is_copied.py,sha256=zmr4b65T0vX6fYc8MpvSmQnYkAiNMpx3RKEd5tudsl8,517
|
|
99
112
|
openedx_tagging/core/tagging/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
100
113
|
openedx_tagging/core/tagging/models/__init__.py,sha256=yYdOnthuc7EUdfEULtZgqRwn5Y4bbYQmJCjVZqR5GTM,236
|
|
101
|
-
openedx_tagging/core/tagging/models/base.py,sha256=
|
|
114
|
+
openedx_tagging/core/tagging/models/base.py,sha256=RXv4jDncV-JPXMAb62dGYNJhMdiS6kSo24eoG7UeKR4,39655
|
|
102
115
|
openedx_tagging/core/tagging/models/import_export.py,sha256=Aj0pleh0nh2LNS6zmdB1P4bpdgUMmvmobTkqBerORAI,4570
|
|
103
116
|
openedx_tagging/core/tagging/models/system_defined.py,sha256=_6LfvUZGEltvQMtm2OXy6TOLh3C8GnVTqtZDSAZW6K4,9062
|
|
104
117
|
openedx_tagging/core/tagging/models/utils.py,sha256=-A3Dj24twmTf65UB7G4WLvb_9qEvduEPIwahZ-FJDlg,1926
|
|
@@ -107,13 +120,13 @@ openedx_tagging/core/tagging/rest_api/paginators.py,sha256=BUIAg3taihHx7uAjpTZAG
|
|
|
107
120
|
openedx_tagging/core/tagging/rest_api/urls.py,sha256=egXaRQv1EAgF04ThgVZBQuvLK1LimuyUKKBD2Hbqb10,148
|
|
108
121
|
openedx_tagging/core/tagging/rest_api/utils.py,sha256=XZXixZ44vpNlxiyFplW8Lktyh_m1EfR3Y-tnyvA7acc,3620
|
|
109
122
|
openedx_tagging/core/tagging/rest_api/v1/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
110
|
-
openedx_tagging/core/tagging/rest_api/v1/permissions.py,sha256=
|
|
123
|
+
openedx_tagging/core/tagging/rest_api/v1/permissions.py,sha256=eD-RFK29vIqPOEQ_QXR4uOV4Jz0fQkNTy8KdVQDE2ak,2419
|
|
111
124
|
openedx_tagging/core/tagging/rest_api/v1/serializers.py,sha256=0HQD_Jrf6-YpocYfzGs74YBYjXe9Yo7cXU0A4VKr5Dw,13876
|
|
112
125
|
openedx_tagging/core/tagging/rest_api/v1/urls.py,sha256=dNUKCtUCx_YzrwlbEbpDfjGVQbb2QdJ1VuJCkladj6E,752
|
|
113
126
|
openedx_tagging/core/tagging/rest_api/v1/views.py,sha256=Hf92cy-tE767DE9FgsZcPKiCYrf5ihfETz8qGKBnuiU,36278
|
|
114
127
|
openedx_tagging/core/tagging/rest_api/v1/views_import.py,sha256=kbHUPe5A6WaaJ3J1lFIcYCt876ecLNQfd19m7YYub6c,1470
|
|
115
|
-
openedx_learning-0.
|
|
116
|
-
openedx_learning-0.
|
|
117
|
-
openedx_learning-0.
|
|
118
|
-
openedx_learning-0.
|
|
119
|
-
openedx_learning-0.
|
|
128
|
+
openedx_learning-0.19.1.dist-info/LICENSE.txt,sha256=QTW2QN7q3XszgUAXm9Dzgtu5LXYKbR1SGnqMa7ufEuY,35139
|
|
129
|
+
openedx_learning-0.19.1.dist-info/METADATA,sha256=Jm1GoiVhtSJfYV6nlRXB7H5XPy8XVvGTivFNF7j0f0w,8975
|
|
130
|
+
openedx_learning-0.19.1.dist-info/WHEEL,sha256=SrDKpSbFN1G94qcmBqS9nyHcDMp9cUS9OC06hC0G3G0,109
|
|
131
|
+
openedx_learning-0.19.1.dist-info/top_level.txt,sha256=IYFbr5mgiEHd-LOtZmXj3q3a0bkGK1M9LY7GXgnfi4M,33
|
|
132
|
+
openedx_learning-0.19.1.dist-info/RECORD,,
|
|
@@ -28,7 +28,7 @@ from .models.utils import ConcatNull, StringAgg
|
|
|
28
28
|
TagDoesNotExist = Tag.DoesNotExist
|
|
29
29
|
|
|
30
30
|
|
|
31
|
-
def create_taxonomy(
|
|
31
|
+
def create_taxonomy( # pylint: disable=too-many-positional-arguments
|
|
32
32
|
name: str,
|
|
33
33
|
description: str | None = None,
|
|
34
34
|
enabled=True,
|
|
@@ -191,9 +191,9 @@ def get_object_tags(
|
|
|
191
191
|
)
|
|
192
192
|
if not include_deleted:
|
|
193
193
|
# Exclude if the whole taxonomy was deleted
|
|
194
|
-
base_qs = base_qs.exclude(
|
|
194
|
+
base_qs = base_qs.exclude(taxonomy=None)
|
|
195
195
|
# Exclude if just the tag is deleted
|
|
196
|
-
base_qs = base_qs.exclude(
|
|
196
|
+
base_qs = base_qs.exclude(tag=None, taxonomy__allow_free_text=False)
|
|
197
197
|
tags = (
|
|
198
198
|
base_qs
|
|
199
199
|
# Preload related objects, including data for the "get_lineage" method on ObjectTag/Tag:
|
|
@@ -321,7 +321,7 @@ def _get_current_tags(
|
|
|
321
321
|
return current_tags
|
|
322
322
|
|
|
323
323
|
|
|
324
|
-
def tag_object(
|
|
324
|
+
def tag_object( # pylint: disable=too-many-positional-arguments
|
|
325
325
|
object_id: str,
|
|
326
326
|
taxonomy: Taxonomy | None,
|
|
327
327
|
tags: list[str],
|