diff --git a/src/openedx_content/applets/publishing/api.py b/src/openedx_content/applets/publishing/api.py index 07d0696b4..ae0c44c18 100644 --- a/src/openedx_content/applets/publishing/api.py +++ b/src/openedx_content/applets/publishing/api.py @@ -474,6 +474,8 @@ def publish_from_drafts( By default, this will also publish all dependencies (e.g. unpinned children) of the Drafts that are passed in. """ + if DraftChangeLogContext.get_active_draft_change_log(learning_package_id) is not None: + raise ValidationError("Cannot publish while in bulk_draft_changes_for().") if published_at is None: published_at = datetime.now(tz=timezone.utc) diff --git a/tests/openedx_content/applets/publishing/test_api.py b/tests/openedx_content/applets/publishing/test_api.py index 157c091a7..9600b1d0a 100644 --- a/tests/openedx_content/applets/publishing/test_api.py +++ b/tests/openedx_content/applets/publishing/test_api.py @@ -2533,3 +2533,39 @@ def test_create_version_rejects_cross_package_dependencies(self) -> None: created_by=None, dependencies=[entity_in_lp2.id], ) + + def test_publish_functions_rejected_inside_bulk_draft_changes_for(self) -> None: + """ + publish_all_drafts() and publish_from_drafts() must not be callable + from within a bulk_draft_changes_for() context. + + bulk_draft_changes_for() opens a DraftChangeLog for accumulating draft + edits; running a publish inside it mixes draft-change bookkeeping with + publish bookkeeping in the same atomic block, which corrupts the + ordering of DraftChangeLog vs. PublishLog records and can leave Drafts + and Published rows out of sync if the outer context later raises. + """ + entity = publishing_api.create_publishable_entity( + self.learning_package_1.id, + "entity_for_bulk_publish_check", + created=self.now, + created_by=None, + ) + publishing_api.create_publishable_entity_version( + entity.id, + version_num=1, + title="Entity v1", + created=self.now, + created_by=None, + ) + + with pytest.raises(ValidationError, match="Cannot publish while in bulk_draft_changes_for()."): + with publishing_api.bulk_draft_changes_for(self.learning_package_1.id): + publishing_api.publish_all_drafts(self.learning_package_1.id) + + with pytest.raises(ValidationError, match="Cannot publish while in bulk_draft_changes_for()."): + with publishing_api.bulk_draft_changes_for(self.learning_package_1.id): + publishing_api.publish_from_drafts( + self.learning_package_1.id, + Draft.objects.filter(entity__learning_package_id=self.learning_package_1.id), + )