fix(contentstore): publish duplicated direct-only subtrees in full#38317
fix(contentstore): publish duplicated direct-only subtrees in full#38317kingoftech-v01 wants to merge 1 commit intoopenedx:masterfrom
Conversation
When duplicating a Section or Subsection in Studio, the modulestore's per-item auto-publish uses blacklist=EXCLUDE_ALL, leaving the published branch with an empty children list for the newly duplicated container. The course_published signal then regenerates the outline from the published branch and silently drops the duplicated blocks until an unrelated edit triggers another publish. After duplicate_block has built the full subtree in the draft branch, issue a proper store.publish() (no blacklist) for the top-level duplicated block when it is in DIRECT_ONLY_CATEGORIES, so the published branch matches the draft before course_published fires. Leaf block duplications (verticals, components) are unaffected and remain draft-only, preserving unpublished unit drafts. Closes openedx#35535
|
Thanks for the pull request, @kingoftech-v01! This repository is currently maintained by Once you've gone through the following steps feel free to tag them in a comment and let them know that your changes are ready for engineering review. 🔘 Get product approvalIf you haven't already, check this list to see if your contribution needs to go through the product review process.
🔘 Provide contextTo help your reviewers and other members of the community understand the purpose and larger context of your changes, feel free to add as much of the following information to the PR description as you can:
🔘 Submit a signed contributor agreement (CLA)
If you've signed an agreement in the past, you may need to re-sign. Once you've signed the CLA, please allow 1 business day for it to be processed. 🔘 Get a green buildIf one or more checks are failing, continue working on your changes until this is no longer the case and your build turns green. DetailsWhere can I find more information?If you'd like to get more details on all aspects of the review process for open source pull requests (OSPRs), check out the following resources: When can I expect my changes to be merged?Our goal is to get community contributions seen and reviewed as efficiently as possible. However, the amount of time that it takes to review and merge a PR can vary significantly based on factors such as:
💡 As a result it may take up to several weeks or months to complete a review and merge your PR. |
Description
Duplicating a Section or Subsection in Studio leaves the newly duplicated blocks silently missing from the generated course outline — they only appear after an unrelated edit forces another publish.
User impact (Course Author): Duplicated sections/subsections appear in the learner-facing outline immediately, with their full child tree.
Root Cause
In
cms/djangoapps/contentstore/utils.py:duplicate_block():store.create_item(...)triggers split modulestore's_auto_publish_no_children, which callspublish(..., blacklist=EXCLUDE_ALL). The published block is created withchildren=[].store.update_item(dest_block, ...)triggers another_auto_publish_no_childrenwithEXCLUDE_ALL._copy_subdagguards at line 3133 skip copying children to the published branch.bulk_operationsexit,course_publishedfires →update_outline_from_modulestore_taskreads the published branch → sees an empty duplicated section → silently drops it from the outline.publish()withoutEXCLUDE_ALL, which finally copies the subtree to the published branch.EXCLUDE_ALLwas added by Mathew Peterson on 2014-07-21 (commit9a039e93ec, LMS-11017) for an important reason: it prevents a container save from cascading into an unwanted publish of separately-edited unpublished unit drafts. We must NOT remove it from_auto_publish_no_children.Fix
Scope the fix to the call site. After
duplicate_block()has assembled the full subtree in the draft branch, issue one explicitstore.publish(dest_block.location, user.id)(no blacklist) only when:DIRECT_ONLY_CATEGORIES(sections, subsections — not verticals/components), ANDis_child=False).This leaves leaf duplication (verticals, components) draft-only, preserving the 2014 protection against stomping unpublished component drafts. The new publish happens inside the same
bulk_operationscontext, socourse_publishedstill fires exactly once.Supporting Information
EXCLUDE_ALLintent: commit9a039e93ec(LMS-11017, 2014-07-21, Mathew Peterson) andabbfa95e4c(LMS-11168, Nimisha Asthagiri) — designed to protect unpublished unit content from cascading publishes.DIRECT_ONLY_CATEGORIES = ['course', 'chapter', 'sequential', 'about', 'static_tab', 'course_info']— only containers that are always-published by design.Testing Instructions
Two new regression tests in
cms/djangoapps/contentstore/tests/test_outlines.py:test_bug_35535_regression_duplicate_section_appears_in_outlinetest_bug_35535_regression_duplicate_subsection_appears_in_outlineDeadline
None
Other Information
course_publishedsignal still fires (new publish is inside the existing bulk_operations context)copy_from_templateinsplit_draft.pymay have a similar latent issue but is out of scope for this fixCloses #35535