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.
Files changed (38) hide show
  1. openedx_learning/__init__.py +1 -1
  2. openedx_learning/api/authoring.py +1 -0
  3. openedx_learning/api/authoring_models.py +1 -1
  4. openedx_learning/apps/authoring/components/api.py +18 -5
  5. openedx_learning/apps/authoring/components/apps.py +1 -1
  6. openedx_learning/apps/authoring/components/models.py +9 -14
  7. openedx_learning/apps/authoring/contents/models.py +1 -1
  8. openedx_learning/apps/authoring/publishing/admin.py +3 -0
  9. openedx_learning/apps/authoring/publishing/api.py +508 -3
  10. openedx_learning/apps/authoring/publishing/apps.py +9 -0
  11. openedx_learning/apps/authoring/publishing/migrations/0003_containers.py +54 -0
  12. openedx_learning/apps/authoring/publishing/migrations/0004_publishableentity_can_stand_alone.py +21 -0
  13. openedx_learning/apps/authoring/publishing/models/__init__.py +27 -0
  14. openedx_learning/apps/authoring/publishing/models/container.py +70 -0
  15. openedx_learning/apps/authoring/publishing/models/draft_published.py +95 -0
  16. openedx_learning/apps/authoring/publishing/models/entity_list.py +69 -0
  17. openedx_learning/apps/authoring/publishing/models/learning_package.py +75 -0
  18. openedx_learning/apps/authoring/publishing/models/publish_log.py +106 -0
  19. openedx_learning/apps/authoring/publishing/{model_mixins.py → models/publishable_entity.py} +289 -41
  20. openedx_learning/apps/authoring/units/__init__.py +0 -0
  21. openedx_learning/apps/authoring/units/api.py +305 -0
  22. openedx_learning/apps/authoring/units/apps.py +25 -0
  23. openedx_learning/apps/authoring/units/migrations/0001_initial.py +36 -0
  24. openedx_learning/apps/authoring/units/migrations/__init__.py +0 -0
  25. openedx_learning/apps/authoring/units/models.py +50 -0
  26. openedx_learning/contrib/media_server/apps.py +1 -1
  27. openedx_learning/lib/managers.py +7 -1
  28. openedx_learning/lib/validators.py +1 -1
  29. {openedx_learning-0.18.3.dist-info → openedx_learning-0.19.1.dist-info}/METADATA +3 -3
  30. {openedx_learning-0.18.3.dist-info → openedx_learning-0.19.1.dist-info}/RECORD +37 -24
  31. {openedx_learning-0.18.3.dist-info → openedx_learning-0.19.1.dist-info}/WHEEL +1 -1
  32. openedx_tagging/core/tagging/api.py +4 -4
  33. openedx_tagging/core/tagging/models/base.py +1 -1
  34. openedx_tagging/core/tagging/rest_api/v1/permissions.py +1 -1
  35. openedx_tagging/core/tagging/rules.py +1 -2
  36. openedx_learning/apps/authoring/publishing/models.py +0 -517
  37. {openedx_learning-0.18.3.dist-info → openedx_learning-0.19.1.dist-info}/LICENSE.txt +0 -0
  38. {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
+ ]
@@ -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
@@ -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.18.3
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=ZT8F2OsEJAp5M7O8SMjarWZezDBwgmA2PjXofoq3_zU,69
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=vbRpiQ2wOfN3oR2bbN0-bI2ra0QRtha9tVixKW1ENis,929
5
- openedx_learning/api/authoring_models.py,sha256=PrhEYozI7nzC7N1v_sP1QgjwjRvLX6it6bVMrubU73M,692
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=Kd8dscUAB5HWX84upmpfK-HjB_1olR7QBP7Ybxn0FVM,24894
22
- openedx_learning/apps/authoring/components/apps.py,sha256=YoYPsI9gcleA3uEs8CiLIrjUncRMo2DKbYt4mDfzePg,770
23
- openedx_learning/apps/authoring/components/models.py,sha256=VX-pi4ZPJkCP3UtyTcVihgBdBj2pmenwdVC_xx8wCi0,11190
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=AG3U00TPEjshngBdBhC_qCTF_dPcQMEmjmzW9pjA4BU,17599
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=F-0QlVQmuovLIF258XK_vKJdOnn7lLa_0A5veE72TKc,4830
40
- openedx_learning/apps/authoring/publishing/api.py,sha256=dawwRDbqbA_02xtPAwFIukWShrkO6MoQptmEKdAN7uA,17475
41
- openedx_learning/apps/authoring/publishing/apps.py,sha256=jUfd78xvXaZg3dwkqXihatbeajJGm3Uz1rJpuyd-3g0,402
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=FPT0rsUFtPyhFpWKjSI1e_s58wU0IbDyaAW_66V6sY4,816
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=ofcUxHS2wsJSFP4CB7OCkN6JiWFxNlIyNh_VDgFtgug,1538
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=98UldFhFxd77zF1VxzQcOPt0blh64gMZDv-d_uQ67mg,396
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=pDaXco7RFTQjPq6aIXLkXcUajm8ljd1vJcFaYuNzLqI,20035
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=Gzw2RCQxoAv2PpOwOWgpD17XoZfowlFnNgQqYn59q_g,6715
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=RX6rZcG7njrgLvM4huh79lC1vXzFDLxqElJqOU50Od8,39606
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=7HPE_NuKku_ISnkeE_HsFNXVYt0IbVkJN6M4wqwHGHU,2443
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.18.3.dist-info/LICENSE.txt,sha256=QTW2QN7q3XszgUAXm9Dzgtu5LXYKbR1SGnqMa7ufEuY,35139
116
- openedx_learning-0.18.3.dist-info/METADATA,sha256=E5Xu9Y1qP7u-KW4lBS5GJ_fk3zEHbHjp1D3f_iRbUcc,8975
117
- openedx_learning-0.18.3.dist-info/WHEEL,sha256=9Hm2OB-j1QcCUq9Jguht7ayGIIZBRTdOXD1qg9cCgPM,109
118
- openedx_learning-0.18.3.dist-info/top_level.txt,sha256=IYFbr5mgiEHd-LOtZmXj3q3a0bkGK1M9LY7GXgnfi4M,33
119
- openedx_learning-0.18.3.dist-info/RECORD,,
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.0)
2
+ Generator: setuptools (76.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py2-none-any
5
5
  Tag: py3-none-any
@@ -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(taxonomy_id=None) # type: ignore
194
+ base_qs = base_qs.exclude(taxonomy=None)
195
195
  # Exclude if just the tag is deleted
196
- base_qs = base_qs.exclude(tag_id=None, taxonomy__allow_free_text=False) # type: ignore
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],
@@ -382,7 +382,7 @@ class Taxonomy(models.Model):
382
382
 
383
383
  return self
384
384
 
385
- def get_filtered_tags(
385
+ def get_filtered_tags( # pylint: disable=too-many-positional-arguments
386
386
  self,
387
387
  depth: int | None = TAXONOMY_MAX_DEPTH,
388
388
  parent_tag_value: str | None = None,
@@ -1,7 +1,7 @@
1
1
  """
2
2
  Tagging permissions
3
3
  """
4
- import rules # type: ignore[import]
4
+ import rules
5
5
  from rest_framework.permissions import DjangoObjectPermissions
6
6
 
7
7
  from ...models import Tag, Taxonomy