openedx-learning 0.5.0__tar.gz → 0.5.1__tar.gz
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-0.5.0/openedx_learning.egg-info → openedx-learning-0.5.1}/PKG-INFO +1 -1
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/__init__.py +1 -1
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/core/components/admin.py +3 -4
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/core/components/api.py +30 -18
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/core/components/migrations/0001_initial.py +22 -7
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/core/components/models.py +62 -30
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/core/contents/api.py +4 -5
- {openedx-learning-0.5.0 → openedx-learning-0.5.1/openedx_learning.egg-info}/PKG-INFO +1 -1
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning.egg-info/SOURCES.txt +1 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/api.py +8 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/import_export/api.py +1 -1
- openedx-learning-0.5.1/openedx_tagging/core/tagging/migrations/0015_taxonomy_export_id.py +38 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/models/base.py +23 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/rest_api/v1/serializers.py +3 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/rest_api/v1/views.py +19 -2
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/CHANGELOG.rst +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/LICENSE.txt +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/MANIFEST.in +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/README.rst +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/contrib/__init__.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/contrib/media_server/__init__.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/contrib/media_server/apps.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/contrib/media_server/urls.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/contrib/media_server/views.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/core/__init__.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/core/components/__init__.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/core/components/apps.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/core/components/migrations/__init__.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/core/contents/__init__.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/core/contents/admin.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/core/contents/apps.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/core/contents/migrations/0001_initial.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/core/contents/migrations/__init__.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/core/contents/models.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/core/publishing/__init__.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/core/publishing/admin.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/core/publishing/api.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/core/publishing/apps.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/core/publishing/migrations/0001_initial.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/core/publishing/migrations/__init__.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/core/publishing/model_mixins.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/core/publishing/models.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/lib/__init__.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/lib/admin_utils.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/lib/cache.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/lib/collations.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/lib/fields.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/lib/managers.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/lib/test_utils.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/lib/validators.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/rest_api/__init__.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/rest_api/apps.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/rest_api/urls.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/rest_api/v1/__init__.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/rest_api/v1/components.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/rest_api/v1/urls.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning.egg-info/dependency_links.txt +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning.egg-info/not-zip-safe +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning.egg-info/requires.txt +3 -3
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning.egg-info/top_level.txt +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/__init__.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/__init__.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/__init__.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/admin.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/apps.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/data.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/import_export/__init__.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/import_export/actions.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/import_export/exceptions.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/import_export/import_plan.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/import_export/parsers.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/import_export/tasks.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/import_export/template.csv +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/import_export/template.json +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/migrations/0001_initial.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/migrations/0001_squashed.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/migrations/0002_auto_20230718_2026.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/migrations/0003_auto_20230721_1238.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/migrations/0004_auto_20230723_2001.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/migrations/0005_language_taxonomy.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/migrations/0006_alter_objecttag_unique_together.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/migrations/0006_auto_20230802_1631.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/migrations/0007_tag_import_task_log_null_fix.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/migrations/0008_taxonomy_description_not_null.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/migrations/0009_alter_objecttag_object_id.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/migrations/0010_cleanups.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/migrations/0011_remove_required.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/migrations/0012_language_taxonomy.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/migrations/0013_tag_parent_blank.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/migrations/0014_minor_fixes.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/migrations/__init__.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/models/__init__.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/models/import_export.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/models/system_defined.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/models/utils.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/rest_api/__init__.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/rest_api/paginators.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/rest_api/urls.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/rest_api/utils.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/rest_api/v1/__init__.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/rest_api/v1/permissions.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/rest_api/v1/urls.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/rest_api/v1/views_import.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/rules.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/urls.py +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/requirements/base.in +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/setup.cfg +0 -0
- {openedx-learning-0.5.0 → openedx-learning-0.5.1}/setup.py +0 -0
|
@@ -35,16 +35,15 @@ class ComponentAdmin(ReadOnlyModelAdmin):
|
|
|
35
35
|
"""
|
|
36
36
|
Django admin configuration for Component
|
|
37
37
|
"""
|
|
38
|
-
list_display = ("key", "uuid", "
|
|
38
|
+
list_display = ("key", "uuid", "component_type", "created")
|
|
39
39
|
readonly_fields = [
|
|
40
40
|
"learning_package",
|
|
41
41
|
"uuid",
|
|
42
|
-
"
|
|
43
|
-
"type",
|
|
42
|
+
"component_type",
|
|
44
43
|
"key",
|
|
45
44
|
"created",
|
|
46
45
|
]
|
|
47
|
-
list_filter = ("
|
|
46
|
+
list_filter = ("component_type", "learning_package")
|
|
48
47
|
search_fields = ["publishable_entity__uuid", "publishable_entity__key"]
|
|
49
48
|
inlines = [ComponentVersionInline]
|
|
50
49
|
|
|
@@ -18,15 +18,28 @@ from pathlib import Path
|
|
|
18
18
|
from django.db.models import Q, QuerySet
|
|
19
19
|
from django.db.transaction import atomic
|
|
20
20
|
|
|
21
|
+
from ...lib.cache import lru_cache
|
|
21
22
|
from ..publishing import api as publishing_api
|
|
22
|
-
from .models import Component, ComponentVersion, ComponentVersionRawContent
|
|
23
|
+
from .models import Component, ComponentType, ComponentVersion, ComponentVersionRawContent
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@lru_cache(maxsize=128)
|
|
27
|
+
def get_or_create_component_type_id(namespace: str, name: str) -> int:
|
|
28
|
+
"""
|
|
29
|
+
Get the ID of a ComponentType, and create if missing.
|
|
30
|
+
"""
|
|
31
|
+
component_type, _created = ComponentType.objects.get_or_create(
|
|
32
|
+
namespace=namespace,
|
|
33
|
+
name=name,
|
|
34
|
+
)
|
|
35
|
+
return component_type.id
|
|
23
36
|
|
|
24
37
|
|
|
25
38
|
def create_component(
|
|
26
39
|
learning_package_id: int,
|
|
27
40
|
/,
|
|
28
41
|
namespace: str,
|
|
29
|
-
|
|
42
|
+
type_name: str,
|
|
30
43
|
local_key: str,
|
|
31
44
|
created: datetime,
|
|
32
45
|
created_by: int | None,
|
|
@@ -34,7 +47,7 @@ def create_component(
|
|
|
34
47
|
"""
|
|
35
48
|
Create a new Component (an entity like a Problem or Video)
|
|
36
49
|
"""
|
|
37
|
-
key = f"{namespace}:{
|
|
50
|
+
key = f"{namespace}:{type_name}@{local_key}"
|
|
38
51
|
with atomic():
|
|
39
52
|
publishable_entity = publishing_api.create_publishable_entity(
|
|
40
53
|
learning_package_id, key, created, created_by
|
|
@@ -42,8 +55,7 @@ def create_component(
|
|
|
42
55
|
component = Component.objects.create(
|
|
43
56
|
publishable_entity=publishable_entity,
|
|
44
57
|
learning_package_id=learning_package_id,
|
|
45
|
-
|
|
46
|
-
type=type,
|
|
58
|
+
component_type_id=get_or_create_component_type_id(namespace, type_name),
|
|
47
59
|
local_key=local_key,
|
|
48
60
|
)
|
|
49
61
|
return component
|
|
@@ -163,7 +175,7 @@ def create_component_and_version(
|
|
|
163
175
|
learning_package_id: int,
|
|
164
176
|
/,
|
|
165
177
|
namespace: str,
|
|
166
|
-
|
|
178
|
+
type_name: str,
|
|
167
179
|
local_key: str,
|
|
168
180
|
title: str,
|
|
169
181
|
created: datetime,
|
|
@@ -174,7 +186,7 @@ def create_component_and_version(
|
|
|
174
186
|
"""
|
|
175
187
|
with atomic():
|
|
176
188
|
component = create_component(
|
|
177
|
-
learning_package_id, namespace,
|
|
189
|
+
learning_package_id, namespace, type_name, local_key, created, created_by
|
|
178
190
|
)
|
|
179
191
|
component_version = create_component_version(
|
|
180
192
|
component.pk,
|
|
@@ -199,7 +211,7 @@ def get_component_by_key(
|
|
|
199
211
|
learning_package_id: int,
|
|
200
212
|
/,
|
|
201
213
|
namespace: str,
|
|
202
|
-
|
|
214
|
+
type_name: str,
|
|
203
215
|
local_key: str,
|
|
204
216
|
) -> Component:
|
|
205
217
|
"""
|
|
@@ -208,8 +220,8 @@ def get_component_by_key(
|
|
|
208
220
|
return Component.with_publishing_relations \
|
|
209
221
|
.get(
|
|
210
222
|
learning_package_id=learning_package_id,
|
|
211
|
-
|
|
212
|
-
|
|
223
|
+
component_type__namespace=namespace,
|
|
224
|
+
component_type__name=type_name,
|
|
213
225
|
local_key=local_key,
|
|
214
226
|
)
|
|
215
227
|
|
|
@@ -218,7 +230,7 @@ def component_exists_by_key(
|
|
|
218
230
|
learning_package_id: int,
|
|
219
231
|
/,
|
|
220
232
|
namespace: str,
|
|
221
|
-
|
|
233
|
+
type_name: str,
|
|
222
234
|
local_key: str
|
|
223
235
|
) -> bool:
|
|
224
236
|
"""
|
|
@@ -228,10 +240,10 @@ def component_exists_by_key(
|
|
|
228
240
|
no current Draft version for it), or if it's been unpublished.
|
|
229
241
|
"""
|
|
230
242
|
try:
|
|
231
|
-
_component = Component.objects.only('pk').get(
|
|
243
|
+
_component = Component.objects.only('pk', 'component_type').get(
|
|
232
244
|
learning_package_id=learning_package_id,
|
|
233
|
-
|
|
234
|
-
|
|
245
|
+
component_type__namespace=namespace,
|
|
246
|
+
component_type__name=type_name,
|
|
235
247
|
local_key=local_key,
|
|
236
248
|
)
|
|
237
249
|
return True
|
|
@@ -245,7 +257,7 @@ def get_components(
|
|
|
245
257
|
draft: bool | None = None,
|
|
246
258
|
published: bool | None = None,
|
|
247
259
|
namespace: str | None = None,
|
|
248
|
-
|
|
260
|
+
type_names: list[str] | None = None,
|
|
249
261
|
draft_title: str | None = None,
|
|
250
262
|
published_title: str | None = None,
|
|
251
263
|
) -> QuerySet[Component]:
|
|
@@ -265,9 +277,9 @@ def get_components(
|
|
|
265
277
|
if published is not None:
|
|
266
278
|
qset = qset.filter(publishable_entity__published__version__isnull=not published)
|
|
267
279
|
if namespace is not None:
|
|
268
|
-
qset = qset.filter(
|
|
269
|
-
if
|
|
270
|
-
qset = qset.filter(
|
|
280
|
+
qset = qset.filter(component_type__namespace=namespace)
|
|
281
|
+
if type_names is not None:
|
|
282
|
+
qset = qset.filter(component_type__name__in=type_names)
|
|
271
283
|
if draft_title is not None:
|
|
272
284
|
qset = qset.filter(
|
|
273
285
|
publishable_entity__draft__version__title__icontains=draft_title
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Generated by Django 3.2.23 on 2024-01-
|
|
1
|
+
# Generated by Django 3.2.23 on 2024-01-31 05:34
|
|
2
2
|
|
|
3
3
|
import uuid
|
|
4
4
|
|
|
@@ -13,8 +13,8 @@ class Migration(migrations.Migration):
|
|
|
13
13
|
initial = True
|
|
14
14
|
|
|
15
15
|
dependencies = [
|
|
16
|
-
('oel_contents', '0001_initial'),
|
|
17
16
|
('oel_publishing', '0001_initial'),
|
|
17
|
+
('oel_contents', '0001_initial'),
|
|
18
18
|
]
|
|
19
19
|
|
|
20
20
|
operations = [
|
|
@@ -22,16 +22,21 @@ class Migration(migrations.Migration):
|
|
|
22
22
|
name='Component',
|
|
23
23
|
fields=[
|
|
24
24
|
('publishable_entity', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='oel_publishing.publishableentity')),
|
|
25
|
-
('namespace', openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'}, max_length=100)),
|
|
26
|
-
('type', openedx_learning.lib.fields.MultiCollationCharField(blank=True, db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'}, max_length=100)),
|
|
27
25
|
('local_key', openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'}, max_length=500)),
|
|
28
|
-
('learning_package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='oel_publishing.learningpackage')),
|
|
29
26
|
],
|
|
30
27
|
options={
|
|
31
28
|
'verbose_name': 'Component',
|
|
32
29
|
'verbose_name_plural': 'Components',
|
|
33
30
|
},
|
|
34
31
|
),
|
|
32
|
+
migrations.CreateModel(
|
|
33
|
+
name='ComponentType',
|
|
34
|
+
fields=[
|
|
35
|
+
('id', models.AutoField(primary_key=True, serialize=False)),
|
|
36
|
+
('namespace', openedx_learning.lib.fields.MultiCollationCharField(db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'}, max_length=100)),
|
|
37
|
+
('name', openedx_learning.lib.fields.MultiCollationCharField(blank=True, db_collations={'mysql': 'utf8mb4_bin', 'sqlite': 'BINARY'}, max_length=100)),
|
|
38
|
+
],
|
|
39
|
+
),
|
|
35
40
|
migrations.CreateModel(
|
|
36
41
|
name='ComponentVersion',
|
|
37
42
|
fields=[
|
|
@@ -59,6 +64,16 @@ class Migration(migrations.Migration):
|
|
|
59
64
|
name='raw_contents',
|
|
60
65
|
field=models.ManyToManyField(related_name='component_versions', through='oel_components.ComponentVersionRawContent', to='oel_contents.RawContent'),
|
|
61
66
|
),
|
|
67
|
+
migrations.AddField(
|
|
68
|
+
model_name='component',
|
|
69
|
+
name='component_type',
|
|
70
|
+
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='oel_components.componenttype'),
|
|
71
|
+
),
|
|
72
|
+
migrations.AddField(
|
|
73
|
+
model_name='component',
|
|
74
|
+
name='learning_package',
|
|
75
|
+
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='oel_publishing.learningpackage'),
|
|
76
|
+
),
|
|
62
77
|
migrations.AddIndex(
|
|
63
78
|
model_name='componentversionrawcontent',
|
|
64
79
|
index=models.Index(fields=['raw_content', 'component_version'], name='oel_cvrawcontent_c_cv'),
|
|
@@ -73,10 +88,10 @@ class Migration(migrations.Migration):
|
|
|
73
88
|
),
|
|
74
89
|
migrations.AddIndex(
|
|
75
90
|
model_name='component',
|
|
76
|
-
index=models.Index(fields=['
|
|
91
|
+
index=models.Index(fields=['component_type', 'local_key'], name='oel_component_idx_ct_lk'),
|
|
77
92
|
),
|
|
78
93
|
migrations.AddConstraint(
|
|
79
94
|
model_name='component',
|
|
80
|
-
constraint=models.UniqueConstraint(fields=('learning_package', '
|
|
95
|
+
constraint=models.UniqueConstraint(fields=('learning_package', 'component_type', 'local_key'), name='oel_component_uniq_lc_ct_lk'),
|
|
81
96
|
),
|
|
82
97
|
]
|
{openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/core/components/models.py
RENAMED
|
@@ -28,6 +28,45 @@ from ..publishing.model_mixins import PublishableEntityMixin, PublishableEntityV
|
|
|
28
28
|
from ..publishing.models import LearningPackage
|
|
29
29
|
|
|
30
30
|
|
|
31
|
+
class ComponentType(models.Model):
|
|
32
|
+
"""
|
|
33
|
+
Normalized representation of a type of Component.
|
|
34
|
+
|
|
35
|
+
The only namespace being used initially will be 'xblock.v1', but we will
|
|
36
|
+
probably add a few others over time, such as a component type to represent
|
|
37
|
+
packages of files for things like Files and Uploads or python_lib.zip files.
|
|
38
|
+
|
|
39
|
+
Make a ForeignKey against this table if you have to set policy based on the
|
|
40
|
+
type of Components–e.g. marking certain types of XBlocks as approved vs.
|
|
41
|
+
experimental for use in libraries.
|
|
42
|
+
"""
|
|
43
|
+
id = models.AutoField(primary_key=True)
|
|
44
|
+
|
|
45
|
+
# namespace and name work together to help figure out what Component needs
|
|
46
|
+
# to handle this data. A namespace is *required*. The namespace for XBlocks
|
|
47
|
+
# is "xblock.v1" (to match the setup.py entrypoint naming scheme).
|
|
48
|
+
namespace = case_sensitive_char_field(max_length=100, blank=False)
|
|
49
|
+
|
|
50
|
+
# name is a way to help sub-divide namespace if that's convenient. This
|
|
51
|
+
# field cannot be null, but it can be blank if it's not necessary. For an
|
|
52
|
+
# XBlock, this corresponds to tag, e.g. "video". It's also the block_type in
|
|
53
|
+
# the UsageKey.
|
|
54
|
+
name = case_sensitive_char_field(max_length=100, blank=True)
|
|
55
|
+
|
|
56
|
+
constraints = [
|
|
57
|
+
models.UniqueConstraint(
|
|
58
|
+
fields=[
|
|
59
|
+
"namespace",
|
|
60
|
+
"name",
|
|
61
|
+
],
|
|
62
|
+
name="oel_component_type_uniq_ns_n",
|
|
63
|
+
),
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
def __str__(self):
|
|
67
|
+
return f"{self.namespace}:{self.name}"
|
|
68
|
+
|
|
69
|
+
|
|
31
70
|
class Component(PublishableEntityMixin): # type: ignore[django-manager-missing]
|
|
32
71
|
"""
|
|
33
72
|
This represents any Component that has ever existed in a LearningPackage.
|
|
@@ -63,7 +102,7 @@ class Component(PublishableEntityMixin): # type: ignore[django-manager-missing]
|
|
|
63
102
|
-----------------
|
|
64
103
|
|
|
65
104
|
The ``key`` field on Component's ``publishable_entity`` is dervied from the
|
|
66
|
-
``
|
|
105
|
+
``component_type`` and ``local_key`` fields in this model. We don't support
|
|
67
106
|
changing the keys yet, but if we do, those values need to be kept in sync.
|
|
68
107
|
|
|
69
108
|
How build on this model
|
|
@@ -75,9 +114,12 @@ class Component(PublishableEntityMixin): # type: ignore[django-manager-missing]
|
|
|
75
114
|
# Tell mypy what type our objects manager has.
|
|
76
115
|
# It's actually PublishableEntityMixinManager, but that has the exact same
|
|
77
116
|
# interface as the base manager class.
|
|
78
|
-
objects: models.Manager[Component]
|
|
117
|
+
objects: models.Manager[Component] = WithRelationsManager(
|
|
118
|
+
'component_type'
|
|
119
|
+
)
|
|
79
120
|
|
|
80
|
-
with_publishing_relations = WithRelationsManager(
|
|
121
|
+
with_publishing_relations: models.Manager[Component] = WithRelationsManager(
|
|
122
|
+
'component_type',
|
|
81
123
|
'publishable_entity',
|
|
82
124
|
'publishable_entity__draft__version',
|
|
83
125
|
'publishable_entity__draft__version__componentversion',
|
|
@@ -93,53 +135,43 @@ class Component(PublishableEntityMixin): # type: ignore[django-manager-missing]
|
|
|
93
135
|
# columns from different tables).
|
|
94
136
|
learning_package = models.ForeignKey(LearningPackage, on_delete=models.CASCADE)
|
|
95
137
|
|
|
96
|
-
#
|
|
97
|
-
#
|
|
98
|
-
|
|
99
|
-
namespace = case_sensitive_char_field(max_length=100, blank=False)
|
|
138
|
+
# What kind of Component are we? This will usually represent a specific
|
|
139
|
+
# XBlock block_type, but we want it to be more flexible in the long term.
|
|
140
|
+
component_type = models.ForeignKey(ComponentType, on_delete=models.PROTECT)
|
|
100
141
|
|
|
101
|
-
#
|
|
102
|
-
#
|
|
103
|
-
#
|
|
104
|
-
# the UsageKey.
|
|
105
|
-
type = case_sensitive_char_field(max_length=100, blank=True)
|
|
106
|
-
|
|
107
|
-
# local_key is an identifier that is local to the (namespace, type). The
|
|
108
|
-
# publishable.key should be calculated as a combination of (namespace, type,
|
|
109
|
-
# local_key).
|
|
142
|
+
# local_key is an identifier that is local to the learning_package and
|
|
143
|
+
# component_type. The publishable.key should be calculated as a
|
|
144
|
+
# combination of component_type and local_key.
|
|
110
145
|
local_key = key_field()
|
|
111
146
|
|
|
112
147
|
class Meta:
|
|
113
148
|
constraints = [
|
|
114
|
-
# The combination of (
|
|
149
|
+
# The combination of (component_type, local_key) is unique within
|
|
115
150
|
# a given LearningPackage. Note that this means it is possible to
|
|
116
|
-
# have two Components
|
|
117
|
-
#
|
|
118
|
-
#
|
|
119
|
-
#
|
|
151
|
+
# have two Components in the same LearningPackage to have the same
|
|
152
|
+
# local_key if the component_types are different. So for example,
|
|
153
|
+
# you could have a ProblemBlock and VideoBlock that both have the
|
|
154
|
+
# local_key "week_1".
|
|
120
155
|
models.UniqueConstraint(
|
|
121
156
|
fields=[
|
|
122
157
|
"learning_package",
|
|
123
|
-
"
|
|
124
|
-
"type",
|
|
158
|
+
"component_type",
|
|
125
159
|
"local_key",
|
|
126
160
|
],
|
|
127
|
-
name="
|
|
161
|
+
name="oel_component_uniq_lc_ct_lk",
|
|
128
162
|
),
|
|
129
163
|
]
|
|
130
164
|
indexes = [
|
|
131
|
-
# Global
|
|
165
|
+
# Global Component-Type/Local-Key Index:
|
|
132
166
|
# * Search by the different Components fields across all Learning
|
|
133
167
|
# Packages on the site. This would be a support-oriented tool
|
|
134
168
|
# from Django Admin.
|
|
135
169
|
models.Index(
|
|
136
170
|
fields=[
|
|
137
|
-
"
|
|
138
|
-
"namespace",
|
|
139
|
-
"type",
|
|
171
|
+
"component_type",
|
|
140
172
|
"local_key",
|
|
141
173
|
],
|
|
142
|
-
name="
|
|
174
|
+
name="oel_component_idx_ct_lk",
|
|
143
175
|
),
|
|
144
176
|
]
|
|
145
177
|
|
|
@@ -148,7 +180,7 @@ class Component(PublishableEntityMixin): # type: ignore[django-manager-missing]
|
|
|
148
180
|
verbose_name_plural = "Components"
|
|
149
181
|
|
|
150
182
|
def __str__(self):
|
|
151
|
-
return f"{self.namespace}:{self.
|
|
183
|
+
return f"{self.component_type.namespace}:{self.component_type.name}:{self.local_key}"
|
|
152
184
|
|
|
153
185
|
|
|
154
186
|
class ComponentVersion(PublishableEntityVersionMixin):
|
|
@@ -12,9 +12,8 @@ from datetime import datetime
|
|
|
12
12
|
from django.core.files.base import ContentFile
|
|
13
13
|
from django.db.transaction import atomic
|
|
14
14
|
|
|
15
|
-
from
|
|
16
|
-
from
|
|
17
|
-
|
|
15
|
+
from ...lib.cache import lru_cache
|
|
16
|
+
from ...lib.fields import create_hash_digest
|
|
18
17
|
from .models import MediaType, RawContent, TextContent
|
|
19
18
|
|
|
20
19
|
|
|
@@ -33,7 +32,7 @@ def create_raw_content(
|
|
|
33
32
|
|
|
34
33
|
raw_content = RawContent.objects.create(
|
|
35
34
|
learning_package_id=learning_package_id,
|
|
36
|
-
media_type_id=
|
|
35
|
+
media_type_id=get_or_create_media_type_id(mime_type),
|
|
37
36
|
hash_digest=hash_digest,
|
|
38
37
|
size=len(data_bytes),
|
|
39
38
|
created=created,
|
|
@@ -58,7 +57,7 @@ def create_text_from_raw_content(raw_content: RawContent, encoding="utf-8-sig")
|
|
|
58
57
|
|
|
59
58
|
|
|
60
59
|
@lru_cache(maxsize=128)
|
|
61
|
-
def
|
|
60
|
+
def get_or_create_media_type_id(mime_type: str) -> int:
|
|
62
61
|
"""
|
|
63
62
|
Return the MediaType.id for the desired mime_type string.
|
|
64
63
|
|
|
@@ -87,6 +87,7 @@ openedx_tagging/core/tagging/migrations/0011_remove_required.py
|
|
|
87
87
|
openedx_tagging/core/tagging/migrations/0012_language_taxonomy.py
|
|
88
88
|
openedx_tagging/core/tagging/migrations/0013_tag_parent_blank.py
|
|
89
89
|
openedx_tagging/core/tagging/migrations/0014_minor_fixes.py
|
|
90
|
+
openedx_tagging/core/tagging/migrations/0015_taxonomy_export_id.py
|
|
90
91
|
openedx_tagging/core/tagging/migrations/__init__.py
|
|
91
92
|
openedx_tagging/core/tagging/models/__init__.py
|
|
92
93
|
openedx_tagging/core/tagging/models/base.py
|
|
@@ -17,6 +17,7 @@ from typing import Any
|
|
|
17
17
|
from django.db import models, transaction
|
|
18
18
|
from django.db.models import F, QuerySet, Value
|
|
19
19
|
from django.db.models.functions import Coalesce, Concat, Lower
|
|
20
|
+
from django.utils.text import slugify
|
|
20
21
|
from django.utils.translation import gettext as _
|
|
21
22
|
|
|
22
23
|
from .data import TagDataQuerySet
|
|
@@ -34,19 +35,26 @@ def create_taxonomy(
|
|
|
34
35
|
allow_multiple=True,
|
|
35
36
|
allow_free_text=False,
|
|
36
37
|
taxonomy_class: type[Taxonomy] | None = None,
|
|
38
|
+
export_id: str | None = None,
|
|
37
39
|
) -> Taxonomy:
|
|
38
40
|
"""
|
|
39
41
|
Creates, saves, and returns a new Taxonomy with the given attributes.
|
|
40
42
|
"""
|
|
43
|
+
if not export_id:
|
|
44
|
+
export_id = f"{Taxonomy.objects.count() + 1}-{slugify(name, allow_unicode=True)}"
|
|
45
|
+
|
|
41
46
|
taxonomy = Taxonomy(
|
|
42
47
|
name=name,
|
|
43
48
|
description=description or "",
|
|
44
49
|
enabled=enabled,
|
|
45
50
|
allow_multiple=allow_multiple,
|
|
46
51
|
allow_free_text=allow_free_text,
|
|
52
|
+
export_id=export_id,
|
|
47
53
|
)
|
|
48
54
|
if taxonomy_class:
|
|
49
55
|
taxonomy.taxonomy_class = taxonomy_class
|
|
56
|
+
|
|
57
|
+
taxonomy.full_clean()
|
|
50
58
|
taxonomy.save()
|
|
51
59
|
return taxonomy.cast()
|
|
52
60
|
|
{openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/import_export/api.py
RENAMED
|
@@ -121,7 +121,7 @@ def import_tags(
|
|
|
121
121
|
task.end_success()
|
|
122
122
|
|
|
123
123
|
return True, task, tag_import_plan
|
|
124
|
-
except Exception as exception:
|
|
124
|
+
except Exception as exception:
|
|
125
125
|
# Log any exception
|
|
126
126
|
task.log_exception(exception)
|
|
127
127
|
return False, task, None
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Generated by Django 3.2.22 on 2024-01-25 14:20
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
from django.utils.text import slugify
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def migrate_export_id(apps, schema_editor):
|
|
8
|
+
Taxonomy = apps.get_model("oel_tagging", "Taxonomy")
|
|
9
|
+
for taxonomy in Taxonomy.objects.all():
|
|
10
|
+
# Adds the id of the taxonomy to avoid duplicates
|
|
11
|
+
taxonomy.export_id = f"{taxonomy.id}-{slugify(taxonomy.name, allow_unicode=True)}"
|
|
12
|
+
taxonomy.save(update_fields=["export_id"])
|
|
13
|
+
|
|
14
|
+
def reverse(app, schema_editor):
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
class Migration(migrations.Migration):
|
|
18
|
+
|
|
19
|
+
dependencies = [
|
|
20
|
+
('oel_tagging', '0014_minor_fixes'),
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
operations = [
|
|
24
|
+
# Create the field allowing null
|
|
25
|
+
migrations.AddField(
|
|
26
|
+
model_name='taxonomy',
|
|
27
|
+
name='export_id',
|
|
28
|
+
field=models.CharField(help_text="User-facing ID that is used on import/export. Should only contain alphanumeric characters or '_' '-' '.'", max_length=255, null=True, unique=True),
|
|
29
|
+
),
|
|
30
|
+
# Fill the field for created taxonomies
|
|
31
|
+
migrations.RunPython(migrate_export_id, reverse),
|
|
32
|
+
# Alter the field to not allowing null
|
|
33
|
+
migrations.AlterField(
|
|
34
|
+
model_name='taxonomy',
|
|
35
|
+
name='export_id',
|
|
36
|
+
field=models.CharField(help_text="User-facing ID that is used on import/export. Should only contain alphanumeric characters or '_' '-' '.'", max_length=255, null=False, unique=True),
|
|
37
|
+
),
|
|
38
|
+
]
|
{openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/models/base.py
RENAMED
|
@@ -4,6 +4,7 @@ Tagging app base data models
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
6
|
import logging
|
|
7
|
+
import re
|
|
7
8
|
from typing import List
|
|
8
9
|
|
|
9
10
|
from django.core.exceptions import ValidationError
|
|
@@ -228,6 +229,19 @@ class Taxonomy(models.Model):
|
|
|
228
229
|
"Indicates whether this taxonomy should be visible to object authors."
|
|
229
230
|
),
|
|
230
231
|
)
|
|
232
|
+
# External ID that should only be used on import/export.
|
|
233
|
+
# NOT use for any other purposes, you can use the numeric ID of the model instead;
|
|
234
|
+
# this id is editable.
|
|
235
|
+
export_id = models.CharField(
|
|
236
|
+
null=False,
|
|
237
|
+
blank=False,
|
|
238
|
+
max_length=255,
|
|
239
|
+
help_text=_(
|
|
240
|
+
"User-facing ID that is used on import/export."
|
|
241
|
+
" Should only contain alphanumeric characters or '_' '-' '.'"
|
|
242
|
+
),
|
|
243
|
+
unique=True,
|
|
244
|
+
)
|
|
231
245
|
_taxonomy_class = models.CharField(
|
|
232
246
|
null=True,
|
|
233
247
|
max_length=255,
|
|
@@ -300,6 +314,14 @@ class Taxonomy(models.Model):
|
|
|
300
314
|
"""
|
|
301
315
|
return False
|
|
302
316
|
|
|
317
|
+
def clean(self):
|
|
318
|
+
super().clean()
|
|
319
|
+
|
|
320
|
+
if not re.match(r'^[\w\-.]+$', self.export_id):
|
|
321
|
+
raise ValidationError(
|
|
322
|
+
"The export_id should only contain alphanumeric characters or '_' '-' '.'"
|
|
323
|
+
)
|
|
324
|
+
|
|
303
325
|
def cast(self):
|
|
304
326
|
"""
|
|
305
327
|
Returns the current Taxonomy instance cast into its taxonomy_class.
|
|
@@ -336,6 +358,7 @@ class Taxonomy(models.Model):
|
|
|
336
358
|
self.allow_multiple = taxonomy.allow_multiple
|
|
337
359
|
self.allow_free_text = taxonomy.allow_free_text
|
|
338
360
|
self.visible_to_authors = taxonomy.visible_to_authors
|
|
361
|
+
self.export_id = taxonomy.export_id
|
|
339
362
|
self._taxonomy_class = taxonomy._taxonomy_class # pylint: disable=protected-access
|
|
340
363
|
return self
|
|
341
364
|
|
|
@@ -72,6 +72,7 @@ class TaxonomySerializer(UserPermissionsSerializerMixin, serializers.ModelSerial
|
|
|
72
72
|
can_change_taxonomy = serializers.SerializerMethodField(method_name='get_can_change')
|
|
73
73
|
can_delete_taxonomy = serializers.SerializerMethodField(method_name='get_can_delete')
|
|
74
74
|
can_tag_object = serializers.SerializerMethodField()
|
|
75
|
+
export_id = serializers.CharField(required=False)
|
|
75
76
|
|
|
76
77
|
class Meta:
|
|
77
78
|
model = Taxonomy
|
|
@@ -88,6 +89,7 @@ class TaxonomySerializer(UserPermissionsSerializerMixin, serializers.ModelSerial
|
|
|
88
89
|
"can_change_taxonomy",
|
|
89
90
|
"can_delete_taxonomy",
|
|
90
91
|
"can_tag_object",
|
|
92
|
+
"export_id",
|
|
91
93
|
]
|
|
92
94
|
|
|
93
95
|
def to_representation(self, instance):
|
|
@@ -332,6 +334,7 @@ class TaxonomyImportNewBodySerializer(TaxonomyImportBodySerializer): # pylint:
|
|
|
332
334
|
"""
|
|
333
335
|
taxonomy_name = serializers.CharField(required=True)
|
|
334
336
|
taxonomy_description = serializers.CharField(default="")
|
|
337
|
+
taxonomy_export_id = serializers.CharField(required=True)
|
|
335
338
|
|
|
336
339
|
|
|
337
340
|
class TagImportTaskSerializer(serializers.ModelSerializer):
|
{openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/rest_api/v1/views.py
RENAMED
|
@@ -3,6 +3,7 @@ Tagging API Views
|
|
|
3
3
|
"""
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
|
+
from django.core import exceptions
|
|
6
7
|
from django.db import models
|
|
7
8
|
from django.http import Http404, HttpResponse
|
|
8
9
|
from rest_framework import mixins, status
|
|
@@ -57,6 +58,11 @@ class TaxonomyView(ModelViewSet):
|
|
|
57
58
|
"""
|
|
58
59
|
View to list, create, retrieve, update, delete, export or import Taxonomies.
|
|
59
60
|
|
|
61
|
+
TODO: We need to add a perform_update and call the api update function when is created.
|
|
62
|
+
This is because it is necessary to call the model validations. (`full_clean()`).
|
|
63
|
+
Currently those validations are not run, which means we could update `export_id` with any value
|
|
64
|
+
through this api.
|
|
65
|
+
|
|
60
66
|
**List Query Parameters**
|
|
61
67
|
* enabled (optional) - Filter by enabled status. Valid values: true,
|
|
62
68
|
false, 1, 0, "true", "false", "1"
|
|
@@ -87,6 +93,8 @@ class TaxonomyView(ModelViewSet):
|
|
|
87
93
|
**Create Parameters**
|
|
88
94
|
* name (required): User-facing label used when applying tags from this
|
|
89
95
|
taxonomy to Open edX objects.
|
|
96
|
+
* export_id (required): User-facing ID that is used on import/export.
|
|
97
|
+
Should only contain alphanumeric characters or '_' '-' '.'.
|
|
90
98
|
* description (optional): Provides extra information for the user when
|
|
91
99
|
applying tags from this taxonomy to an object.
|
|
92
100
|
* enabled (optional): Only enabled taxonomies will be shown to authors
|
|
@@ -101,6 +109,7 @@ class TaxonomyView(ModelViewSet):
|
|
|
101
109
|
POST api/tagging/v1/taxonomy - Create a taxonomy
|
|
102
110
|
{
|
|
103
111
|
"name": "Taxonomy Name",
|
|
112
|
+
"export_id": "taxonomy_export_id",
|
|
104
113
|
"description": "This is a description",
|
|
105
114
|
"enabled": True,
|
|
106
115
|
"allow_multiple": True,
|
|
@@ -117,6 +126,8 @@ class TaxonomyView(ModelViewSet):
|
|
|
117
126
|
**Update Request Body**
|
|
118
127
|
* name (optional): User-facing label used when applying tags from this
|
|
119
128
|
taxonomy to Open edX objects.
|
|
129
|
+
* export_id (optional): User-facing ID that is used on import/export.
|
|
130
|
+
Should only contain alphanumeric characters or '_' '-' '.'.
|
|
120
131
|
* description (optional): Provides extra information for the user when
|
|
121
132
|
applying tags from this taxonomy to an object.
|
|
122
133
|
* enabled (optional): Only enabled taxonomies will be shown to authors.
|
|
@@ -129,6 +140,7 @@ class TaxonomyView(ModelViewSet):
|
|
|
129
140
|
PUT api/tagging/v1/taxonomy/:pk - Update a taxonomy
|
|
130
141
|
{
|
|
131
142
|
"name": "Taxonomy New Name",
|
|
143
|
+
"export_id": "taxonomy_new_name",
|
|
132
144
|
"description": "This is a new description",
|
|
133
145
|
"enabled": False,
|
|
134
146
|
"allow_multiple": False,
|
|
@@ -174,6 +186,7 @@ class TaxonomyView(ModelViewSet):
|
|
|
174
186
|
POST /tagging/rest_api/v1/taxonomy/import/
|
|
175
187
|
{
|
|
176
188
|
"taxonomy_name": "Taxonomy Name",
|
|
189
|
+
"taxonomy_export_id": "this_is_the_export_id",
|
|
177
190
|
"taxonomy_description": "This is a description",
|
|
178
191
|
"file": <file>,
|
|
179
192
|
}
|
|
@@ -245,7 +258,10 @@ class TaxonomyView(ModelViewSet):
|
|
|
245
258
|
"""
|
|
246
259
|
Create a new taxonomy.
|
|
247
260
|
"""
|
|
248
|
-
|
|
261
|
+
try:
|
|
262
|
+
serializer.instance = create_taxonomy(**serializer.validated_data)
|
|
263
|
+
except exceptions.ValidationError as e:
|
|
264
|
+
raise ValidationError() from e
|
|
249
265
|
|
|
250
266
|
@action(detail=True, methods=["get"])
|
|
251
267
|
def export(self, request, **_kwargs) -> HttpResponse:
|
|
@@ -286,11 +302,12 @@ class TaxonomyView(ModelViewSet):
|
|
|
286
302
|
body.is_valid(raise_exception=True)
|
|
287
303
|
|
|
288
304
|
taxonomy_name = body.validated_data["taxonomy_name"]
|
|
305
|
+
taxonomy_export_id = body.validated_data["taxonomy_export_id"]
|
|
289
306
|
taxonomy_description = body.validated_data["taxonomy_description"]
|
|
290
307
|
file = body.validated_data["file"].file
|
|
291
308
|
parser_format = body.validated_data["parser_format"]
|
|
292
309
|
|
|
293
|
-
taxonomy = create_taxonomy(taxonomy_name, taxonomy_description)
|
|
310
|
+
taxonomy = create_taxonomy(taxonomy_name, taxonomy_description, export_id=taxonomy_export_id)
|
|
294
311
|
try:
|
|
295
312
|
import_success, task, _plan = import_tags(taxonomy, file, parser_format)
|
|
296
313
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/contrib/media_server/__init__.py
RENAMED
|
File without changes
|
{openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/contrib/media_server/apps.py
RENAMED
|
File without changes
|
{openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/contrib/media_server/urls.py
RENAMED
|
File without changes
|
{openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/contrib/media_server/views.py
RENAMED
|
File without changes
|
|
File without changes
|
{openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/core/components/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/core/contents/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/core/publishing/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/core/publishing/model_mixins.py
RENAMED
|
File without changes
|
{openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/core/publishing/models.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning/rest_api/v1/components.py
RENAMED
|
File without changes
|
|
File without changes
|
{openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_learning.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/models/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/models/utils.py
RENAMED
|
File without changes
|
{openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/rest_api/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/rest_api/urls.py
RENAMED
|
File without changes
|
{openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/rest_api/utils.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openedx-learning-0.5.0 → openedx-learning-0.5.1}/openedx_tagging/core/tagging/rest_api/v1/urls.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|