openedx-learning 0.20.0__py2.py3-none-any.whl → 0.23.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/collections/api.py +46 -1
- openedx_learning/apps/authoring/components/api.py +1 -55
- openedx_learning/apps/authoring/publishing/admin.py +76 -1
- openedx_learning/apps/authoring/publishing/api.py +417 -76
- openedx_learning/apps/authoring/publishing/contextmanagers.py +157 -0
- openedx_learning/apps/authoring/publishing/migrations/0006_draftchangelog.py +68 -0
- openedx_learning/apps/authoring/publishing/migrations/0007_bootstrap_draftchangelog.py +94 -0
- openedx_learning/apps/authoring/publishing/models/__init__.py +2 -2
- openedx_learning/apps/authoring/publishing/models/draft_log.py +315 -0
- openedx_learning/apps/authoring/publishing/models/publish_log.py +44 -0
- openedx_learning/apps/authoring/units/api.py +23 -24
- {openedx_learning-0.20.0.dist-info → openedx_learning-0.23.0.dist-info}/METADATA +5 -5
- {openedx_learning-0.20.0.dist-info → openedx_learning-0.23.0.dist-info}/RECORD +17 -14
- openedx_learning/apps/authoring/publishing/models/draft_published.py +0 -95
- {openedx_learning-0.20.0.dist-info → openedx_learning-0.23.0.dist-info}/WHEEL +0 -0
- {openedx_learning-0.20.0.dist-info → openedx_learning-0.23.0.dist-info}/licenses/LICENSE.txt +0 -0
- {openedx_learning-0.20.0.dist-info → openedx_learning-0.23.0.dist-info}/top_level.txt +0 -0
openedx_learning/__init__.py
CHANGED
|
@@ -10,7 +10,7 @@ from django.db.models import QuerySet
|
|
|
10
10
|
|
|
11
11
|
from ..publishing import api as publishing_api
|
|
12
12
|
from ..publishing.models import PublishableEntity
|
|
13
|
-
from .models import Collection
|
|
13
|
+
from .models import Collection, CollectionPublishableEntity
|
|
14
14
|
|
|
15
15
|
# The public API that will be re-exported by openedx_learning.apps.authoring.api
|
|
16
16
|
# is listed in the __all__ entries below. Internal helper functions that are
|
|
@@ -27,6 +27,7 @@ __all__ = [
|
|
|
27
27
|
"remove_from_collection",
|
|
28
28
|
"restore_collection",
|
|
29
29
|
"update_collection",
|
|
30
|
+
"set_collections",
|
|
30
31
|
]
|
|
31
32
|
|
|
32
33
|
|
|
@@ -204,3 +205,47 @@ def get_collections(learning_package_id: int, enabled: bool | None = True) -> Qu
|
|
|
204
205
|
if enabled is not None:
|
|
205
206
|
qs = qs.filter(enabled=enabled)
|
|
206
207
|
return qs.select_related("learning_package").order_by('pk')
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def set_collections(
|
|
211
|
+
publishable_entity: PublishableEntity,
|
|
212
|
+
collection_qset: QuerySet[Collection],
|
|
213
|
+
created_by: int | None = None,
|
|
214
|
+
) -> set[Collection]:
|
|
215
|
+
"""
|
|
216
|
+
Set collections for a given publishable entity.
|
|
217
|
+
|
|
218
|
+
These Collections must belong to the same LearningPackage as the PublishableEntity,
|
|
219
|
+
or a ValidationError will be raised.
|
|
220
|
+
|
|
221
|
+
Modified date of all collections related to entity is updated.
|
|
222
|
+
|
|
223
|
+
Returns the updated collections.
|
|
224
|
+
"""
|
|
225
|
+
# Disallow adding entities outside the collection's learning package
|
|
226
|
+
if collection_qset.exclude(learning_package_id=publishable_entity.learning_package_id).count():
|
|
227
|
+
raise ValidationError(
|
|
228
|
+
"Collection entities must be from the same learning package as the collection.",
|
|
229
|
+
)
|
|
230
|
+
current_relations = CollectionPublishableEntity.objects.filter(
|
|
231
|
+
entity=publishable_entity
|
|
232
|
+
).select_related('collection')
|
|
233
|
+
# Clear other collections for given entity and add only new collections from collection_qset
|
|
234
|
+
removed_collections = set(
|
|
235
|
+
r.collection for r in current_relations.exclude(collection__in=collection_qset)
|
|
236
|
+
)
|
|
237
|
+
new_collections = set(collection_qset.exclude(
|
|
238
|
+
id__in=current_relations.values_list('collection', flat=True)
|
|
239
|
+
))
|
|
240
|
+
# Triggers a m2m_changed signal
|
|
241
|
+
publishable_entity.collections.set(
|
|
242
|
+
objs=collection_qset,
|
|
243
|
+
through_defaults={"created_by_id": created_by},
|
|
244
|
+
)
|
|
245
|
+
# Update modified date via update to avoid triggering post_save signal for all collections, which can be very slow.
|
|
246
|
+
affected_collection = removed_collections | new_collections
|
|
247
|
+
Collection.objects.filter(
|
|
248
|
+
id__in=[collection.id for collection in affected_collection]
|
|
249
|
+
).update(modified=datetime.now(tz=timezone.utc))
|
|
250
|
+
|
|
251
|
+
return affected_collection
|
|
@@ -13,18 +13,16 @@ are stored in this app.
|
|
|
13
13
|
from __future__ import annotations
|
|
14
14
|
|
|
15
15
|
import mimetypes
|
|
16
|
-
from datetime import datetime
|
|
16
|
+
from datetime import datetime
|
|
17
17
|
from enum import StrEnum, auto
|
|
18
18
|
from logging import getLogger
|
|
19
19
|
from pathlib import Path
|
|
20
20
|
from uuid import UUID
|
|
21
21
|
|
|
22
|
-
from django.core.exceptions import ValidationError
|
|
23
22
|
from django.db.models import Q, QuerySet
|
|
24
23
|
from django.db.transaction import atomic
|
|
25
24
|
from django.http.response import HttpResponse, HttpResponseNotFound
|
|
26
25
|
|
|
27
|
-
from ..collections.models import Collection, CollectionPublishableEntity
|
|
28
26
|
from ..contents import api as contents_api
|
|
29
27
|
from ..publishing import api as publishing_api
|
|
30
28
|
from .models import Component, ComponentType, ComponentVersion, ComponentVersionContent
|
|
@@ -51,7 +49,6 @@ __all__ = [
|
|
|
51
49
|
"look_up_component_version_content",
|
|
52
50
|
"AssetError",
|
|
53
51
|
"get_redirect_response_for_component_asset",
|
|
54
|
-
"set_collections",
|
|
55
52
|
]
|
|
56
53
|
|
|
57
54
|
|
|
@@ -605,54 +602,3 @@ def get_redirect_response_for_component_asset(
|
|
|
605
602
|
)
|
|
606
603
|
|
|
607
604
|
return HttpResponse(headers={**info_headers, **redirect_headers})
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
def set_collections(
|
|
611
|
-
learning_package_id: int,
|
|
612
|
-
component: Component,
|
|
613
|
-
collection_qset: QuerySet[Collection],
|
|
614
|
-
created_by: int | None = None,
|
|
615
|
-
) -> set[Collection]:
|
|
616
|
-
"""
|
|
617
|
-
Set collections for a given component.
|
|
618
|
-
|
|
619
|
-
These Collections must belong to the same LearningPackage as the Component, or a ValidationError will be raised.
|
|
620
|
-
|
|
621
|
-
Modified date of all collections related to component is updated.
|
|
622
|
-
|
|
623
|
-
Returns the updated collections.
|
|
624
|
-
"""
|
|
625
|
-
# Disallow adding entities outside the collection's learning package
|
|
626
|
-
invalid_collection = collection_qset.exclude(learning_package_id=learning_package_id).first()
|
|
627
|
-
if invalid_collection:
|
|
628
|
-
raise ValidationError(
|
|
629
|
-
f"Cannot add collection {invalid_collection.pk} in learning package "
|
|
630
|
-
f"{invalid_collection.learning_package_id} to component {component} in "
|
|
631
|
-
f"learning package {learning_package_id}."
|
|
632
|
-
)
|
|
633
|
-
current_relations = CollectionPublishableEntity.objects.filter(
|
|
634
|
-
entity=component.publishable_entity
|
|
635
|
-
).select_related('collection')
|
|
636
|
-
# Clear other collections for given component and add only new collections from collection_qset
|
|
637
|
-
removed_collections = set(
|
|
638
|
-
r.collection for r in current_relations.exclude(collection__in=collection_qset)
|
|
639
|
-
)
|
|
640
|
-
new_collections = set(collection_qset.exclude(
|
|
641
|
-
id__in=current_relations.values_list('collection', flat=True)
|
|
642
|
-
))
|
|
643
|
-
# Use `remove` instead of `CollectionPublishableEntity.delete()` to trigger m2m_changed signal which will handle
|
|
644
|
-
# updating component index.
|
|
645
|
-
component.publishable_entity.collections.remove(*removed_collections)
|
|
646
|
-
component.publishable_entity.collections.add(
|
|
647
|
-
*new_collections,
|
|
648
|
-
through_defaults={"created_by_id": created_by},
|
|
649
|
-
)
|
|
650
|
-
# Update modified date via update to avoid triggering post_save signal for collections
|
|
651
|
-
# The signal triggers index update for each collection synchronously which will be very slow in this case.
|
|
652
|
-
# Instead trigger the index update in the caller function asynchronously.
|
|
653
|
-
affected_collection = removed_collections | new_collections
|
|
654
|
-
Collection.objects.filter(
|
|
655
|
-
id__in=[collection.id for collection in affected_collection]
|
|
656
|
-
).update(modified=datetime.now(tz=timezone.utc))
|
|
657
|
-
|
|
658
|
-
return affected_collection
|
|
@@ -4,10 +4,19 @@ Django admin for publishing models
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
6
|
from django.contrib import admin
|
|
7
|
+
from django.db.models import Count
|
|
7
8
|
|
|
8
9
|
from openedx_learning.lib.admin_utils import ReadOnlyModelAdmin, one_to_one_related_model_html
|
|
9
10
|
|
|
10
|
-
from .models import
|
|
11
|
+
from .models import (
|
|
12
|
+
DraftChangeLog,
|
|
13
|
+
DraftChangeLogRecord,
|
|
14
|
+
LearningPackage,
|
|
15
|
+
PublishableEntity,
|
|
16
|
+
PublishLog,
|
|
17
|
+
PublishLogRecord,
|
|
18
|
+
)
|
|
19
|
+
from .models.publish_log import Published
|
|
11
20
|
|
|
12
21
|
|
|
13
22
|
@admin.register(LearningPackage)
|
|
@@ -171,3 +180,69 @@ class PublishedAdmin(ReadOnlyModelAdmin):
|
|
|
171
180
|
|
|
172
181
|
def message(self, published_obj):
|
|
173
182
|
return published_obj.publish_log_record.publish_log.message
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class DraftChangeLogRecordTabularInline(admin.TabularInline):
|
|
186
|
+
"""
|
|
187
|
+
Tabular inline for a single Draft change.
|
|
188
|
+
"""
|
|
189
|
+
model = DraftChangeLogRecord
|
|
190
|
+
|
|
191
|
+
fields = (
|
|
192
|
+
"entity",
|
|
193
|
+
"title",
|
|
194
|
+
"old_version_num",
|
|
195
|
+
"new_version_num",
|
|
196
|
+
)
|
|
197
|
+
readonly_fields = fields
|
|
198
|
+
|
|
199
|
+
def get_queryset(self, request):
|
|
200
|
+
queryset = super().get_queryset(request)
|
|
201
|
+
return queryset.select_related("entity", "old_version", "new_version") \
|
|
202
|
+
.order_by("entity__key")
|
|
203
|
+
|
|
204
|
+
def old_version_num(self, draft_change: DraftChangeLogRecord):
|
|
205
|
+
if draft_change.old_version is None:
|
|
206
|
+
return "-"
|
|
207
|
+
return draft_change.old_version.version_num
|
|
208
|
+
|
|
209
|
+
def new_version_num(self, draft_change: DraftChangeLogRecord):
|
|
210
|
+
if draft_change.new_version is None:
|
|
211
|
+
return "-"
|
|
212
|
+
return draft_change.new_version.version_num
|
|
213
|
+
|
|
214
|
+
def title(self, draft_change: DraftChangeLogRecord):
|
|
215
|
+
"""
|
|
216
|
+
Get the title to display for the DraftChange
|
|
217
|
+
"""
|
|
218
|
+
if draft_change.new_version:
|
|
219
|
+
return draft_change.new_version.title
|
|
220
|
+
if draft_change.old_version:
|
|
221
|
+
return draft_change.old_version.title
|
|
222
|
+
return ""
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
@admin.register(DraftChangeLog)
|
|
226
|
+
class DraftChangeSetAdmin(ReadOnlyModelAdmin):
|
|
227
|
+
"""
|
|
228
|
+
Read-only admin to view Draft changes (via inline tables)
|
|
229
|
+
"""
|
|
230
|
+
inlines = [DraftChangeLogRecordTabularInline]
|
|
231
|
+
fields = (
|
|
232
|
+
"uuid",
|
|
233
|
+
"learning_package",
|
|
234
|
+
"num_changes",
|
|
235
|
+
"changed_at",
|
|
236
|
+
"changed_by",
|
|
237
|
+
)
|
|
238
|
+
readonly_fields = fields
|
|
239
|
+
list_display = fields
|
|
240
|
+
list_filter = ["learning_package"]
|
|
241
|
+
|
|
242
|
+
def num_changes(self, draft_change_set):
|
|
243
|
+
return draft_change_set.num_changes
|
|
244
|
+
|
|
245
|
+
def get_queryset(self, request):
|
|
246
|
+
queryset = super().get_queryset(request)
|
|
247
|
+
return queryset.select_related("learning_package", "changed_by") \
|
|
248
|
+
.annotate(num_changes=Count("records"))
|