openedx-learning 0.26.0__py2.py3-none-any.whl → 0.27.0__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openedx_learning/__init__.py +1 -1
- openedx_learning/apps/authoring/backup_restore/__init__.py +0 -0
- openedx_learning/apps/authoring/backup_restore/admin.py +3 -0
- openedx_learning/apps/authoring/backup_restore/api.py +25 -0
- openedx_learning/apps/authoring/backup_restore/apps.py +12 -0
- openedx_learning/apps/authoring/backup_restore/management/__init__.py +0 -0
- openedx_learning/apps/authoring/backup_restore/management/commands/__init__.py +0 -0
- openedx_learning/apps/authoring/backup_restore/management/commands/lp_dump.py +45 -0
- openedx_learning/apps/authoring/backup_restore/migrations/__init__.py +0 -0
- openedx_learning/apps/authoring/backup_restore/models.py +3 -0
- openedx_learning/apps/authoring/backup_restore/toml.py +72 -0
- openedx_learning/apps/authoring/publishing/admin.py +277 -12
- openedx_learning/apps/authoring/publishing/models/publishable_entity.py +13 -0
- openedx_learning/apps/authoring/sections/admin.py +48 -0
- openedx_learning/apps/authoring/sections/api.py +0 -1
- openedx_learning/apps/authoring/sections/apps.py +2 -2
- openedx_learning/apps/authoring/subsections/admin.py +48 -0
- openedx_learning/apps/authoring/subsections/api.py +0 -1
- openedx_learning/apps/authoring/units/admin.py +48 -0
- openedx_learning/apps/authoring/units/api.py +0 -1
- openedx_learning/lib/admin_utils.py +17 -1
- {openedx_learning-0.26.0.dist-info → openedx_learning-0.27.0.dist-info}/METADATA +6 -5
- {openedx_learning-0.26.0.dist-info → openedx_learning-0.27.0.dist-info}/RECORD +27 -14
- {openedx_learning-0.26.0.dist-info → openedx_learning-0.27.0.dist-info}/WHEEL +1 -1
- openedx_tagging/core/tagging/models/base.py +1 -1
- {openedx_learning-0.26.0.dist-info → openedx_learning-0.27.0.dist-info}/licenses/LICENSE.txt +0 -0
- {openedx_learning-0.26.0.dist-info → openedx_learning-0.27.0.dist-info}/top_level.txt +0 -0
openedx_learning/__init__.py
CHANGED
|
File without changes
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Backup Restore API
|
|
3
|
+
"""
|
|
4
|
+
import zipfile
|
|
5
|
+
|
|
6
|
+
from openedx_learning.apps.authoring.publishing.api import get_learning_package_by_key
|
|
7
|
+
|
|
8
|
+
from .toml import TOMLLearningPackageFile
|
|
9
|
+
|
|
10
|
+
TOML_PACKAGE_NAME = "package.toml"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def create_zip_file(lp_key: str, path: str) -> None:
|
|
14
|
+
"""
|
|
15
|
+
Creates a zip file with a toml file so far (WIP)
|
|
16
|
+
|
|
17
|
+
Can throw a NotFoundError at get_learning_package_by_key
|
|
18
|
+
"""
|
|
19
|
+
learning_package = get_learning_package_by_key(lp_key)
|
|
20
|
+
toml_file = TOMLLearningPackageFile(learning_package)
|
|
21
|
+
toml_file.create()
|
|
22
|
+
toml_content: str = toml_file.get()
|
|
23
|
+
with zipfile.ZipFile(path, "w", compression=zipfile.ZIP_DEFLATED) as zipf:
|
|
24
|
+
# Add the TOML string as a file in the ZIP
|
|
25
|
+
zipf.writestr(TOML_PACKAGE_NAME, toml_content)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Backup/Restore application initialization.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from django.apps import AppConfig
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class BackupRestoreConfig(AppConfig):
|
|
9
|
+
name = 'openedx_learning.apps.authoring.backup_restore'
|
|
10
|
+
verbose_name = "Learning Core > Authoring > Backup Restore"
|
|
11
|
+
default_auto_field = 'django.db.models.BigAutoField'
|
|
12
|
+
label = "oel_backup_restore"
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Django management commands to handle backup and restore learning packages (WIP)
|
|
3
|
+
"""
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
from django.core.management import CommandError
|
|
7
|
+
from django.core.management.base import BaseCommand
|
|
8
|
+
|
|
9
|
+
from openedx_learning.apps.authoring.backup_restore.api import create_zip_file
|
|
10
|
+
from openedx_learning.apps.authoring.publishing.api import LearningPackage
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Command(BaseCommand):
|
|
16
|
+
"""
|
|
17
|
+
Django management command to export a learning package to a zip file.
|
|
18
|
+
"""
|
|
19
|
+
help = 'Export a learning package to a zip file.'
|
|
20
|
+
|
|
21
|
+
def add_arguments(self, parser):
|
|
22
|
+
parser.add_argument('lp_key', type=str, help='The key of the LearningPackage to dump')
|
|
23
|
+
parser.add_argument('file_name', type=str, help='The name of the output zip file')
|
|
24
|
+
|
|
25
|
+
def handle(self, *args, **options):
|
|
26
|
+
lp_key = options['lp_key']
|
|
27
|
+
file_name = options['file_name']
|
|
28
|
+
if not file_name.endswith(".zip"):
|
|
29
|
+
raise CommandError("Output file name must end with .zip")
|
|
30
|
+
try:
|
|
31
|
+
create_zip_file(lp_key, file_name)
|
|
32
|
+
message = f'{lp_key} written to {file_name}'
|
|
33
|
+
self.stdout.write(self.style.SUCCESS(message))
|
|
34
|
+
except LearningPackage.DoesNotExist as exc:
|
|
35
|
+
message = f"Learning package with key {lp_key} not found"
|
|
36
|
+
logger.exception(message)
|
|
37
|
+
raise CommandError(message) from exc
|
|
38
|
+
except Exception as e:
|
|
39
|
+
message = f"Failed to export learning package '{lp_key}': {e}"
|
|
40
|
+
logger.exception(
|
|
41
|
+
"Failed to create zip file %s (learning‑package key %s)",
|
|
42
|
+
file_name,
|
|
43
|
+
lp_key,
|
|
44
|
+
)
|
|
45
|
+
raise CommandError(message) from e
|
|
File without changes
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utilities for backup and restore app
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from typing import Any, Dict
|
|
7
|
+
|
|
8
|
+
from tomlkit import comment, document, dumps, nl, table
|
|
9
|
+
from tomlkit.items import Table
|
|
10
|
+
|
|
11
|
+
from openedx_learning.apps.authoring.publishing.models.learning_package import LearningPackage
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TOMLLearningPackageFile():
|
|
15
|
+
"""
|
|
16
|
+
Class to create a .toml representation of a LearningPackage instance.
|
|
17
|
+
|
|
18
|
+
This class builds a structured TOML document using `tomlkit` with metadata and fields
|
|
19
|
+
extracted from a `LearningPackage` object. The output can later be saved to a file or used elsewhere.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, learning_package: LearningPackage):
|
|
23
|
+
self.doc = document()
|
|
24
|
+
self.learning_package = learning_package
|
|
25
|
+
|
|
26
|
+
def _create_header(self) -> None:
|
|
27
|
+
"""
|
|
28
|
+
Adds a comment with the current datetime to indicate when the export occurred.
|
|
29
|
+
This helps with traceability and file versioning.
|
|
30
|
+
"""
|
|
31
|
+
self.doc.add(comment(f"Datetime of the export: {datetime.now()}"))
|
|
32
|
+
self.doc.add(nl())
|
|
33
|
+
|
|
34
|
+
def _create_table(self, params: Dict[str, Any]) -> Table:
|
|
35
|
+
"""
|
|
36
|
+
Builds a TOML table section from a dictionary of key-value pairs.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
params (Dict[str, Any]): A dictionary containing keys and values to include in the TOML table.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
Table: A TOML table populated with the provided keys and values.
|
|
43
|
+
"""
|
|
44
|
+
section = table()
|
|
45
|
+
for key, value in params.items():
|
|
46
|
+
section.add(key, value)
|
|
47
|
+
return section
|
|
48
|
+
|
|
49
|
+
def create(self) -> None:
|
|
50
|
+
"""
|
|
51
|
+
Populates the TOML document with a header and a table containing
|
|
52
|
+
metadata from the LearningPackage instance.
|
|
53
|
+
|
|
54
|
+
This method must be called before calling `get()`, otherwise the document will be empty.
|
|
55
|
+
"""
|
|
56
|
+
self._create_header()
|
|
57
|
+
section = self._create_table({
|
|
58
|
+
"title": self.learning_package.title,
|
|
59
|
+
"key": self.learning_package.key,
|
|
60
|
+
"description": self.learning_package.description,
|
|
61
|
+
"created": self.learning_package.created,
|
|
62
|
+
"updated": self.learning_package.updated
|
|
63
|
+
})
|
|
64
|
+
self.doc.add("learning_package", section)
|
|
65
|
+
|
|
66
|
+
def get(self) -> str:
|
|
67
|
+
"""
|
|
68
|
+
Returns:
|
|
69
|
+
str: The string representation of the generated TOML document.
|
|
70
|
+
Ensure `create()` has been called beforehand to get meaningful output.
|
|
71
|
+
"""
|
|
72
|
+
return dumps(self.doc)
|
|
@@ -3,14 +3,22 @@ Django admin for publishing models
|
|
|
3
3
|
"""
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
|
+
import functools
|
|
7
|
+
|
|
6
8
|
from django.contrib import admin
|
|
7
9
|
from django.db.models import Count
|
|
10
|
+
from django.utils.html import format_html
|
|
11
|
+
from django.utils.safestring import SafeText
|
|
8
12
|
|
|
9
|
-
from openedx_learning.lib.admin_utils import ReadOnlyModelAdmin, one_to_one_related_model_html
|
|
13
|
+
from openedx_learning.lib.admin_utils import ReadOnlyModelAdmin, model_detail_link, one_to_one_related_model_html
|
|
10
14
|
|
|
11
15
|
from .models import (
|
|
16
|
+
Container,
|
|
17
|
+
ContainerVersion,
|
|
12
18
|
DraftChangeLog,
|
|
13
19
|
DraftChangeLogRecord,
|
|
20
|
+
EntityList,
|
|
21
|
+
EntityListRow,
|
|
14
22
|
LearningPackage,
|
|
15
23
|
PublishableEntity,
|
|
16
24
|
PublishLog,
|
|
@@ -122,6 +130,12 @@ class PublishableEntityAdmin(ReadOnlyModelAdmin):
|
|
|
122
130
|
"can_stand_alone",
|
|
123
131
|
]
|
|
124
132
|
|
|
133
|
+
def draft_version(self, entity: PublishableEntity):
|
|
134
|
+
return entity.draft.version.version_num if entity.draft.version else None
|
|
135
|
+
|
|
136
|
+
def published_version(self, entity: PublishableEntity):
|
|
137
|
+
return entity.published.version.version_num if entity.published and entity.published.version else None
|
|
138
|
+
|
|
125
139
|
def get_queryset(self, request):
|
|
126
140
|
queryset = super().get_queryset(request)
|
|
127
141
|
return queryset.select_related(
|
|
@@ -131,16 +145,6 @@ class PublishableEntityAdmin(ReadOnlyModelAdmin):
|
|
|
131
145
|
def see_also(self, entity):
|
|
132
146
|
return one_to_one_related_model_html(entity)
|
|
133
147
|
|
|
134
|
-
def draft_version(self, entity):
|
|
135
|
-
if entity.draft.version:
|
|
136
|
-
return entity.draft.version.version_num
|
|
137
|
-
return None
|
|
138
|
-
|
|
139
|
-
def published_version(self, entity):
|
|
140
|
-
if entity.published.version:
|
|
141
|
-
return entity.published.version.version_num
|
|
142
|
-
return None
|
|
143
|
-
|
|
144
148
|
|
|
145
149
|
@admin.register(Published)
|
|
146
150
|
class PublishedAdmin(ReadOnlyModelAdmin):
|
|
@@ -229,7 +233,7 @@ class DraftChangeSetAdmin(ReadOnlyModelAdmin):
|
|
|
229
233
|
"""
|
|
230
234
|
inlines = [DraftChangeLogRecordTabularInline]
|
|
231
235
|
fields = (
|
|
232
|
-
"
|
|
236
|
+
"pk",
|
|
233
237
|
"learning_package",
|
|
234
238
|
"num_changes",
|
|
235
239
|
"changed_at",
|
|
@@ -246,3 +250,264 @@ class DraftChangeSetAdmin(ReadOnlyModelAdmin):
|
|
|
246
250
|
queryset = super().get_queryset(request)
|
|
247
251
|
return queryset.select_related("learning_package", "changed_by") \
|
|
248
252
|
.annotate(num_changes=Count("records"))
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _entity_list_detail_link(el: EntityList) -> SafeText:
|
|
256
|
+
"""
|
|
257
|
+
A link to the detail page for an EntityList which includes its PK and length.
|
|
258
|
+
"""
|
|
259
|
+
num_rows = el.entitylistrow_set.count()
|
|
260
|
+
rows_noun = "row" if num_rows == 1 else "rows"
|
|
261
|
+
return model_detail_link(el, f"EntityList #{el.pk} with {num_rows} {rows_noun}")
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
class ContainerVersionInlineForContainer(admin.TabularInline):
|
|
265
|
+
"""
|
|
266
|
+
Inline admin view of ContainerVersions in a given Container
|
|
267
|
+
"""
|
|
268
|
+
model = ContainerVersion
|
|
269
|
+
ordering = ["-publishable_entity_version__version_num"]
|
|
270
|
+
fields = [
|
|
271
|
+
"pk",
|
|
272
|
+
"version_num",
|
|
273
|
+
"title",
|
|
274
|
+
"children",
|
|
275
|
+
"created",
|
|
276
|
+
"created_by",
|
|
277
|
+
]
|
|
278
|
+
readonly_fields = fields # type: ignore[assignment]
|
|
279
|
+
extra = 0
|
|
280
|
+
|
|
281
|
+
def get_queryset(self, request):
|
|
282
|
+
return super().get_queryset(request).select_related(
|
|
283
|
+
"publishable_entity_version"
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
def children(self, obj: ContainerVersion):
|
|
287
|
+
return _entity_list_detail_link(obj.entity_list)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
@admin.register(Container)
|
|
291
|
+
class ContainerAdmin(ReadOnlyModelAdmin):
|
|
292
|
+
"""
|
|
293
|
+
Django admin configuration for Container
|
|
294
|
+
"""
|
|
295
|
+
list_display = ("key", "created", "draft", "published", "see_also")
|
|
296
|
+
fields = [
|
|
297
|
+
"pk",
|
|
298
|
+
"publishable_entity",
|
|
299
|
+
"learning_package",
|
|
300
|
+
"draft",
|
|
301
|
+
"published",
|
|
302
|
+
"created",
|
|
303
|
+
"created_by",
|
|
304
|
+
"see_also",
|
|
305
|
+
"most_recent_parent_entity_list",
|
|
306
|
+
]
|
|
307
|
+
readonly_fields = fields # type: ignore[assignment]
|
|
308
|
+
search_fields = ["publishable_entity__uuid", "publishable_entity__key"]
|
|
309
|
+
inlines = [ContainerVersionInlineForContainer]
|
|
310
|
+
|
|
311
|
+
def learning_package(self, obj: Container) -> SafeText:
|
|
312
|
+
return model_detail_link(
|
|
313
|
+
obj.publishable_entity.learning_package,
|
|
314
|
+
obj.publishable_entity.learning_package.key,
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
def get_queryset(self, request):
|
|
318
|
+
return super().get_queryset(request).select_related(
|
|
319
|
+
"publishable_entity",
|
|
320
|
+
"publishable_entity__learning_package",
|
|
321
|
+
"publishable_entity__published__version",
|
|
322
|
+
"publishable_entity__draft__version",
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
def draft(self, obj: Container) -> str:
|
|
326
|
+
"""
|
|
327
|
+
Link to this Container's draft ContainerVersion
|
|
328
|
+
"""
|
|
329
|
+
if draft := obj.versioning.draft:
|
|
330
|
+
return format_html(
|
|
331
|
+
'Version {} "{}" ({})', draft.version_num, draft.title, _entity_list_detail_link(draft.entity_list)
|
|
332
|
+
)
|
|
333
|
+
return "-"
|
|
334
|
+
|
|
335
|
+
def published(self, obj: Container) -> str:
|
|
336
|
+
"""
|
|
337
|
+
Link to this Container's published ContainerVersion
|
|
338
|
+
"""
|
|
339
|
+
if published := obj.versioning.published:
|
|
340
|
+
return format_html(
|
|
341
|
+
'Version {} "{}" ({})',
|
|
342
|
+
published.version_num,
|
|
343
|
+
published.title,
|
|
344
|
+
_entity_list_detail_link(published.entity_list),
|
|
345
|
+
)
|
|
346
|
+
return "-"
|
|
347
|
+
|
|
348
|
+
def see_also(self, obj: Container):
|
|
349
|
+
return one_to_one_related_model_html(obj)
|
|
350
|
+
|
|
351
|
+
def most_recent_parent_entity_list(self, obj: Container) -> str:
|
|
352
|
+
if latest_row := EntityListRow.objects.filter(entity_id=obj.publishable_entity_id).order_by("-pk").first():
|
|
353
|
+
return _entity_list_detail_link(latest_row.entity_list)
|
|
354
|
+
return "-"
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
class ContainerVersionInlineForEntityList(admin.TabularInline):
|
|
358
|
+
"""
|
|
359
|
+
Inline admin view of ContainerVersions which use a given EntityList
|
|
360
|
+
"""
|
|
361
|
+
model = ContainerVersion
|
|
362
|
+
verbose_name = "Container Version that references this Entity List"
|
|
363
|
+
verbose_name_plural = "Container Versions that reference this Entity List"
|
|
364
|
+
ordering = ["-pk"] # Newest first
|
|
365
|
+
fields = [
|
|
366
|
+
"pk",
|
|
367
|
+
"version_num",
|
|
368
|
+
"container_key",
|
|
369
|
+
"title",
|
|
370
|
+
"created",
|
|
371
|
+
"created_by",
|
|
372
|
+
]
|
|
373
|
+
readonly_fields = fields # type: ignore[assignment]
|
|
374
|
+
extra = 0
|
|
375
|
+
|
|
376
|
+
def get_queryset(self, request):
|
|
377
|
+
return super().get_queryset(request).select_related(
|
|
378
|
+
"container",
|
|
379
|
+
"container__publishable_entity",
|
|
380
|
+
"publishable_entity_version",
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
def container_key(self, obj: ContainerVersion) -> SafeText:
|
|
384
|
+
return model_detail_link(obj.container, obj.container.key)
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
class EntityListRowInline(admin.TabularInline):
|
|
388
|
+
"""
|
|
389
|
+
Table of entity rows in the entitylist admin
|
|
390
|
+
"""
|
|
391
|
+
model = EntityListRow
|
|
392
|
+
readonly_fields = [
|
|
393
|
+
"order_num",
|
|
394
|
+
"pinned_version_num",
|
|
395
|
+
"entity_models",
|
|
396
|
+
"container_models",
|
|
397
|
+
"container_children",
|
|
398
|
+
]
|
|
399
|
+
fields = readonly_fields # type: ignore[assignment]
|
|
400
|
+
|
|
401
|
+
def get_queryset(self, request):
|
|
402
|
+
return super().get_queryset(request).select_related(
|
|
403
|
+
"entity",
|
|
404
|
+
"entity_version",
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
def pinned_version_num(self, obj: EntityListRow):
|
|
408
|
+
return str(obj.entity_version.version_num) if obj.entity_version else "(Unpinned)"
|
|
409
|
+
|
|
410
|
+
def entity_models(self, obj: EntityListRow):
|
|
411
|
+
return format_html(
|
|
412
|
+
"{}<ul>{}</ul>",
|
|
413
|
+
model_detail_link(obj.entity, obj.entity.key),
|
|
414
|
+
one_to_one_related_model_html(obj.entity),
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
def container_models(self, obj: EntityListRow) -> SafeText:
|
|
418
|
+
if not hasattr(obj.entity, "container"):
|
|
419
|
+
return SafeText("(Not a Container)")
|
|
420
|
+
return format_html(
|
|
421
|
+
"{}<ul>{}</ul>",
|
|
422
|
+
model_detail_link(obj.entity.container, str(obj.entity.container)),
|
|
423
|
+
one_to_one_related_model_html(obj.entity.container),
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
def container_children(self, obj: EntityListRow) -> SafeText:
|
|
427
|
+
"""
|
|
428
|
+
If this row holds a Container, then link *its* EntityList, allowing easy hierarchy browsing.
|
|
429
|
+
|
|
430
|
+
When determining which ContainerVersion to grab the EntityList from, prefer the pinned
|
|
431
|
+
version if there is one; otherwise use the Draft version.
|
|
432
|
+
"""
|
|
433
|
+
if not hasattr(obj.entity, "container"):
|
|
434
|
+
return SafeText("(Not a Container)")
|
|
435
|
+
child_container_version: ContainerVersion = (
|
|
436
|
+
obj.entity_version.containerversion
|
|
437
|
+
if obj.entity_version
|
|
438
|
+
else obj.entity.container.versioning.draft
|
|
439
|
+
)
|
|
440
|
+
return _entity_list_detail_link(child_container_version.entity_list)
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
@admin.register(EntityList)
|
|
444
|
+
class EntityListAdmin(ReadOnlyModelAdmin):
|
|
445
|
+
"""
|
|
446
|
+
Django admin configuration for EntityList
|
|
447
|
+
"""
|
|
448
|
+
list_display = [
|
|
449
|
+
"entity_list",
|
|
450
|
+
"row_count",
|
|
451
|
+
"recent_container_version_num",
|
|
452
|
+
"recent_container",
|
|
453
|
+
"recent_container_package"
|
|
454
|
+
]
|
|
455
|
+
inlines = [ContainerVersionInlineForEntityList, EntityListRowInline]
|
|
456
|
+
|
|
457
|
+
def entity_list(self, obj: EntityList) -> SafeText:
|
|
458
|
+
return model_detail_link(obj, f"EntityList #{obj.pk}")
|
|
459
|
+
|
|
460
|
+
def row_count(self, obj: EntityList) -> int:
|
|
461
|
+
return obj.entitylistrow_set.count()
|
|
462
|
+
|
|
463
|
+
def recent_container_version_num(self, obj: EntityList) -> str:
|
|
464
|
+
"""
|
|
465
|
+
Number of the newest ContainerVersion that references this EntityList
|
|
466
|
+
"""
|
|
467
|
+
if latest := _latest_container_version(obj):
|
|
468
|
+
return f"Version {latest.version_num}"
|
|
469
|
+
else:
|
|
470
|
+
return "-"
|
|
471
|
+
|
|
472
|
+
def recent_container(self, obj: EntityList) -> SafeText | None:
|
|
473
|
+
"""
|
|
474
|
+
Link to the Container of the newest ContainerVersion that references this EntityList
|
|
475
|
+
"""
|
|
476
|
+
if latest := _latest_container_version(obj):
|
|
477
|
+
return format_html("of: {}", model_detail_link(latest.container, latest.container.key))
|
|
478
|
+
else:
|
|
479
|
+
return None
|
|
480
|
+
|
|
481
|
+
def recent_container_package(self, obj: EntityList) -> SafeText | None:
|
|
482
|
+
"""
|
|
483
|
+
Link to the LearningPackage of the newest ContainerVersion that references this EntityList
|
|
484
|
+
"""
|
|
485
|
+
if latest := _latest_container_version(obj):
|
|
486
|
+
return format_html(
|
|
487
|
+
"in: {}",
|
|
488
|
+
model_detail_link(
|
|
489
|
+
latest.container.publishable_entity.learning_package,
|
|
490
|
+
latest.container.publishable_entity.learning_package.key
|
|
491
|
+
)
|
|
492
|
+
)
|
|
493
|
+
else:
|
|
494
|
+
return None
|
|
495
|
+
|
|
496
|
+
# We'd like it to appear as if these three columns are just a single
|
|
497
|
+
# nicely-formatted column, so only give the left one a description.
|
|
498
|
+
recent_container_version_num.short_description = ( # type: ignore[attr-defined]
|
|
499
|
+
"Most recent container version using this entity list"
|
|
500
|
+
)
|
|
501
|
+
recent_container.short_description = "" # type: ignore[attr-defined]
|
|
502
|
+
recent_container_package.short_description = "" # type: ignore[attr-defined]
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
@functools.cache
|
|
506
|
+
def _latest_container_version(obj: EntityList) -> ContainerVersion | None:
|
|
507
|
+
"""
|
|
508
|
+
Any given EntityList can be used by multiple ContainerVersion (which may even
|
|
509
|
+
span multiple Containers). We only have space here to show one ContainerVersion
|
|
510
|
+
easily, so let's show the one that's most likely to be interesting to the Django
|
|
511
|
+
admin user: the most-recently-created one.
|
|
512
|
+
"""
|
|
513
|
+
return obj.container_versions.order_by("-pk").first()
|
|
@@ -221,6 +221,9 @@ class PublishableEntityVersion(models.Model):
|
|
|
221
221
|
blank=True,
|
|
222
222
|
)
|
|
223
223
|
|
|
224
|
+
def __str__(self):
|
|
225
|
+
return f"{self.entity.key} @ v{self.version_num} - {self.title}"
|
|
226
|
+
|
|
224
227
|
class Meta:
|
|
225
228
|
constraints = [
|
|
226
229
|
# Prevent the situation where we have multiple
|
|
@@ -303,6 +306,9 @@ class PublishableEntityMixin(models.Model):
|
|
|
303
306
|
def created_by(self):
|
|
304
307
|
return self.publishable_entity.created_by
|
|
305
308
|
|
|
309
|
+
def __str__(self) -> str:
|
|
310
|
+
return str(self.publishable_entity)
|
|
311
|
+
|
|
306
312
|
class Meta:
|
|
307
313
|
abstract = True
|
|
308
314
|
|
|
@@ -570,10 +576,17 @@ class PublishableEntityVersionMixin(models.Model):
|
|
|
570
576
|
def created(self) -> datetime:
|
|
571
577
|
return self.publishable_entity_version.created
|
|
572
578
|
|
|
579
|
+
@property
|
|
580
|
+
def created_by(self):
|
|
581
|
+
return self.publishable_entity_version.created_by
|
|
582
|
+
|
|
573
583
|
@property
|
|
574
584
|
def version_num(self) -> int:
|
|
575
585
|
return self.publishable_entity_version.version_num
|
|
576
586
|
|
|
587
|
+
def __str__(self) -> str:
|
|
588
|
+
return str(self.publishable_entity_version)
|
|
589
|
+
|
|
577
590
|
class Meta:
|
|
578
591
|
abstract = True
|
|
579
592
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Django admin for sections models
|
|
3
|
+
"""
|
|
4
|
+
from django.contrib import admin
|
|
5
|
+
from django.utils.safestring import SafeText
|
|
6
|
+
|
|
7
|
+
from openedx_learning.lib.admin_utils import ReadOnlyModelAdmin, model_detail_link
|
|
8
|
+
|
|
9
|
+
from ..publishing.models import ContainerVersion
|
|
10
|
+
from .models import Section, SectionVersion
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SectionVersionInline(admin.TabularInline):
|
|
14
|
+
"""
|
|
15
|
+
Minimal table for section versions in a section.
|
|
16
|
+
|
|
17
|
+
(Generally, this information is useless, because each SectionVersion should have a
|
|
18
|
+
matching ContainerVersion, shown in much more detail on the Container detail page.
|
|
19
|
+
But we've hit at least one bug where ContainerVersions were being created without
|
|
20
|
+
their connected SectionVersions, so we'll leave this table here for debugging
|
|
21
|
+
at least until we've made the APIs more robust against that sort of data corruption.)
|
|
22
|
+
"""
|
|
23
|
+
model = SectionVersion
|
|
24
|
+
fields = ["pk"]
|
|
25
|
+
readonly_fields = ["pk"]
|
|
26
|
+
ordering = ["-pk"] # Newest first
|
|
27
|
+
|
|
28
|
+
def pk(self, obj: ContainerVersion) -> SafeText:
|
|
29
|
+
return obj.pk
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@admin.register(Section)
|
|
33
|
+
class SectionAdmin(ReadOnlyModelAdmin):
|
|
34
|
+
"""
|
|
35
|
+
Very minimal interface... just direct the admin user's attention towards the related Container model admin.
|
|
36
|
+
"""
|
|
37
|
+
inlines = [SectionVersionInline]
|
|
38
|
+
list_display = ["pk", "key"]
|
|
39
|
+
fields = ["key"]
|
|
40
|
+
readonly_fields = ["key"]
|
|
41
|
+
|
|
42
|
+
def key(self, obj: Section) -> SafeText:
|
|
43
|
+
return model_detail_link(obj.container, obj.key)
|
|
44
|
+
|
|
45
|
+
def get_form(self, request, obj=None, change=False, **kwargs):
|
|
46
|
+
help_texts = {'key': f'For more details of this {self.model.__name__}, click above to see its Container view'}
|
|
47
|
+
kwargs.update({'help_texts': help_texts})
|
|
48
|
+
return super().get_form(request, obj, **kwargs)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
|
|
2
|
+
Sections Django application initialization.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from django.apps import AppConfig
|
|
@@ -7,7 +7,7 @@ from django.apps import AppConfig
|
|
|
7
7
|
|
|
8
8
|
class SectionsConfig(AppConfig):
|
|
9
9
|
"""
|
|
10
|
-
Configuration for the
|
|
10
|
+
Configuration for the Sections Django application.
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
13
|
name = "openedx_learning.apps.authoring.sections"
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Django admin for subsection models
|
|
3
|
+
"""
|
|
4
|
+
from django.contrib import admin
|
|
5
|
+
from django.utils.safestring import SafeText
|
|
6
|
+
|
|
7
|
+
from openedx_learning.lib.admin_utils import ReadOnlyModelAdmin, model_detail_link
|
|
8
|
+
|
|
9
|
+
from ..publishing.models import ContainerVersion
|
|
10
|
+
from .models import Subsection, SubsectionVersion
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SubsectionVersionInline(admin.TabularInline):
|
|
14
|
+
"""
|
|
15
|
+
Minimal table for subsection versions in a subsection.
|
|
16
|
+
|
|
17
|
+
(Generally, this information is useless, because each SubsectionVersion should have a
|
|
18
|
+
matching ContainerVersion, shown in much more detail on the Container detail page.
|
|
19
|
+
But we've hit at least one bug where ContainerVersions were being created without
|
|
20
|
+
their connected SubsectionVersions, so we'll leave this table here for debugging
|
|
21
|
+
at least until we've made the APIs more robust against that sort of data corruption.)
|
|
22
|
+
"""
|
|
23
|
+
model = SubsectionVersion
|
|
24
|
+
fields = ["pk"]
|
|
25
|
+
readonly_fields = ["pk"]
|
|
26
|
+
ordering = ["-pk"] # Newest first
|
|
27
|
+
|
|
28
|
+
def pk(self, obj: ContainerVersion) -> SafeText:
|
|
29
|
+
return obj.pk
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@admin.register(Subsection)
|
|
33
|
+
class SubsectionAdmin(ReadOnlyModelAdmin):
|
|
34
|
+
"""
|
|
35
|
+
Very minimal interface... just direct the admin user's attention towards the related Container model admin.
|
|
36
|
+
"""
|
|
37
|
+
inlines = [SubsectionVersionInline]
|
|
38
|
+
list_display = ["pk", "key"]
|
|
39
|
+
fields = ["key"]
|
|
40
|
+
readonly_fields = ["key"]
|
|
41
|
+
|
|
42
|
+
def key(self, obj: Subsection) -> SafeText:
|
|
43
|
+
return model_detail_link(obj.container, obj.key)
|
|
44
|
+
|
|
45
|
+
def get_form(self, request, obj=None, change=False, **kwargs):
|
|
46
|
+
help_texts = {'key': f'For more details of this {self.model.__name__}, click above to see its Container view'}
|
|
47
|
+
kwargs.update({'help_texts': help_texts})
|
|
48
|
+
return super().get_form(request, obj, **kwargs)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Django admin for units models
|
|
3
|
+
"""
|
|
4
|
+
from django.contrib import admin
|
|
5
|
+
from django.utils.safestring import SafeText
|
|
6
|
+
|
|
7
|
+
from openedx_learning.lib.admin_utils import ReadOnlyModelAdmin, model_detail_link
|
|
8
|
+
|
|
9
|
+
from ..publishing.models import ContainerVersion
|
|
10
|
+
from .models import Unit, UnitVersion
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class UnitVersionInline(admin.TabularInline):
|
|
14
|
+
"""
|
|
15
|
+
Minimal table for unit versions in a unit
|
|
16
|
+
|
|
17
|
+
(Generally, this information is useless, because each UnitVersion should have a
|
|
18
|
+
matching ContainerVersion, shown in much more detail on the Container detail page.
|
|
19
|
+
But we've hit at least one bug where ContainerVersions were being created without
|
|
20
|
+
their connected UnitVersions, so we'll leave this table here for debugging
|
|
21
|
+
at least until we've made the APIs more robust against that sort of data corruption.)
|
|
22
|
+
"""
|
|
23
|
+
model = UnitVersion
|
|
24
|
+
fields = ["pk"]
|
|
25
|
+
readonly_fields = ["pk"]
|
|
26
|
+
ordering = ["-pk"] # Newest first
|
|
27
|
+
|
|
28
|
+
def pk(self, obj: ContainerVersion) -> SafeText:
|
|
29
|
+
return obj.pk
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@admin.register(Unit)
|
|
33
|
+
class UnitAdmin(ReadOnlyModelAdmin):
|
|
34
|
+
"""
|
|
35
|
+
Very minimal interface... just direct the admin user's attention towards the related Container model admin.
|
|
36
|
+
"""
|
|
37
|
+
inlines = [UnitVersionInline]
|
|
38
|
+
list_display = ["pk", "key"]
|
|
39
|
+
fields = ["key"]
|
|
40
|
+
readonly_fields = ["key"]
|
|
41
|
+
|
|
42
|
+
def key(self, obj: Unit) -> SafeText:
|
|
43
|
+
return model_detail_link(obj.container, obj.key)
|
|
44
|
+
|
|
45
|
+
def get_form(self, request, obj=None, change=False, **kwargs):
|
|
46
|
+
help_texts = {'key': f'For more details of this {self.model.__name__}, click above to see its Container view'}
|
|
47
|
+
kwargs.update({'help_texts': help_texts})
|
|
48
|
+
return super().get_form(request, obj, **kwargs)
|
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
Convenience utilities for the Django Admin.
|
|
3
3
|
"""
|
|
4
4
|
from django.contrib import admin
|
|
5
|
+
from django.db import models
|
|
5
6
|
from django.db.models.fields.reverse_related import OneToOneRel
|
|
6
7
|
from django.urls import NoReverseMatch, reverse
|
|
7
8
|
from django.utils.html import format_html, format_html_join
|
|
9
|
+
from django.utils.safestring import SafeText
|
|
8
10
|
|
|
9
11
|
|
|
10
12
|
class ReadOnlyModelAdmin(admin.ModelAdmin):
|
|
@@ -31,7 +33,7 @@ class ReadOnlyModelAdmin(admin.ModelAdmin):
|
|
|
31
33
|
return False
|
|
32
34
|
|
|
33
35
|
|
|
34
|
-
def one_to_one_related_model_html(model_obj):
|
|
36
|
+
def one_to_one_related_model_html(model_obj: models.Model) -> SafeText:
|
|
35
37
|
"""
|
|
36
38
|
HTML for clickable list of a models that are 1:1-related to ``model_obj``.
|
|
37
39
|
|
|
@@ -98,3 +100,17 @@ def one_to_one_related_model_html(model_obj):
|
|
|
98
100
|
text.append(html)
|
|
99
101
|
|
|
100
102
|
return format_html_join("\n", "<li>{}</li>", ((t,) for t in text))
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def model_detail_link(obj: models.Model, link_text: str) -> SafeText:
|
|
106
|
+
"""
|
|
107
|
+
Render an HTML link to the admin focus page for `obj`.
|
|
108
|
+
"""
|
|
109
|
+
return format_html(
|
|
110
|
+
'<a href="{}">{}</a>',
|
|
111
|
+
reverse(
|
|
112
|
+
f"admin:{obj._meta.app_label}_{(obj._meta.model_name or obj.__class__.__name__).lower()}_change",
|
|
113
|
+
args=(obj.pk,),
|
|
114
|
+
),
|
|
115
|
+
link_text,
|
|
116
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openedx-learning
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.27.0
|
|
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,12 +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: djangorestframework<4.0
|
|
23
|
-
Requires-Dist: celery
|
|
24
|
-
Requires-Dist: edx-drf-extensions
|
|
25
|
-
Requires-Dist: attrs
|
|
26
22
|
Requires-Dist: Django
|
|
27
23
|
Requires-Dist: rules<4.0
|
|
24
|
+
Requires-Dist: celery
|
|
25
|
+
Requires-Dist: djangorestframework<4.0
|
|
26
|
+
Requires-Dist: attrs
|
|
27
|
+
Requires-Dist: tomlkit
|
|
28
|
+
Requires-Dist: edx-drf-extensions
|
|
28
29
|
Dynamic: author
|
|
29
30
|
Dynamic: author-email
|
|
30
31
|
Dynamic: classifier
|
|
@@ -1,10 +1,20 @@
|
|
|
1
|
-
openedx_learning/__init__.py,sha256=
|
|
1
|
+
openedx_learning/__init__.py,sha256=SyGXwpGB_cwgn1A-xCULm9ZZQwV783LkH-64qx6sehg,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=1sh-hUH3pJLVIQHzzjlqWb_9uxf9y3-hanLpU4mRvXc,1061
|
|
5
5
|
openedx_learning/api/authoring_models.py,sha256=lA500-C7LBlVzeaEVqmCiQMAPPmeoDMi8wS0TbzgdGw,778
|
|
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
|
+
openedx_learning/apps/authoring/backup_restore/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
openedx_learning/apps/authoring/backup_restore/admin.py,sha256=OnEixkOuysPRr-F6C_CMwPkiXawkqgSEF46n3yiUK0o,59
|
|
10
|
+
openedx_learning/apps/authoring/backup_restore/api.py,sha256=T5hYOuelkK2-UnJNDObk2DnkoKPxKdc8LJ6MwtwaXRI,760
|
|
11
|
+
openedx_learning/apps/authoring/backup_restore/apps.py,sha256=UnExBA7jhd3qI30_87JMvzVhS_k82t89qDVKSMpvg_A,340
|
|
12
|
+
openedx_learning/apps/authoring/backup_restore/models.py,sha256=jlr0ppxW0IOW3HPHoJNChHvDrYVnKMb5_3uC2itxqQk,45
|
|
13
|
+
openedx_learning/apps/authoring/backup_restore/toml.py,sha256=zjaeVJqZJhtITVaL2Pmopkg3bhgbSM8-t4J3IUeNHts,2496
|
|
14
|
+
openedx_learning/apps/authoring/backup_restore/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
+
openedx_learning/apps/authoring/backup_restore/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
+
openedx_learning/apps/authoring/backup_restore/management/commands/lp_dump.py,sha256=NiZ-fEDGgh9Q0IUjpjOIvm9BtzwnVp6LK6IY-OA_o98,1734
|
|
17
|
+
openedx_learning/apps/authoring/backup_restore/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
18
|
openedx_learning/apps/authoring/collections/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
19
|
openedx_learning/apps/authoring/collections/admin.py,sha256=f0hySjDMfIphVDEGkCSMIUHoEiqHRA7EE2NiO7lvL4g,1156
|
|
10
20
|
openedx_learning/apps/authoring/collections/api.py,sha256=DaGg73iom7fN9fODajo8B2e9Jkx2syfLEVjip0cAzlQ,7747
|
|
@@ -36,7 +46,7 @@ openedx_learning/apps/authoring/contents/models.py,sha256=8CYrHK7CZ4U3F7Den4ZV_a
|
|
|
36
46
|
openedx_learning/apps/authoring/contents/migrations/0001_initial.py,sha256=FtOTmIGX2KHpjw-PHbfRjxkFEomI5CEDhNKCZ7IpFeE,3060
|
|
37
47
|
openedx_learning/apps/authoring/contents/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
38
48
|
openedx_learning/apps/authoring/publishing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
39
|
-
openedx_learning/apps/authoring/publishing/admin.py,sha256=
|
|
49
|
+
openedx_learning/apps/authoring/publishing/admin.py,sha256=nvAAl3Xswqqq3WyaI1NT7pLCcu1o-ynciJZOlc-9L24,16244
|
|
40
50
|
openedx_learning/apps/authoring/publishing/api.py,sha256=BQ5Q0JsSRBmb8ZRKzUlEOOV6QxNUjYkkqCjxmnX9O48,55565
|
|
41
51
|
openedx_learning/apps/authoring/publishing/apps.py,sha256=v9PTe3YoICaYT9wfu268ZkVAlnZFvxi-DqYdbRi25bY,750
|
|
42
52
|
openedx_learning/apps/authoring/publishing/contextmanagers.py,sha256=AH5zhr0Tz_gUG9--dfr_oZAu8DMy94n6mnOJuPbWkeU,6723
|
|
@@ -55,21 +65,24 @@ openedx_learning/apps/authoring/publishing/models/draft_log.py,sha256=7hpbtnnc3y
|
|
|
55
65
|
openedx_learning/apps/authoring/publishing/models/entity_list.py,sha256=8MyJqDdC8dqJY-N9UAu-WS-ZeFOYuRNMKloSY9wMH1w,3042
|
|
56
66
|
openedx_learning/apps/authoring/publishing/models/learning_package.py,sha256=1fuNLHD6k0qGuL0jXYGf4-TA5WczgxJrXUdAIM_JNBI,2688
|
|
57
67
|
openedx_learning/apps/authoring/publishing/models/publish_log.py,sha256=QD7Fb00yUMWM4HHae_m60JW-TDX8upbQdWCEIdhB0yE,5615
|
|
58
|
-
openedx_learning/apps/authoring/publishing/models/publishable_entity.py,sha256
|
|
68
|
+
openedx_learning/apps/authoring/publishing/models/publishable_entity.py,sha256=ncks-eNn5h-b1FjWPMAYtnuiMSvFYHwsGeNWGJA8GBE,25773
|
|
59
69
|
openedx_learning/apps/authoring/sections/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
60
|
-
openedx_learning/apps/authoring/sections/
|
|
61
|
-
openedx_learning/apps/authoring/sections/
|
|
70
|
+
openedx_learning/apps/authoring/sections/admin.py,sha256=OQOTtXYM-Zj8BBb1wNBkOxgkF2Pv3JdUrZ45VOEmThM,1757
|
|
71
|
+
openedx_learning/apps/authoring/sections/api.py,sha256=qfT29Vgcc-1d3-BTiKY_a5M9H7oFBXVD8KqWe-1OqM0,10323
|
|
72
|
+
openedx_learning/apps/authoring/sections/apps.py,sha256=h2-1csh-f3tLkauxXvn1xvi_P-l-_oe819XYO6BIYm4,738
|
|
62
73
|
openedx_learning/apps/authoring/sections/models.py,sha256=2GK-dDMJwNRw_9gNFho8iKcDV-iYz_zBzqGMDmQ_jbc,1450
|
|
63
74
|
openedx_learning/apps/authoring/sections/migrations/0001_initial.py,sha256=iW5AFhC26mfZNWEVNu8cTsr32Ca4htL4CUanHoXfaeY,1152
|
|
64
75
|
openedx_learning/apps/authoring/sections/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
65
76
|
openedx_learning/apps/authoring/subsections/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
66
|
-
openedx_learning/apps/authoring/subsections/
|
|
77
|
+
openedx_learning/apps/authoring/subsections/admin.py,sha256=vPfOeTzh10aRhtZjXAzYsbwfw4Hc5yuySbpjAvtDH98,1795
|
|
78
|
+
openedx_learning/apps/authoring/subsections/api.py,sha256=7heZHREJy4t0L8b6mOLtpi0JxLjqsJ7sHQFZc8z07a4,10229
|
|
67
79
|
openedx_learning/apps/authoring/subsections/apps.py,sha256=WueCaPOE-7x3cu-6rA9FdeKzipCZSNIhvqpAbxTysOg,773
|
|
68
80
|
openedx_learning/apps/authoring/subsections/models.py,sha256=1uhdpS9Eg6keSqkzQaE8-XSVLAQlmi0llIIU2V7Nl44,1492
|
|
69
81
|
openedx_learning/apps/authoring/subsections/migrations/0001_initial.py,sha256=7kEHIC-EwG2KvlW4hg5tnl45--dW4Yv5gqV5SDqNYNo,1158
|
|
70
82
|
openedx_learning/apps/authoring/subsections/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
71
83
|
openedx_learning/apps/authoring/units/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
72
|
-
openedx_learning/apps/authoring/units/
|
|
84
|
+
openedx_learning/apps/authoring/units/admin.py,sha256=chp-bTfufBiQ3uycVF1DBEFSPvwXaROJnyyY8AaH_yw,1717
|
|
85
|
+
openedx_learning/apps/authoring/units/api.py,sha256=Bk6CndTU3sO0SvAphEl5UWKGBkWD_TE9p12BXBxxvLQ,9767
|
|
73
86
|
openedx_learning/apps/authoring/units/apps.py,sha256=cIzphjDw5sjIZ3NLE911N7IMUa8JQSXMReNl03uI7jg,701
|
|
74
87
|
openedx_learning/apps/authoring/units/models.py,sha256=eTOwFWC9coQLf0ovx08Mj7zi8mPAWCw9QOznybajRk0,1418
|
|
75
88
|
openedx_learning/apps/authoring/units/migrations/0001_initial.py,sha256=qM_0JGffxECVgXzncHXfgSE-g8u3L3a14R0M1Bnj_Ys,1129
|
|
@@ -80,14 +93,14 @@ openedx_learning/contrib/media_server/apps.py,sha256=GicFBN3N6wzVs5i3RgrQFJZeMlS
|
|
|
80
93
|
openedx_learning/contrib/media_server/urls.py,sha256=newNjV41sM9A9Oy_rgnZSXdkTFxSHiupIiAsVIGE2CE,365
|
|
81
94
|
openedx_learning/contrib/media_server/views.py,sha256=qZPhdEW_oYj1MEdgLVP6Cq3tRiZtp7dTb7ASaSKZ2HY,1350
|
|
82
95
|
openedx_learning/lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
83
|
-
openedx_learning/lib/admin_utils.py,sha256=
|
|
96
|
+
openedx_learning/lib/admin_utils.py,sha256=dQ02NrgXIJL6kx9LFBgEYc1Pr_xtFdr7NVXx01VOIE0,4514
|
|
84
97
|
openedx_learning/lib/cache.py,sha256=ppT36KiPLdsAF3GfZCF0IdiHodckd2gLiF1sNhjSJuk,958
|
|
85
98
|
openedx_learning/lib/collations.py,sha256=f65575r3BfAvFWU6pdBMsqrxPwFijB2SbJtDXq4UVc4,2401
|
|
86
99
|
openedx_learning/lib/fields.py,sha256=eiGoXMPhRuq25EH2qf6BAODshAQE3DBVdIYAMIUAXW0,7522
|
|
87
100
|
openedx_learning/lib/managers.py,sha256=-Q3gxalSqyPZ9Im4DTROW5tF8wVTZLlmfTe62_xmowY,1643
|
|
88
101
|
openedx_learning/lib/test_utils.py,sha256=g3KLuepIZbaDBCsaj9711YuqyUx7LD4gXDcfNC-mWdc,527
|
|
89
102
|
openedx_learning/lib/validators.py,sha256=iqEdEAvFV2tC7Ecssx69kjecpdU8nE87AlDJYrqrsnc,404
|
|
90
|
-
openedx_learning-0.
|
|
103
|
+
openedx_learning-0.27.0.dist-info/licenses/LICENSE.txt,sha256=QTW2QN7q3XszgUAXm9Dzgtu5LXYKbR1SGnqMa7ufEuY,35139
|
|
91
104
|
openedx_tagging/__init__.py,sha256=V9N8M7f9LYlAbA_DdPUsHzTnWjYRXKGa5qHw9P1JnNI,30
|
|
92
105
|
openedx_tagging/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
93
106
|
openedx_tagging/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -129,7 +142,7 @@ openedx_tagging/core/tagging/migrations/0017_alter_tagimporttask_status.py,sha25
|
|
|
129
142
|
openedx_tagging/core/tagging/migrations/0018_objecttag_is_copied.py,sha256=zmr4b65T0vX6fYc8MpvSmQnYkAiNMpx3RKEd5tudsl8,517
|
|
130
143
|
openedx_tagging/core/tagging/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
131
144
|
openedx_tagging/core/tagging/models/__init__.py,sha256=yYdOnthuc7EUdfEULtZgqRwn5Y4bbYQmJCjVZqR5GTM,236
|
|
132
|
-
openedx_tagging/core/tagging/models/base.py,sha256=
|
|
145
|
+
openedx_tagging/core/tagging/models/base.py,sha256=ju4mvgRS_I2AgPsRf4sMFy6qle2i0aA0MbyBYZXf32g,39685
|
|
133
146
|
openedx_tagging/core/tagging/models/import_export.py,sha256=Aj0pleh0nh2LNS6zmdB1P4bpdgUMmvmobTkqBerORAI,4570
|
|
134
147
|
openedx_tagging/core/tagging/models/system_defined.py,sha256=_6LfvUZGEltvQMtm2OXy6TOLh3C8GnVTqtZDSAZW6K4,9062
|
|
135
148
|
openedx_tagging/core/tagging/models/utils.py,sha256=-A3Dj24twmTf65UB7G4WLvb_9qEvduEPIwahZ-FJDlg,1926
|
|
@@ -143,7 +156,7 @@ openedx_tagging/core/tagging/rest_api/v1/serializers.py,sha256=0HQD_Jrf6-YpocYfz
|
|
|
143
156
|
openedx_tagging/core/tagging/rest_api/v1/urls.py,sha256=dNUKCtUCx_YzrwlbEbpDfjGVQbb2QdJ1VuJCkladj6E,752
|
|
144
157
|
openedx_tagging/core/tagging/rest_api/v1/views.py,sha256=Hf92cy-tE767DE9FgsZcPKiCYrf5ihfETz8qGKBnuiU,36278
|
|
145
158
|
openedx_tagging/core/tagging/rest_api/v1/views_import.py,sha256=kbHUPe5A6WaaJ3J1lFIcYCt876ecLNQfd19m7YYub6c,1470
|
|
146
|
-
openedx_learning-0.
|
|
147
|
-
openedx_learning-0.
|
|
148
|
-
openedx_learning-0.
|
|
149
|
-
openedx_learning-0.
|
|
159
|
+
openedx_learning-0.27.0.dist-info/METADATA,sha256=aj1eSVrsZiti6xVUZKZONiQbNPv-5tD8AbAkiOKbgvk,9055
|
|
160
|
+
openedx_learning-0.27.0.dist-info/WHEEL,sha256=JNWh1Fm1UdwIQV075glCn4MVuCRs0sotJIq-J6rbxCU,109
|
|
161
|
+
openedx_learning-0.27.0.dist-info/top_level.txt,sha256=IYFbr5mgiEHd-LOtZmXj3q3a0bkGK1M9LY7GXgnfi4M,33
|
|
162
|
+
openedx_learning-0.27.0.dist-info/RECORD,,
|
|
@@ -509,7 +509,7 @@ class Taxonomy(models.Model):
|
|
|
509
509
|
count=models.Func(F('id'), function='Count')
|
|
510
510
|
)
|
|
511
511
|
qs = qs.annotate(usage_count=models.Subquery(obj_tags.values('count')))
|
|
512
|
-
return qs
|
|
512
|
+
return qs # type: ignore[return-value]
|
|
513
513
|
|
|
514
514
|
def _get_filtered_tags_deep(
|
|
515
515
|
self,
|
{openedx_learning-0.26.0.dist-info → openedx_learning-0.27.0.dist-info}/licenses/LICENSE.txt
RENAMED
|
File without changes
|
|
File without changes
|