You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Retrieves the children of a container/vertical block in a course. Returns a JSON object with children array (including name, block_id, block_type, user_partition_info, upstream_link, actions, validation_messages, render_error), is_published, can_paste_component, display_name, and upstream_ready_to_sync_children_info. Useful for AI or external tools analyzing course structure, component readiness, or automating content synchronization.
PR:#39317 — feat: serve container children from authoring v2 State: open
What this does
Standardizes the container children read from the [API] Containers tracking issue (#39063, part of the FC-0118 umbrella #38137): GET /api/contentstore/v1/container/{usage_key_string}/children is superseded by GET /api/authoring/v2/containers/{usage_key}/children/. This is the second half of the container pair: the container itself is #39274 (/api/authoring/v2/containers/{usage_key}/), and its PR body assigns this address to this PR.
ADRs applied
11 of 16 decisions applied on this pass; every one left open carries its reason.
0025 serializers
0026 permissions
0027 schema / docs
0028 viewsets
0030 GET idempotence
0032 pagination
0034 authentication
0036 nested JSON
0037 versioning
0038 URL structure
OEP-69 conventions
0029 errors — partial: every error the view and CanViewCourse produce is the envelope; a malformed or i4x:// key fails at URL resolution and gets the service's HTML 404, and 405/406 are typed internal because edx-drf-extensions 10.9.0 omits them (both pinned by tests that flip when fixed)
0031 merged endpoints — n/a: one read operation; the only sibling is the frozen 301 alias, and container_handler is a different resource
0033 filtering — n/a: no filter and no sort; the order is the order the container stores; view selects a representation
0035 MFE config — n/a: course content, not site or front-end configuration
OEP-66 queryset scoping — the mechanism is excluded: no ORM queryset to wrap; the layering is below
The evidence for each tick is the compliance matrix in the PR body; nothing is ticked without a named test or gate line.
Backward compatibility
Nothing changes for any v1 caller.v1/ is not edited; every v1 address, URL name, body and status stays, including the 301 alias container/vertical/{key}/children.
Versions gate (check_old_versions_untouched.sh --base origin/pr/39274): PASS. Two allowed edits (listed below), four added files.
URL gate (url_snapshot.py check): 2166 routes at head, 2165 at base, no address and no URL name lost, one new API route checked: PASS.
Schema diff: 0 BREAKING. Added: operation GET /api/authoring/v2/containers/{usage_key}/children/, operationId v2_containers_children_list, 8 components (CourseContainerChild, CourseContainerChildActions, CourseContainerChildFull, CourseContainerChildMessage, CourseContainerChildRead, CourseContainerChildUpstreamChild, CourseContainerChildUpstreamLink, PaginatedCourseContainerChildReadList). Zero operationIds and zero components.schemas names removed. The v1 operation is newly marked deprecated: true. The two WARN lines are drf-spectacular failing to resolve the platform's default authenticator classes, which every default-authentication view in this schema triggers.
Parity (ContainerChildrenParityTest, ContainerChildrenLibraryLinkedParityTest): every child of a section, subsection, unit, split test and group, with a library link to a block and to a container, a broken link, restricted groups, a validation error, tagging on and off, and a block that fails to render, is compared leaf by leaf with the v1 body. INTENTIONAL_DIFFERENCES, verbatim from the test:
is_published|can_paste_component|display_name|upstream_ready_to_sync_children_info(\[\d+\].*)? — v1 also describes the container itself; the page lists only its children
count|next|previous|num_pages|current_page|start — the page envelope
results\[\d+\]\.render_error — the cause of a render failure is logged and a fixed message returned
RESULT: PASS (0 FAIL, 0 WARN)
head routes: 2166, base routes: 2165, new api routes checked: 1
All v1 addresses still resolve. New route: /api/authoring/v2/containers/{usage_key}/children/
RESULT: PASS (144 cases)
86 test methods, parametrized to 144 cases.
Also: test_spectacular.py (25 cases) PASS.
Full affected set: 606 passed, 3 failed (pre-existing in #39274's test_containers.py, fail identically at base).
linting
ruff check: All checks passed
pylint: no findings (except pytest-plugin F6401, environment)
NOTE: gates run against origin/pr/39274 (stack base), not origin/master
Re-run against origin/master after rebase (plan step 7 / 12.4)
Deprecations
GET /api/contentstore/v1/container/{usage_key_string}/children is superseded by GET /api/authoring/v2/containers/{usage_key}/children/?view=full and marked deprecated: true in the published schema, by one exact SUPERSEDED_PATHS entry. The superseded view is unchanged: no response header, no deprecation notes in code. The 301 alias container/vertical/{usage_key_string}/children is not a DRF view, has no schema operation, and is named on the DEPR entry.
Tracked on the FC-0118 umbrella DEPR ticket, #39128, which removes them one named release after the release this ships in. This area's entry is posted there once the PR is open.
Gap named, per the deprecation decision for this area: four things leave the response and two of them have no v2 home yet: is_published and the container's own upstream_ready_to_sync_children_info. The Authoring MFE's compare-containers widget reads the second. #39274 is asked to carry both on the container detail before it merges; until then the window could end before one exists, and the DEPR entry says so.
Migration notes for callers: call the v2 address with the trailing slash and ?view=full&page_size=100 for the Unit page; read results and follow next; errors use the envelope; Bearer is refused; a leaf or course-root key is 404; the caller must be able to view the course; render_error is a fixed message.
Follow-ups
Carry is_published and the container's own upstream readiness (ready_to_sync_children) on the container detail (feat: serve course containers from authoring v2 #39274), so the compare-containers widget has a v2 source; recorded in the DEPR entry.
A catch-all under containers/ so keys that fail to parse answer with the envelope instead of the HTML 404 (both container routes together).
The untyped user_partition_info and user_partitions objects are candidates for a typed shape in a next version; v1's declared shapes are kept as they are.
The UpstreamLink fields upstream_name, downstream_key, upstream_link and is_ready_to_sync_individually stay dropped, as in v1.
CourseContainerChildPagination is deleted for DefaultPagination once edx-drf-extensions documents all seven keys.
docs/cms-openapi.yaml is regenerated by the schema workflow, not edited here; the SDK gains a method for the new operation.
The end-to-end suite's containerChildrenPath calls the 301 alias; it moves to the v2 address in a separate change.
Activity
Faraz32123 commentedon Oct 27, 2026
Standardization PR open: #39317
PR: #39317 — feat: serve container children from authoring v2
State: open
What this does
Standardizes the container children read from the
[API] Containerstracking issue (#39063, part of the FC-0118 umbrella #38137):GET /api/contentstore/v1/container/{usage_key_string}/childrenis superseded byGET /api/authoring/v2/containers/{usage_key}/children/. This is the second half of the container pair: the container itself is #39274 (/api/authoring/v2/containers/{usage_key}/), and its PR body assigns this address to this PR.ADRs applied
11 of 16 decisions applied on this pass; every one left open carries its reason.
CanViewCourseproduce is the envelope; a malformed ori4x://key fails at URL resolution and gets the service's HTML 404, and 405/406 are typedinternalbecause edx-drf-extensions 10.9.0 omits them (both pinned by tests that flip when fixed)container_handleris a different resourceviewselects a representationThe evidence for each tick is the compliance matrix in the PR body; nothing is ticked without a named test or gate line.
Backward compatibility
v1/is not edited; every v1 address, URL name, body and status stays, including the 301 aliascontainer/vertical/{key}/children.check_old_versions_untouched.sh --base origin/pr/39274): PASS. Two allowed edits (listed below), four added files.url_snapshot.py check): 2166 routes at head, 2165 at base, no address and no URL name lost, one new API route checked: PASS.GET /api/authoring/v2/containers/{usage_key}/children/, operationIdv2_containers_children_list, 8 components (CourseContainerChild,CourseContainerChildActions,CourseContainerChildFull,CourseContainerChildMessage,CourseContainerChildRead,CourseContainerChildUpstreamChild,CourseContainerChildUpstreamLink,PaginatedCourseContainerChildReadList). Zero operationIds and zerocomponents.schemasnames removed. The v1 operation is newly markeddeprecated: true. The two WARN lines aredrf-spectacularfailing to resolve the platform's default authenticator classes, which every default-authentication view in this schema triggers.ContainerChildrenParityTest,ContainerChildrenLibraryLinkedParityTest): every child of a section, subsection, unit, split test and group, with a library link to a block and to a container, a broken link, restricted groups, a validation error, tagging on and off, and a block that fails to render, is compared leaf by leaf with the v1 body.INTENTIONAL_DIFFERENCES, verbatim from the test:is_published|can_paste_component|display_name|upstream_ready_to_sync_children_info(\[\d+\].*)?— v1 also describes the container itself; the page lists only its childrencount|next|previous|num_pages|current_page|start— the page enveloperesults\[\d+\]\.render_error— the cause of a render failure is logged and a fixed message returnedGates
Gate report — Containers (#39063)
Run 2026-10-27, base origin/pr/39274 (3291339)
Gates run individually (not through run_gates.sh) due to worktree environment
versions gate: check_old_versions_untouched.sh
RESULT: PASS (2 allowed, 4 added)
--base origin/pr/39274
--allow cms/djangoapps/contentstore/rest_api/v2/authoring_urls.py
--allow cms/djangoapps/contentstore/rest_api/v2/views/tests/test_containers.py
--protect cms/djangoapps/contentstore/rest_api/urls.py
--protect cms/djangoapps/contentstore/xblock_storage_handlers/view_handlers.py
--protect cms/djangoapps/contentstore/utils.py
--protect cms/djangoapps/contentstore/helpers.py
--protect cms/djangoapps/contentstore/views/preview.py
--protect cms/lib/xblock/upstream_sync.py
--protect openedx/core/lib/api/view_utils.py
No v0/, v1/, v3/, v4/ file changed. No protected file changed.
Allowed: authoring_urls.py, test_containers.py
Added: container_children_service.py, serializers/container_children.py, views/container_children.py, views/tests/test_container_children.py
hygiene gate: check_code_hygiene.py
RESULT: PASS (0 FAIL, 1 WARN)
WARN H3: CourseContainerChildPagination pagination subclass (kept: adds the seven-key schema; documented in docstring)
schema gate: schema_diff.py compare
RESULT: PASS (0 BREAKING, 2 WARN, 4 INFO)
Added: 1 operation (v2_containers_children_list), 8 components
v1 operation v1_container_children_retrieve: newly deprecated: true
0 operationIds removed, 0 component names removed
WARN: "could not resolve authenticator" for default JWT and session classes (every default-authentication view)
check-present --prefix /api/authoring/v2/containers/ -> PASS
url gate: url_snapshot.py check
RESULT: PASS (0 FAIL, 0 WARN)
head routes: 2166, base routes: 2165, new api routes checked: 1
All v1 addresses still resolve. New route: /api/authoring/v2/containers/{usage_key}/children/
old-tests gate: pytest v1/views/tests/test_vertical_block.py
RESULT: PASS
All classes (ContainerVerticalViewTest, ContainerHandlerViewTest, ContainerHandlerViewAuthzTest) green.
No v1 test edited.
tests gate: pytest v2/views/tests/test_container_children.py
RESULT: PASS (144 cases)
86 test methods, parametrized to 144 cases.
Also: test_spectacular.py (25 cases) PASS.
Full affected set: 606 passed, 3 failed (pre-existing in #39274's test_containers.py, fail identically at base).
linting
ruff check: All checks passed
pylint: no findings (except pytest-plugin F6401, environment)
NOTE: gates run against origin/pr/39274 (stack base), not origin/master
Re-run against origin/master after rebase (plan step 7 / 12.4)
Deprecations
GET /api/contentstore/v1/container/{usage_key_string}/childrenis superseded byGET /api/authoring/v2/containers/{usage_key}/children/?view=fulland markeddeprecated: truein the published schema, by one exactSUPERSEDED_PATHSentry. The superseded view is unchanged: no response header, no deprecation notes in code. The 301 aliascontainer/vertical/{usage_key_string}/childrenis not a DRF view, has no schema operation, and is named on the DEPR entry.Tracked on the FC-0118 umbrella DEPR ticket, #39128, which removes them one named release after the release this ships in. This area's entry is posted there once the PR is open.
Gap named, per the deprecation decision for this area: four things leave the response and two of them have no v2 home yet:
is_publishedand the container's ownupstream_ready_to_sync_children_info. The Authoring MFE's compare-containers widget reads the second. #39274 is asked to carry both on the container detail before it merges; until then the window could end before one exists, and the DEPR entry says so.Migration notes for callers: call the v2 address with the trailing slash and
?view=full&page_size=100for the Unit page; readresultsand follownext; errors use the envelope; Bearer is refused; a leaf or course-root key is 404; the caller must be able to view the course;render_erroris a fixed message.Follow-ups
is_publishedand the container's own upstream readiness (ready_to_sync_children) on the container detail (feat: serve course containers from authoring v2 #39274), so the compare-containers widget has a v2 source; recorded in the DEPR entry.containers/so keys that fail to parse answer with the envelope instead of the HTML 404 (both container routes together).user_partition_infoanduser_partitionsobjects are candidates for a typed shape in a next version; v1's declared shapes are kept as they are.UpstreamLinkfieldsupstream_name,downstream_key,upstream_linkandis_ready_to_sync_individuallystay dropped, as in v1.CourseContainerChildPaginationis deleted forDefaultPaginationonce edx-drf-extensions documents all seven keys.docs/cms-openapi.yamlis regenerated by the schema workflow, not edited here; the SDK gains a method for the new operation.containerChildrenPathcalls the 301 alias; it moves to the v2 address in a separate change.🤖 Generated with Claude Code
Review on the PR, please — this issue stays open until it merges.
feat: serve container children from authoring v2
feat: serve container children from authoring v2
feat: serve container children from authoring v2
feat: serve container children from authoring v2
feat: serve container children from authoring v2