openedx-learning 0.28.0__py2.py3-none-any.whl → 0.29.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.
@@ -34,6 +34,7 @@ from .models import Component, ComponentType, ComponentVersion, ComponentVersion
34
34
  # to be callable only by other apps in the authoring package.
35
35
  __all__ = [
36
36
  "get_or_create_component_type",
37
+ "get_or_create_component_type_by_entity_key",
37
38
  "create_component",
38
39
  "create_component_version",
39
40
  "create_next_component_version",
@@ -73,6 +74,27 @@ def get_or_create_component_type(namespace: str, name: str) -> ComponentType:
73
74
  return component_type
74
75
 
75
76
 
77
+ def get_or_create_component_type_by_entity_key(entity_key: str) -> tuple[ComponentType, str]:
78
+ """
79
+ Get or create a ComponentType based on a full entity key string.
80
+
81
+ The entity key is expected to be in the format
82
+ ``"{namespace}:{type_name}:{local_key}"``. This function will parse out the
83
+ ``namespace`` and ``type_name`` parts and use those to get or create the
84
+ ComponentType.
85
+
86
+ Raises ValueError if the entity_key is not in the expected format.
87
+ """
88
+ try:
89
+ namespace, type_name, local_key = entity_key.split(':', 2)
90
+ except ValueError as exc:
91
+ raise ValueError(
92
+ f"Invalid entity_key format: {entity_key!r}. "
93
+ "Expected format: '{namespace}:{type_name}:{local_key}'"
94
+ ) from exc
95
+ return get_or_create_component_type(namespace, type_name), local_key
96
+
97
+
76
98
  def create_component(
77
99
  learning_package_id: int,
78
100
  /,
@@ -137,10 +159,28 @@ def create_next_component_version(
137
159
  created: datetime,
138
160
  title: str | None = None,
139
161
  created_by: int | None = None,
162
+ *,
163
+ force_version_num: int | None = None,
164
+ ignore_previous_content: bool = False,
140
165
  ) -> ComponentVersion:
141
166
  """
142
167
  Create a new ComponentVersion based on the most recent version.
143
168
 
169
+ Args:
170
+ component_pk (int): The primary key of the Component to version.
171
+ content_to_replace (dict): Mapping of file keys to Content IDs,
172
+ None (for deletion), or bytes (for new file content).
173
+ created (datetime): The creation timestamp for the new version.
174
+ title (str, optional): Title for the new version. If None, uses the previous version's title.
175
+ created_by (int, optional): User ID of the creator.
176
+ force_version_num (int, optional): If provided, overrides the automatic version number increment and sets
177
+ this version's number explicitly. Use this if you need to restore or import a version with a specific
178
+ version number, such as during data migration or when synchronizing with external systems.
179
+ ignore_previous_content (bool): If True, do not copy over content from the previous version.
180
+
181
+ Returns:
182
+ ComponentVersion: The newly created ComponentVersion instance.
183
+
144
184
  A very common pattern for making a new ComponentVersion is going to be "make
145
185
  it just like the last version, except changing these one or two things".
146
186
  Before calling this, you should create any new contents via the contents
@@ -161,6 +201,14 @@ def create_next_component_version(
161
201
  convenient to remove paths (e.g. due to deprecation) without having to
162
202
  always check for its existence first.
163
203
 
204
+ Why use force_version_num?
205
+ Normally, the version number is incremented automatically from the latest version. If you need to set a specific
206
+ version number (for example, when restoring from backup, importing legacy data, or synchronizing with another
207
+ system), use force_version_num to override the default behavior.
208
+
209
+ Why not use create_component_version?
210
+ The main reason is that we want to reuse the logic to create a static file component from a dictionary.
211
+
164
212
  TODO: Have to add learning_downloadable info to this when it comes time to
165
213
  support static asset download.
166
214
  """
@@ -180,6 +228,9 @@ def create_next_component_version(
180
228
  if title is None:
181
229
  title = last_version.title
182
230
 
231
+ if force_version_num is not None:
232
+ next_version_num = force_version_num
233
+
183
234
  with atomic():
184
235
  publishable_entity_version = publishing_api.create_publishable_entity_version(
185
236
  component_pk,
@@ -219,6 +270,10 @@ def create_next_component_version(
219
270
  component_version=component_version,
220
271
  key=key,
221
272
  )
273
+
274
+ if ignore_previous_content:
275
+ return component_version
276
+
222
277
  # Now copy any old associations that existed, as long as they aren't
223
278
  # in conflict with the new stuff or marked for deletion.
224
279
  last_version_content_mapping = ComponentVersionContent.objects \
@@ -0,0 +1,17 @@
1
+ # Generated by Django 4.2.23 on 2025-10-02 14:00
2
+
3
+ from django.db import migrations
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('oel_components', '0003_remove_componentversioncontent_learner_downloadable'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.RemoveField(
14
+ model_name='componentversioncontent',
15
+ name='uuid',
16
+ ),
17
+ ]
@@ -21,7 +21,7 @@ from typing import ClassVar
21
21
 
22
22
  from django.db import models
23
23
 
24
- from ....lib.fields import case_sensitive_char_field, immutable_uuid_field, key_field
24
+ from ....lib.fields import case_sensitive_char_field, key_field
25
25
  from ....lib.managers import WithRelationsManager
26
26
  from ..contents.models import Content
27
27
  from ..publishing.models import LearningPackage, PublishableEntityMixin, PublishableEntityVersionMixin
@@ -240,8 +240,6 @@ class ComponentVersionContent(models.Model):
240
240
  component_version = models.ForeignKey(ComponentVersion, on_delete=models.CASCADE)
241
241
  content = models.ForeignKey(Content, on_delete=models.RESTRICT)
242
242
 
243
- uuid = immutable_uuid_field()
244
-
245
243
  # "key" is a reserved word for MySQL, so we're temporarily using the column
246
244
  # name of "_key" to avoid breaking downstream tooling. A possible
247
245
  # alternative name for this would be "path", since it's most often used as
@@ -89,6 +89,7 @@ __all__ = [
89
89
  "get_containers_with_entity",
90
90
  "get_container_children_count",
91
91
  "bulk_draft_changes_for",
92
+ "get_container_children_entities_keys",
92
93
  ]
93
94
 
94
95
 
@@ -1141,6 +1142,7 @@ def create_next_container_version(
1141
1142
  created_by: int | None,
1142
1143
  container_version_cls: type[ContainerVersionModel] = ContainerVersion, # type: ignore[assignment]
1143
1144
  entities_action: ChildrenEntitiesAction = ChildrenEntitiesAction.REPLACE,
1145
+ force_version_num: int | None = None,
1144
1146
  ) -> ContainerVersionModel:
1145
1147
  """
1146
1148
  [ 🛑 UNSTABLE ]
@@ -1161,26 +1163,40 @@ def create_next_container_version(
1161
1163
  created: The date and time the container version was created.
1162
1164
  created_by: The ID of the user who created the container version.
1163
1165
  container_version_cls: The subclass of ContainerVersion to use, if applicable.
1166
+ force_version_num (int, optional): If provided, overrides the automatic version number increment and sets
1167
+ this version's number explicitly. Use this if you need to restore or import a version with a specific
1168
+ version number, such as during data migration or when synchronizing with external systems.
1164
1169
 
1165
1170
  Returns:
1166
1171
  The newly created container version.
1172
+
1173
+ Why use force_version_num?
1174
+ Normally, the version number is incremented automatically from the latest version.
1175
+ If you need to set a specific version number (for example, when restoring from backup,
1176
+ importing legacy data, or synchronizing with another system),
1177
+ use force_version_num to override the default behavior.
1167
1178
  """
1168
1179
  assert issubclass(container_version_cls, ContainerVersion)
1169
1180
  with atomic():
1170
1181
  container = Container.objects.select_related("publishable_entity").get(pk=container_pk)
1171
1182
  entity = container.publishable_entity
1172
1183
  last_version = container.versioning.latest
1173
- assert last_version is not None
1174
- next_version_num = last_version.version_num + 1
1184
+ if last_version is None:
1185
+ next_version_num = 1
1186
+ else:
1187
+ next_version_num = last_version.version_num + 1
1188
+
1189
+ if force_version_num is not None:
1190
+ next_version_num = force_version_num
1175
1191
 
1176
- if entity_rows is None:
1192
+ if entity_rows is None and last_version is not None:
1177
1193
  # We're only changing metadata. Keep the same entity list.
1178
1194
  next_entity_list = last_version.entity_list
1179
1195
  else:
1180
1196
  next_entity_list = create_next_entity_list(
1181
1197
  entity.learning_package_id,
1182
1198
  last_version,
1183
- entity_rows,
1199
+ entity_rows if entity_rows is not None else [],
1184
1200
  entities_action
1185
1201
  )
1186
1202
 
@@ -1468,6 +1484,22 @@ def get_container_children_count(
1468
1484
  return container_version.entity_list.entitylistrow_set.filter(**filter_deleted).count()
1469
1485
 
1470
1486
 
1487
+ def get_container_children_entities_keys(container_version: ContainerVersion) -> list[str]:
1488
+ """
1489
+ Fetch the list of entity keys for all entities in the given container version.
1490
+
1491
+ Args:
1492
+ container_version: The ContainerVersion to fetch the entity keys for.
1493
+ Returns:
1494
+ A list of entity keys for all entities in the container version, ordered by entity key.
1495
+ """
1496
+ return list(
1497
+ container_version.entity_list.entitylistrow_set
1498
+ .values_list("entity__key", flat=True)
1499
+ .order_by("order_num")
1500
+ )
1501
+
1502
+
1471
1503
  def bulk_draft_changes_for(
1472
1504
  learning_package_id: int,
1473
1505
  changed_by: int | None = None,
@@ -131,6 +131,7 @@ def create_next_section_version(
131
131
  created: datetime,
132
132
  created_by: int | None = None,
133
133
  entities_action: publishing_api.ChildrenEntitiesAction = publishing_api.ChildrenEntitiesAction.REPLACE,
134
+ force_version_num: int | None = None,
134
135
  ) -> SectionVersion:
135
136
  """
136
137
  [ 🛑 UNSTABLE ] Create the next section version.
@@ -142,6 +143,21 @@ def create_next_section_version(
142
143
  Passing None will leave the existing subsections unchanged.
143
144
  created: The creation date.
144
145
  created_by: The user who created the section.
146
+ force_version_num (int, optional): If provided, overrides the automatic version number increment and sets
147
+ this version's number explicitly. Use this if you need to restore or import a version with a specific
148
+ version number, such as during data migration or when synchronizing with external systems.
149
+
150
+ Returns:
151
+ The newly created SectionVersion.
152
+
153
+ Why use force_version_num?
154
+ Normally, the version number is incremented automatically from the latest version.
155
+ If you need to set a specific version number (for example, when restoring from backup,
156
+ importing legacy data, or synchronizing with another system),
157
+ use force_version_num to override the default behavior.
158
+
159
+ Why not use create_component_version?
160
+ The main reason is that we want to reuse the logic for adding entities to this container.
145
161
  """
146
162
  entity_rows = _pub_entities_for_subsections(subsections)
147
163
  section_version = publishing_api.create_next_container_version(
@@ -152,6 +168,7 @@ def create_next_section_version(
152
168
  created_by=created_by,
153
169
  container_version_cls=SectionVersion,
154
170
  entities_action=entities_action,
171
+ force_version_num=force_version_num,
155
172
  )
156
173
  return section_version
157
174
 
@@ -130,6 +130,7 @@ def create_next_subsection_version(
130
130
  created: datetime,
131
131
  created_by: int | None = None,
132
132
  entities_action: publishing_api.ChildrenEntitiesAction = publishing_api.ChildrenEntitiesAction.REPLACE,
133
+ force_version_num: int | None = None,
133
134
  ) -> SubsectionVersion:
134
135
  """
135
136
  [ 🛑 UNSTABLE ] Create the next subsection version.
@@ -141,6 +142,21 @@ def create_next_subsection_version(
141
142
  will leave the existing units unchanged.
142
143
  created: The creation date.
143
144
  created_by: The user who created the subsection.
145
+ force_version_num (int, optional): If provided, overrides the automatic version number increment and sets
146
+ this version's number explicitly. Use this if you need to restore or import a version with a specific
147
+ version number, such as during data migration or when synchronizing with external systems.
148
+
149
+ Returns:
150
+ The newly created subsection version.
151
+
152
+ Why use force_version_num?
153
+ Normally, the version number is incremented automatically from the latest version.
154
+ If you need to set a specific version number (for example, when restoring from backup,
155
+ importing legacy data, or synchronizing with another system),
156
+ use force_version_num to override the default behavior.
157
+
158
+ Why not use create_component_version?
159
+ The main reason is that we want to reuse the logic for adding entities to this container.
144
160
  """
145
161
  entity_rows = _pub_entities_for_units(units)
146
162
  subsection_version = publishing_api.create_next_container_version(
@@ -151,6 +167,7 @@ def create_next_subsection_version(
151
167
  created_by=created_by,
152
168
  container_version_cls=SubsectionVersion,
153
169
  entities_action=entities_action,
170
+ force_version_num=force_version_num,
154
171
  )
155
172
  return subsection_version
156
173
 
@@ -79,6 +79,21 @@ def create_unit_version(
79
79
  entity_rows: child entities/versions
80
80
  created: The creation date.
81
81
  created_by: The user who created the unit.
82
+ force_version_num (int, optional): If provided, overrides the automatic version number increment and sets
83
+ this version's number explicitly. Use this if you need to restore or import a version with a specific
84
+ version number, such as during data migration or when synchronizing with external systems.
85
+
86
+ Returns:
87
+ UnitVersion: The newly created UnitVersion instance.
88
+
89
+ Why use force_version_num?
90
+ Normally, the version number is incremented automatically from the latest version.
91
+ If you need to set a specific version number (for example, when restoring from backup,
92
+ importing legacy data, or synchronizing with another system),
93
+ use force_version_num to override the default behavior.
94
+
95
+ Why not use create_component_version?
96
+ The main reason is that we want to reuse the logic for adding entities to this container.
82
97
  """
83
98
  return publishing_api.create_container_version(
84
99
  unit.pk,
@@ -131,6 +146,7 @@ def create_next_unit_version(
131
146
  created: datetime,
132
147
  created_by: int | None = None,
133
148
  entities_action: publishing_api.ChildrenEntitiesAction = publishing_api.ChildrenEntitiesAction.REPLACE,
149
+ force_version_num: int | None = None,
134
150
  ) -> UnitVersion:
135
151
  """
136
152
  [ 🛑 UNSTABLE ] Create the next unit version.
@@ -152,6 +168,7 @@ def create_next_unit_version(
152
168
  created_by=created_by,
153
169
  container_version_cls=UnitVersion,
154
170
  entities_action=entities_action,
171
+ force_version_num=force_version_num,
155
172
  )
156
173
  return unit_version
157
174
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openedx-learning
3
- Version: 0.28.0
3
+ Version: 0.29.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
@@ -19,13 +19,13 @@ Classifier: Programming Language :: Python :: 3.11
19
19
  Classifier: Programming Language :: Python :: 3.12
20
20
  Requires-Python: >=3.11
21
21
  License-File: LICENSE.txt
22
- Requires-Dist: celery
23
22
  Requires-Dist: rules<4.0
24
- Requires-Dist: Django
25
23
  Requires-Dist: edx-drf-extensions
24
+ Requires-Dist: Django
26
25
  Requires-Dist: tomlkit
27
- Requires-Dist: attrs
28
26
  Requires-Dist: djangorestframework<4.0
27
+ Requires-Dist: attrs
28
+ Requires-Dist: celery
29
29
  Dynamic: author
30
30
  Dynamic: author-email
31
31
  Dynamic: classifier
@@ -148,6 +148,15 @@ Every time you develop something in this repo
148
148
 
149
149
  # Open a PR and ask for review.
150
150
 
151
+ Configuring Visual Studio Code
152
+ ------------------------------
153
+
154
+ If you are using VS Code as your editor, you can enable the Testing bar by copying from the example configuration provided in the ``.vscode`` directory::
155
+
156
+ cd .vscode/
157
+ cp launch.json.example launch.json
158
+ cp settings.json.example settings.json
159
+
151
160
  License
152
161
  -------
153
162
 
@@ -1,4 +1,4 @@
1
- openedx_learning/__init__.py,sha256=uNMwU0K5IBvKkVUxhRpjU7JmSuDVu7oR_QPr65ElCPE,69
1
+ openedx_learning/__init__.py,sha256=Z6oFsZrI2xFIXJ8CCTDE4l0MUEFWwTjOpo6C06gHz0U,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
4
  openedx_learning/api/authoring.py,sha256=EDWTY_JDKtjD9nFrrijzWuVccs3LZeDLEdzTUNanR4I,1111
@@ -7,14 +7,16 @@ openedx_learning/apps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3h
7
7
  openedx_learning/apps/authoring/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  openedx_learning/apps/authoring/backup_restore/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
9
  openedx_learning/apps/authoring/backup_restore/admin.py,sha256=OnEixkOuysPRr-F6C_CMwPkiXawkqgSEF46n3yiUK0o,59
10
- openedx_learning/apps/authoring/backup_restore/api.py,sha256=zEns3crvfFEFFh7MmwzSqW0WuGmZaSgdmujzl0PnfvU,508
10
+ openedx_learning/apps/authoring/backup_restore/api.py,sha256=TlQmb9VADDj8UlNjOczlwOfY8JVFfGyZ3UN5nluL9Tw,1243
11
11
  openedx_learning/apps/authoring/backup_restore/apps.py,sha256=UnExBA7jhd3qI30_87JMvzVhS_k82t89qDVKSMpvg_A,340
12
12
  openedx_learning/apps/authoring/backup_restore/models.py,sha256=jlr0ppxW0IOW3HPHoJNChHvDrYVnKMb5_3uC2itxqQk,45
13
- openedx_learning/apps/authoring/backup_restore/toml.py,sha256=8D103aAfCtzVPV4U-yuB-HsXvNCO3cs3xhvMJTI4IXo,2955
14
- openedx_learning/apps/authoring/backup_restore/zipper.py,sha256=_SWgnfLHziFLWF-iXf_38L8v6C-yO8PjyZHxDCtaJ0k,11494
13
+ openedx_learning/apps/authoring/backup_restore/serializers.py,sha256=LBWrlWmA0Aok-XSfwiGBqNTU-1ig4hnEX_AxFaAjaoY,6487
14
+ openedx_learning/apps/authoring/backup_restore/toml.py,sha256=4QFKDoFXsEF3xr1gvRXKQien-4_GezH6Gn1BdYQF8yU,8286
15
+ openedx_learning/apps/authoring/backup_restore/zipper.py,sha256=S3pxdsKmt-KeTaJlJBzZg0qKOmUOvhkSKP12N6dGguU,47171
15
16
  openedx_learning/apps/authoring/backup_restore/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
17
  openedx_learning/apps/authoring/backup_restore/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
- openedx_learning/apps/authoring/backup_restore/management/commands/lp_dump.py,sha256=TkbyBf9Jsa7yoXiGEduO0ZqKTYO7vWGHbqr5NbEclRs,1696
18
+ openedx_learning/apps/authoring/backup_restore/management/commands/lp_dump.py,sha256=6-8D1rnDajTAIJu1HZLayY87hTfMnkaumYgSmLWpiGE,2302
19
+ openedx_learning/apps/authoring/backup_restore/management/commands/lp_load.py,sha256=z-glaYCulllcptqFbX5cb0UL_b9NslaaHK-0kOmPLa4,2205
18
20
  openedx_learning/apps/authoring/backup_restore/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
21
  openedx_learning/apps/authoring/collections/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
22
  openedx_learning/apps/authoring/collections/admin.py,sha256=f0hySjDMfIphVDEGkCSMIUHoEiqHRA7EE2NiO7lvL4g,1156
@@ -29,15 +31,16 @@ openedx_learning/apps/authoring/collections/migrations/0005_alter_collection_opt
29
31
  openedx_learning/apps/authoring/collections/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
32
  openedx_learning/apps/authoring/components/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
33
  openedx_learning/apps/authoring/components/admin.py,sha256=zfEpuBEySMYpUZzygaE2MDoI8SH-2H3xIL20YCSCMLo,4582
32
- openedx_learning/apps/authoring/components/api.py,sha256=nJZcGXN5gOz8EYX3XHCiDILowNRcm__RxTfTNt5rZZw,22780
34
+ openedx_learning/apps/authoring/components/api.py,sha256=fnc7PaxaQFsTkJHfQMsxu62V4wHGXt4I5fjJHT9xDsU,25386
33
35
  openedx_learning/apps/authoring/components/apps.py,sha256=hi1SF2Z8Ex0hgE82wJK5Z_vYYfbcRhtaUW1zWZCdJYI,786
34
- openedx_learning/apps/authoring/components/models.py,sha256=ttZzVnMZTa14-qudrLb4CFuCanEQJT8cuC_iVPH8XTA,10887
36
+ openedx_learning/apps/authoring/components/models.py,sha256=j0AXjCDSBqqBlhP_06aGEuLFiJkhsolA0FdA41NbCmg,10830
35
37
  openedx_learning/apps/authoring/components/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
36
38
  openedx_learning/apps/authoring/components/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
39
  openedx_learning/apps/authoring/components/management/commands/add_assets_to_component.py,sha256=0dJ77NZZoNzYheOdFPXtJrjdL_Z-pCNg3l1rbEGnMCY,3175
38
40
  openedx_learning/apps/authoring/components/migrations/0001_initial.py,sha256=446LkJSFeK8J_-l-bxakZ_BVx_CiJIllGcBYqWcEenA,4664
39
41
  openedx_learning/apps/authoring/components/migrations/0002_alter_componentversioncontent_key.py,sha256=98724dtucRjJCRyLt5p45qXYb2d6-ouVGp7PB6zTG6E,539
40
42
  openedx_learning/apps/authoring/components/migrations/0003_remove_componentversioncontent_learner_downloadable.py,sha256=hDAkKdBvKULepML9pVMqkZg31nAyCeszQHJsFJ4qGws,382
43
+ openedx_learning/apps/authoring/components/migrations/0004_remove_componentversioncontent_uuid.py,sha256=hmCUt9VkiisGPckXjVY_qDNNkyj3r6KMK-MpasgOf6w,384
41
44
  openedx_learning/apps/authoring/components/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
42
45
  openedx_learning/apps/authoring/contents/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
43
46
  openedx_learning/apps/authoring/contents/admin.py,sha256=9Njd_lje1emcd168KBWUTGf0mVJ6K-dMYMcqHNjRU4k,1761
@@ -48,7 +51,7 @@ openedx_learning/apps/authoring/contents/migrations/0001_initial.py,sha256=FtOTm
48
51
  openedx_learning/apps/authoring/contents/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
49
52
  openedx_learning/apps/authoring/publishing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
50
53
  openedx_learning/apps/authoring/publishing/admin.py,sha256=nvAAl3Xswqqq3WyaI1NT7pLCcu1o-ynciJZOlc-9L24,16244
51
- openedx_learning/apps/authoring/publishing/api.py,sha256=yNHqupNDY4FPNmQYnwMr1Sey_t85CjrpYpNDFh9nfF8,58162
54
+ openedx_learning/apps/authoring/publishing/api.py,sha256=zTG88gVIWfw-oCxC1SLl64OXbA_V_Cd3KDwAAoJWcZM,59679
52
55
  openedx_learning/apps/authoring/publishing/apps.py,sha256=PXYIx-TwN7a8dDudodX80Z7hNV9bWzrMZnpDET8lCGE,758
53
56
  openedx_learning/apps/authoring/publishing/contextmanagers.py,sha256=AH5zhr0Tz_gUG9--dfr_oZAu8DMy94n6mnOJuPbWkeU,6723
54
57
  openedx_learning/apps/authoring/publishing/migrations/0001_initial.py,sha256=wvekNV19YRSdxRmQaFnLSn_nCsQlHIucPDVMmgKf_OE,9272
@@ -69,21 +72,21 @@ openedx_learning/apps/authoring/publishing/models/publish_log.py,sha256=QD7Fb00y
69
72
  openedx_learning/apps/authoring/publishing/models/publishable_entity.py,sha256=ErzsvCcYbvjnMvsPqErOgSob9Vpaa7nmykNPTSQkZk8,25894
70
73
  openedx_learning/apps/authoring/sections/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
71
74
  openedx_learning/apps/authoring/sections/admin.py,sha256=OQOTtXYM-Zj8BBb1wNBkOxgkF2Pv3JdUrZ45VOEmThM,1757
72
- openedx_learning/apps/authoring/sections/api.py,sha256=MFAHxDqUqbXygY_L37Lo4uXrju0m4Kf9ozk4yZnDs6Y,10438
75
+ openedx_learning/apps/authoring/sections/api.py,sha256=DXFHCjR8hauJK0Cmwxtk-PQLQpoVPpna-USOYNeRenI,11405
73
76
  openedx_learning/apps/authoring/sections/apps.py,sha256=vbLhC3WIKmG1MD0mvxX01IjoF6xPiJoutJar-h_bH7Y,746
74
77
  openedx_learning/apps/authoring/sections/models.py,sha256=2GK-dDMJwNRw_9gNFho8iKcDV-iYz_zBzqGMDmQ_jbc,1450
75
78
  openedx_learning/apps/authoring/sections/migrations/0001_initial.py,sha256=iW5AFhC26mfZNWEVNu8cTsr32Ca4htL4CUanHoXfaeY,1152
76
79
  openedx_learning/apps/authoring/sections/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
77
80
  openedx_learning/apps/authoring/subsections/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
78
81
  openedx_learning/apps/authoring/subsections/admin.py,sha256=vPfOeTzh10aRhtZjXAzYsbwfw4Hc5yuySbpjAvtDH98,1795
79
- openedx_learning/apps/authoring/subsections/api.py,sha256=cJHPZ2JbAL3aXa1rhV6R5_oIP_VmWWDCi9L-2qjifwo,10338
82
+ openedx_learning/apps/authoring/subsections/api.py,sha256=aoGLjSbf9cUcAFbPvENThKhbJvZlAuzYP-P6Q0G5duw,11309
80
83
  openedx_learning/apps/authoring/subsections/apps.py,sha256=awpHVtg6bwIF1sEMeVcaGMwvrVzckfEHOFA9eFt5900,781
81
84
  openedx_learning/apps/authoring/subsections/models.py,sha256=1uhdpS9Eg6keSqkzQaE8-XSVLAQlmi0llIIU2V7Nl44,1492
82
85
  openedx_learning/apps/authoring/subsections/migrations/0001_initial.py,sha256=7kEHIC-EwG2KvlW4hg5tnl45--dW4Yv5gqV5SDqNYNo,1158
83
86
  openedx_learning/apps/authoring/subsections/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
84
87
  openedx_learning/apps/authoring/units/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
85
88
  openedx_learning/apps/authoring/units/admin.py,sha256=chp-bTfufBiQ3uycVF1DBEFSPvwXaROJnyyY8AaH_yw,1717
86
- openedx_learning/apps/authoring/units/api.py,sha256=vmNdXwI4n-ksDu_nD3b-U0ZyQa0mGaJ3cuaj882HU2s,9863
89
+ openedx_learning/apps/authoring/units/api.py,sha256=ERJhwkpcIKNOZogZ2R3-T8mv3ud9oQNgloQRv-7ld-k,10849
87
90
  openedx_learning/apps/authoring/units/apps.py,sha256=AlKOUoC5zPrRrEedLvGzMf31ujWyhcaaoCNS4LI-u50,709
88
91
  openedx_learning/apps/authoring/units/models.py,sha256=eTOwFWC9coQLf0ovx08Mj7zi8mPAWCw9QOznybajRk0,1418
89
92
  openedx_learning/apps/authoring/units/migrations/0001_initial.py,sha256=qM_0JGffxECVgXzncHXfgSE-g8u3L3a14R0M1Bnj_Ys,1129
@@ -101,7 +104,7 @@ openedx_learning/lib/fields.py,sha256=eiGoXMPhRuq25EH2qf6BAODshAQE3DBVdIYAMIUAXW
101
104
  openedx_learning/lib/managers.py,sha256=-Q3gxalSqyPZ9Im4DTROW5tF8wVTZLlmfTe62_xmowY,1643
102
105
  openedx_learning/lib/test_utils.py,sha256=g3KLuepIZbaDBCsaj9711YuqyUx7LD4gXDcfNC-mWdc,527
103
106
  openedx_learning/lib/validators.py,sha256=iqEdEAvFV2tC7Ecssx69kjecpdU8nE87AlDJYrqrsnc,404
104
- openedx_learning-0.28.0.dist-info/licenses/LICENSE.txt,sha256=QTW2QN7q3XszgUAXm9Dzgtu5LXYKbR1SGnqMa7ufEuY,35139
107
+ openedx_learning-0.29.1.dist-info/licenses/LICENSE.txt,sha256=QTW2QN7q3XszgUAXm9Dzgtu5LXYKbR1SGnqMa7ufEuY,35139
105
108
  openedx_tagging/__init__.py,sha256=V9N8M7f9LYlAbA_DdPUsHzTnWjYRXKGa5qHw9P1JnNI,30
106
109
  openedx_tagging/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
107
110
  openedx_tagging/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -143,10 +146,10 @@ openedx_tagging/core/tagging/migrations/0017_alter_tagimporttask_status.py,sha25
143
146
  openedx_tagging/core/tagging/migrations/0018_objecttag_is_copied.py,sha256=zmr4b65T0vX6fYc8MpvSmQnYkAiNMpx3RKEd5tudsl8,517
144
147
  openedx_tagging/core/tagging/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
145
148
  openedx_tagging/core/tagging/models/__init__.py,sha256=yYdOnthuc7EUdfEULtZgqRwn5Y4bbYQmJCjVZqR5GTM,236
146
- openedx_tagging/core/tagging/models/base.py,sha256=ju4mvgRS_I2AgPsRf4sMFy6qle2i0aA0MbyBYZXf32g,39685
149
+ openedx_tagging/core/tagging/models/base.py,sha256=V2Qyp4L2QhTs6lZwB05_H54W6yWvyIEHhTcAGJAq-BY,39845
147
150
  openedx_tagging/core/tagging/models/import_export.py,sha256=Aj0pleh0nh2LNS6zmdB1P4bpdgUMmvmobTkqBerORAI,4570
148
151
  openedx_tagging/core/tagging/models/system_defined.py,sha256=_6LfvUZGEltvQMtm2OXy6TOLh3C8GnVTqtZDSAZW6K4,9062
149
- openedx_tagging/core/tagging/models/utils.py,sha256=x2dg3IYE3RVOZtInkwKtznHcByDmc2R-sBMVWksHnjs,2917
152
+ openedx_tagging/core/tagging/models/utils.py,sha256=VhNL2cUTbCuxhsEsLXEDcgdV2tK2amUwVdWxmeZDr2w,2925
150
153
  openedx_tagging/core/tagging/rest_api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
151
154
  openedx_tagging/core/tagging/rest_api/paginators.py,sha256=BUIAg3taihHx7uAjpTZAGK1xSZzZY9G0aib4OKv5c0k,2651
152
155
  openedx_tagging/core/tagging/rest_api/urls.py,sha256=egXaRQv1EAgF04ThgVZBQuvLK1LimuyUKKBD2Hbqb10,148
@@ -157,7 +160,7 @@ openedx_tagging/core/tagging/rest_api/v1/serializers.py,sha256=0HQD_Jrf6-YpocYfz
157
160
  openedx_tagging/core/tagging/rest_api/v1/urls.py,sha256=dNUKCtUCx_YzrwlbEbpDfjGVQbb2QdJ1VuJCkladj6E,752
158
161
  openedx_tagging/core/tagging/rest_api/v1/views.py,sha256=Hf92cy-tE767DE9FgsZcPKiCYrf5ihfETz8qGKBnuiU,36278
159
162
  openedx_tagging/core/tagging/rest_api/v1/views_import.py,sha256=kbHUPe5A6WaaJ3J1lFIcYCt876ecLNQfd19m7YYub6c,1470
160
- openedx_learning-0.28.0.dist-info/METADATA,sha256=2B4GnwRZKKZfm9RmEkeJITaAaGGHi4lfvnhPkW8a5Jk,9055
161
- openedx_learning-0.28.0.dist-info/WHEEL,sha256=JNWh1Fm1UdwIQV075glCn4MVuCRs0sotJIq-J6rbxCU,109
162
- openedx_learning-0.28.0.dist-info/top_level.txt,sha256=IYFbr5mgiEHd-LOtZmXj3q3a0bkGK1M9LY7GXgnfi4M,33
163
- openedx_learning-0.28.0.dist-info/RECORD,,
163
+ openedx_learning-0.29.1.dist-info/METADATA,sha256=T9KZOwk1jrSCv47Qy-9TsC4lFxjVcKg3oNAX0K2Vz7w,9372
164
+ openedx_learning-0.29.1.dist-info/WHEEL,sha256=JNWh1Fm1UdwIQV075glCn4MVuCRs0sotJIq-J6rbxCU,109
165
+ openedx_learning-0.29.1.dist-info/top_level.txt,sha256=IYFbr5mgiEHd-LOtZmXj3q3a0bkGK1M9LY7GXgnfi4M,33
166
+ openedx_learning-0.29.1.dist-info/RECORD,,
@@ -465,7 +465,7 @@ class Taxonomy(models.Model):
465
465
  if include_counts:
466
466
  return qs.annotate(usage_count=models.Count("value"))
467
467
  else:
468
- return qs.distinct()
468
+ return qs.distinct() # type: ignore[return-value]
469
469
 
470
470
  def _get_filtered_tags_one_level(
471
471
  self,
@@ -486,12 +486,14 @@ class Taxonomy(models.Model):
486
486
  # Use parent_tag.value not parent_tag_value because they may differ in case
487
487
  qs = qs.annotate(parent_value=Value(parent_tag.value))
488
488
  else:
489
- qs = self.tag_set.filter(parent=None).annotate(depth=Value(0))
489
+ qs = self.tag_set.filter(parent=None).annotate(depth=Value(0)) # type: ignore[no-redef]
490
490
  qs = qs.annotate(parent_value=Value(None, output_field=models.CharField()))
491
- qs = qs.annotate(child_count=models.Count("children", distinct=True))
491
+ qs = qs.annotate(child_count=models.Count("children", distinct=True)) # type: ignore[no-redef]
492
492
  qs = qs.annotate(grandchild_count=models.Count("children__children", distinct=True))
493
493
  qs = qs.annotate(great_grandchild_count=models.Count("children__children__children"))
494
- qs = qs.annotate(descendant_count=F("child_count") + F("grandchild_count") + F("great_grandchild_count"))
494
+ qs = qs.annotate(
495
+ descendant_count=F("child_count") + F("grandchild_count") + F("great_grandchild_count")
496
+ ) # type: ignore[no-redef]
495
497
  # Filter by search term:
496
498
  if search_term:
497
499
  qs = qs.filter(value__icontains=search_term)
@@ -597,7 +599,7 @@ class Taxonomy(models.Model):
597
599
  count=models.Func(F('id'), function='Count')
598
600
  )
599
601
  qs = qs.annotate(usage_count=models.Subquery(obj_tags.values('count')))
600
- return qs
602
+ return qs # type: ignore[return-value]
601
603
 
602
604
  def add_tag(
603
605
  self,
@@ -59,7 +59,7 @@ class StringAgg(Aggregate, Combinable):
59
59
  # Check the database backend (PostgreSQL, MySQL, or SQLite)
60
60
  if 'postgresql' in db_connection.vendor.lower():
61
61
  self.function = 'STRING_AGG'
62
- self.template = '%(function)s(%(distinct)s%(expressions)s, %(delimiter)s)'
62
+ self.template = "%(function)s(%(distinct)s%(expressions)s::TEXT, '%(delimiter)s')"
63
63
  extra.update({
64
64
  "delimiter": self.delimiter,
65
65
  "output_field": TextField(),