Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
2e9cc4c
feat: track publishing dependencies and side-effects
ormsbee Apr 18, 2025
0af01c0
temp: linter fixups
ormsbee Sep 3, 2025
00e6ea6
temp: remove redundant early return, add comments
ormsbee Sep 3, 2025
a74d8b1
refactor: models part of the refactoring of putting the dependency da…
ormsbee Oct 2, 2025
dee35da
temp: transitional state to deps in logs
ormsbee Oct 17, 2025
65793f0
temp: mostly through refactor to putting hashes on the log records
ormsbee Oct 28, 2025
9d9ec94
temp: all the tests pass, finally
ormsbee Oct 29, 2025
3adc0cc
temp: fixups, remove some old comments
ormsbee Oct 29, 2025
2778e97
fix: typo in comments
ormsbee Oct 29, 2025
a8b053a
temp: comment tweaks
ormsbee Oct 29, 2025
69e3eeb
temp: Merge remote-tracking branch 'origin/main' into simpler-side-ef…
ormsbee Oct 29, 2025
4efa131
temp: minor renames
ormsbee Oct 29, 2025
23c04e5
fix: dependency draft search was broken before this
ormsbee Oct 29, 2025
42822ea
fix: quality and admin fixes
ormsbee Oct 29, 2025
bb073e8
refactor: rename dependency model fields for clarity, regenerate migr…
ormsbee Oct 29, 2025
a09cdab
temp: tried to fix up the docstring around set_version_dependencies t…
ormsbee Oct 29, 2025
5bf9862
refactor: more field name tweaking
ormsbee Oct 29, 2025
f23fd02
feat: add data migration for dependencies
ormsbee Oct 30, 2025
4ce7dbb
temp: cleanup
ormsbee Oct 30, 2025
1a858f6
temp: lint fixups
ormsbee Oct 30, 2025
b2d1a7d
temp: lint fixes and version bump
ormsbee Oct 30, 2025
898be1e
fix: version constraint was overly conservative
ormsbee Oct 30, 2025
3e5c507
temp: re-enable pylint (cough)
ormsbee Oct 30, 2025
be1d376
temp: small renaming and comment fixes from Kyle's reviews
ormsbee Nov 3, 2025
6673a69
temp: update openedx_learning/apps/authoring/publishing/api.py
ormsbee Nov 4, 2025
764c98e
temp: comment fixups based on review comments
ormsbee Nov 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion openedx_learning/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
Open edX Learning ("Learning Core").
"""

__version__ = "0.29.1"
__version__ = "0.30.0"
122 changes: 110 additions & 12 deletions openedx_learning/apps/authoring/publishing/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import functools

from django.contrib import admin
from django.db.models import Count
from django.db.models import Count, F
from django.utils.html import format_html
from django.utils.safestring import SafeText

Expand All @@ -21,6 +21,7 @@
EntityListRow,
LearningPackage,
PublishableEntity,
PublishableEntityVersion,
PublishLog,
PublishLogRecord,
)
Expand Down Expand Up @@ -48,6 +49,7 @@ class PublishLogRecordTabularInline(admin.TabularInline):
"title",
"old_version_num",
"new_version_num",
"dependencies_hash_digest",
)
readonly_fields = fields

Expand Down Expand Up @@ -89,28 +91,89 @@ class PublishLogAdmin(ReadOnlyModelAdmin):
list_filter = ["learning_package"]


class PublishableEntityVersionTabularInline(admin.TabularInline):
"""
Tabular inline for a single Draft change.
"""
model = PublishableEntityVersion

fields = (
"version_num",
"title",
"created",
"created_by",
"dependencies_list",
)
readonly_fields = fields

def dependencies_list(self, version: PublishableEntityVersion):
identifiers = sorted(
[str(dep.key) for dep in version.dependencies.all()]
)
return "\n".join(identifiers)

def get_queryset(self, request):
queryset = super().get_queryset(request)
return (
queryset
.order_by('-version_num')
.select_related('created_by', 'entity')
.prefetch_related('dependencies')
)


class PublishStatusFilter(admin.SimpleListFilter):
"""
Custom filter for entities that have unpublished changes.
"""
title = "publish status"
parameter_name = "publish_status"

def lookups(self, request, model_admin):
return [
("unpublished_changes", "Has unpublished changes"),
]

def queryset(self, request, queryset):
if self.value() == "unpublished_changes":
return (
queryset
.exclude(
published__version__isnull=True,
draft__version__isnull=True,
)
.exclude(
published__version=F("draft__version"),
published__dependencies_hash_digest=F("draft__dependencies_hash_digest")
)
)
return queryset


@admin.register(PublishableEntity)
class PublishableEntityAdmin(ReadOnlyModelAdmin):
"""
Read-only admin view for Publishable Entities
"""
inlines = [PublishableEntityVersionTabularInline]

list_display = [
"key",
"draft_version",
"published_version",
"draft_version",
"uuid",
"learning_package",
"created",
"created_by",
"can_stand_alone",
]
list_filter = ["learning_package"]
list_filter = ["learning_package", PublishStatusFilter]
search_fields = ["key", "uuid"]

fields = [
"key",
"draft_version",
"published_version",
"draft_version",
"uuid",
"learning_package",
"created",
Expand All @@ -120,8 +183,8 @@ class PublishableEntityAdmin(ReadOnlyModelAdmin):
]
readonly_fields = [
"key",
"draft_version",
"published_version",
"draft_version",
"uuid",
"learning_package",
"created",
Expand All @@ -130,21 +193,55 @@ class PublishableEntityAdmin(ReadOnlyModelAdmin):
"can_stand_alone",
]

def draft_version(self, entity: PublishableEntity):
return entity.draft.version.version_num if entity.draft.version else None

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

def get_queryset(self, request):
queryset = super().get_queryset(request)
return queryset.select_related(
"learning_package", "published__version",
"learning_package", "published__version", "draft__version", "created_by"
)

def see_also(self, entity):
return one_to_one_related_model_html(entity)

def draft_version(self, entity: PublishableEntity):
"""
Version num + dependency hash if applicable, e.g. "5" or "5 (825064c2)"

If the version info is different from the published version, we
italicize the text for emphasis.
"""
if hasattr(entity, "draft") and entity.draft.version:
draft_log_record = entity.draft.draft_log_record
if draft_log_record and draft_log_record.dependencies_hash_digest:
version_str = (
f"{entity.draft.version.version_num} "
f"({draft_log_record.dependencies_hash_digest})"
)
else:
version_str = str(entity.draft.version.version_num)

if version_str == self.published_version(entity):
return version_str
else:
return format_html("<em>{}</em>", version_str)

return None

def published_version(self, entity: PublishableEntity):
"""
Version num + dependency hash if applicable, e.g. "5" or "5 (825064c2)"
"""
if hasattr(entity, "published") and entity.published.version:
publish_log_record = entity.published.publish_log_record
if publish_log_record.dependencies_hash_digest:
return (
f"{entity.published.version.version_num} "
f"({publish_log_record.dependencies_hash_digest})"
)
else:
return str(entity.published.version.version_num)

return None


@admin.register(Published)
class PublishedAdmin(ReadOnlyModelAdmin):
Expand Down Expand Up @@ -197,6 +294,7 @@ class DraftChangeLogRecordTabularInline(admin.TabularInline):
"title",
"old_version_num",
"new_version_num",
"dependencies_hash_digest",
)
readonly_fields = fields

Expand Down
Loading