Skip to content

Commit 2e9cc4c

Browse files
committed
feat: track publishing dependencies and side-effects
Create the PublishSideEffect model as the publishing analog for the DraftSideEffect model, and create these side effects for parent containers when one of the children is published. This allows us to efficiently query when any subtree of content was affected by a publish. Also introduces PublishableEntityVersionDependency as a way of tracking unpinned dependencies of a version, e.g. the children of a version of a Unit. Also introduces a new dependencies_hash_digest field to Draft and Published models to track dependency state. This PR adds Django admin functionality for this new data, as well as optimizing some existing calls that used to have to do more complex container -> entity list traversal.
1 parent 11876a5 commit 2e9cc4c

15 files changed

Lines changed: 1204 additions & 223 deletions

File tree

openedx_learning/apps/authoring/publishing/admin.py

Lines changed: 99 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import functools
77

88
from django.contrib import admin
9-
from django.db.models import Count
9+
from django.db.models import Count, F, Q
1010
from django.utils.html import format_html
1111
from django.utils.safestring import SafeText
1212

@@ -21,6 +21,8 @@
2121
EntityListRow,
2222
LearningPackage,
2323
PublishableEntity,
24+
PublishableEntityVersion,
25+
PublishableEntityVersionDependency,
2426
PublishLog,
2527
PublishLogRecord,
2628
)
@@ -89,28 +91,84 @@ class PublishLogAdmin(ReadOnlyModelAdmin):
8991
list_filter = ["learning_package"]
9092

9193

94+
class PublishableEntityVersionTabularInline(admin.TabularInline):
95+
"""
96+
Tabular inline for a single Draft change.
97+
"""
98+
model = PublishableEntityVersion
99+
100+
fields = (
101+
"version_num",
102+
"title",
103+
"created",
104+
"created_by",
105+
"dependencies_list",
106+
)
107+
readonly_fields = fields
108+
109+
def dependencies_list(self, version: PublishableEntityVersion):
110+
identifiers = sorted(
111+
[str(dep.key) for dep in version.dependencies.all()]
112+
)
113+
return "\n".join(identifiers)
114+
115+
def get_queryset(self, request):
116+
queryset = super().get_queryset(request)
117+
return (
118+
queryset
119+
.order_by('-version_num')
120+
.select_related('created_by', 'entity')
121+
.prefetch_related('dependencies')
122+
)
123+
124+
class PublishStatusFilter(admin.SimpleListFilter):
125+
title = "publish status"
126+
parameter_name = "publish_status"
127+
128+
def lookups(self, request, model_admin):
129+
return [
130+
("unpublished_changes", "Has unpublished changes"),
131+
]
132+
133+
def queryset(self, request, queryset):
134+
if self.value() == "unpublished_changes":
135+
return (
136+
queryset
137+
.exclude(
138+
published__version__isnull=True,
139+
draft__version__isnull=True,
140+
)
141+
.exclude(
142+
published__version=F("draft__version"),
143+
published__dependencies_hash_digest=F("draft__dependencies_hash_digest")
144+
)
145+
)
146+
147+
92148
@admin.register(PublishableEntity)
93149
class PublishableEntityAdmin(ReadOnlyModelAdmin):
94150
"""
95151
Read-only admin view for Publishable Entities
96152
"""
153+
inlines = [PublishableEntityVersionTabularInline]
154+
97155
list_display = [
98156
"key",
99-
"draft_version",
100157
"published_version",
158+
"draft_version",
101159
"uuid",
102160
"learning_package",
103161
"created",
104162
"created_by",
105163
"can_stand_alone",
106164
]
107-
list_filter = ["learning_package"]
165+
list_filter = ["learning_package", PublishStatusFilter]
108166
search_fields = ["key", "uuid"]
109167

110168
fields = [
111169
"key",
112-
"draft_version",
113170
"published_version",
171+
"draft_version",
114172
"uuid",
115173
"learning_package",
116174
"created",
@@ -120,8 +178,8 @@ class PublishableEntityAdmin(ReadOnlyModelAdmin):
120178
]
121179
readonly_fields = [
122180
"key",
123-
"draft_version",
124181
"published_version",
182+
"draft_version",
125183
"uuid",
126184
"learning_package",
127185
"created",
@@ -131,20 +189,54 @@ class PublishableEntityAdmin(ReadOnlyModelAdmin):
131189
]
132190

133191
def draft_version(self, entity: PublishableEntity):
134-
return entity.draft.version.version_num if entity.draft.version else None
192+
from django.utils.html import format_html
193+
194+
if hasattr(entity, "draft") and entity.draft.version:
195+
if entity.draft.dependencies_hash_digest:
196+
version_str = (
197+
f"{entity.draft.version.version_num} "
198+
f"({entity.draft.dependencies_hash_digest})"
199+
)
200+
else:
201+
version_str = str(entity.draft.version.version_num)
202+
203+
if version_str == self.published_version(entity):
204+
return version_str
205+
else:
206+
return format_html("<em>{}</em>", version_str)
207+
208+
return None
135209

136210
def published_version(self, entity: PublishableEntity):
137211
return entity.published.version.version_num if entity.published and entity.published.version else None
138212

139213
def get_queryset(self, request):
140214
queryset = super().get_queryset(request)
141215
return queryset.select_related(
142-
"learning_package", "published__version",
216+
"learning_package", "published__version", "draft__version", "created_by"
143217
)
144218

145219
def see_also(self, entity):
146220
return one_to_one_related_model_html(entity)
147221

222+
def published_version(self, entity):
223+
if entity.published.version:
224+
return entity.published.version.version_num
225+
return None
226+
227+
def published_version(self, entity: PublishableEntity):
228+
if hasattr(entity, "published") and entity.published.version:
229+
if entity.published.dependencies_hash_digest:
230+
return (
231+
f"{entity.published.version.version_num} "
232+
f"({entity.published.dependencies_hash_digest})"
233+
)
234+
else:
235+
return str(entity.published.version.version_num)
236+
237+
return None
238+
239+
148240

149241
@admin.register(Published)
150242
class PublishedAdmin(ReadOnlyModelAdmin):

0 commit comments

Comments
 (0)