Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ class LibraryHistoryEntry:
title: str # title at time of change
item_type: str
action: str # "created" | "edited" | "renamed" | "deleted"
old_version: int
new_version: int | None


@dataclass(frozen=True)
Expand Down
14 changes: 14 additions & 0 deletions openedx/core/djangoapps/content_libraries/api/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,12 +220,18 @@ def _contributor(user):
entries = []
for record in draft_change_records:
version = record.new_version if record.new_version is not None else record.old_version
# old_version is None only for the very first publish (entity had no prior published version)
old_version_num = record.old_version.version_num if record.old_version else 0
# new_version is None for soft-delete publishes (component deleted without a new draft version)
new_version_num = record.new_version.version_num if record.new_version else None
entries.append(LibraryHistoryEntry(
contributor=_contributor(record.draft_change_log.changed_by),
changed_at=record.draft_change_log.changed_at,
title=version.title if version is not None else "",
item_type=record.entity.component.component_type.name,
action=resolve_change_action(record.old_version, record.new_version),
old_version=old_version_num,
new_version=new_version_num,
))
return entries

Expand Down Expand Up @@ -351,12 +357,18 @@ def _contributor(user):
# Deleted components can't reach this endpoint, so new_version is always set.
# (Unlike containers — see get_library_container_publish_history_entries.)
assert record.new_version is not None # for satisfy the type check
# old_version is None only for the very first publish (entity had no prior published version)
old_version_num = record.old_version.version_num if record.old_version else 0
# new_version is None for soft-delete publishes (component deleted without a new draft version)
new_version_num = record.new_version.version_num if record.new_version else None
entries.append(LibraryHistoryEntry(
contributor=_contributor(record.draft_change_log.changed_by),
changed_at=record.draft_change_log.changed_at,
title=record.new_version.title,
item_type=record.entity.component.component_type.name,
action=resolve_change_action(record.old_version, record.new_version),
old_version=old_version_num,
new_version=new_version_num,
))
return entries

Expand Down Expand Up @@ -396,6 +408,8 @@ def get_library_component_creation_entry(
title=first_version.title,
item_type=component.component_type.name,
action="created",
old_version=0,
new_version=first_version.version_num,
)


Expand Down
14 changes: 14 additions & 0 deletions openedx/core/djangoapps/content_libraries/api/containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,13 +346,19 @@ def _contributor(user):
# Use the new version when available; fall back to the old version
# (e.g. for delete records where new_version is None).
version = record.new_version if record.new_version is not None else record.old_version
# old_version is None only for the very first publish (entity had no prior published version)
old_version_num = record.old_version.version_num if record.old_version else 0
# new_version is None for soft-delete publishes (container deleted without a new draft version)
new_version_num = record.new_version.version_num if record.new_version else None
item_type = get_entity_item_type(record.entity)
results.append(LibraryHistoryEntry(
contributor=_contributor(record.draft_change_log.changed_by),
changed_at=record.draft_change_log.changed_at,
title=version.title if version is not None else "",
item_type=item_type,
action=resolve_change_action(record.old_version, record.new_version),
old_version=old_version_num,
new_version=new_version_num,
))

# Return all entries sorted newest-first across the container and its children.
Expand Down Expand Up @@ -576,13 +582,19 @@ def _contributor(user):

for record in records:
version = record.new_version if record.new_version is not None else record.old_version
# old_version is None only for the very first publish (entity had no prior published version)
old_version_num = record.old_version.version_num if record.old_version else 0
# new_version is None for soft-delete publishes (component deleted without a new draft version)
new_version_num = record.new_version.version_num if record.new_version else None
item_type = get_entity_item_type(record.entity)
entries.append(LibraryHistoryEntry(
contributor=_contributor(record.draft_change_log.changed_by),
changed_at=record.draft_change_log.changed_at,
title=version.title if version is not None else "",
item_type=item_type,
action=resolve_change_action(record.old_version, record.new_version),
old_version=old_version_num,
new_version=new_version_num,
))

# Return entries sorted newest-first; use title as tiebreaker for determinism.
Expand Down Expand Up @@ -619,4 +631,6 @@ def get_library_container_creation_entry(
title=first_version.title,
item_type=container.container_type.type_code,
action="created",
old_version=0,
new_version=first_version.version_num,
)
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ class LibraryHistoryEntrySerializer(serializers.Serializer):
title = serializers.CharField(read_only=True)
item_type = serializers.CharField(read_only=True)
action = serializers.CharField(read_only=True)
old_version = serializers.IntegerField(read_only=True)
new_version = serializers.IntegerField(read_only=True, allow_null=True)


class UsageKeyV2Serializer(serializers.BaseSerializer):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -977,6 +977,8 @@ def test_draft_history_action_created(self):
history = self._get_block_draft_history(block_key)
assert len(history) >= 1
assert history[-1]["action"] == "created"
assert history[-1]["old_version"] == 0
assert history[-1]["new_version"] is not None

def test_draft_history_action_deleted(self):
"""
Expand Down Expand Up @@ -1153,6 +1155,25 @@ def test_publish_history_entries(self):
assert "changed_at" in entry
assert "title" in entry
assert "action" in entry
assert "old_version" in entry
assert "new_version" in entry

def test_draft_history_deleted_has_null_new_version(self):
"""
Deleted draft history entry exposes new_version as null.
"""
lib = self._create_library(slug="draft-hist-delete-null", title="Draft History Delete Null")
block = self._add_block_to_library(lib["id"], "problem", "prob1")
block_key = block["id"]

self._publish_library_block(block_key)
self._delete_library_block(block_key)

history = self._get_block_draft_history(block_key)
assert len(history) >= 1
assert history[0]["action"] == "deleted"
assert history[0]["old_version"] > 0
assert history[0]["new_version"] is None

def test_publish_history_entries_unknown_uuid(self):
"""
Expand Down Expand Up @@ -1436,6 +1457,8 @@ def test_creation_entry_returns_first_version(self):
assert entry is not None
assert entry["action"] == "created"
assert entry["item_type"] == "problem"
assert entry["old_version"] == 0
assert entry["new_version"] == 1
assert "changed_at" in entry
assert "title" in entry
assert "contributor" in entry
Expand Down Expand Up @@ -1492,6 +1515,8 @@ def test_container_creation_entry_returns_first_version(self):
assert entry is not None
assert entry["action"] == "created"
assert entry["item_type"] == "unit"
assert entry["old_version"] == 0
assert entry["new_version"] == 1
assert entry["title"] == "My Unit"
assert "changed_at" in entry
assert "contributor" in entry
Expand Down
Loading