feat: apply ADR 0038 (URL structure standardization) across 6 standardized APIs - #39078
Open
Abdul-Muqadim-Arbisoft wants to merge 7 commits into
Open
feat: apply ADR 0038 (URL structure standardization) across 6 standardized APIs#39078Abdul-Muqadim-Arbisoft wants to merge 7 commits into
Abdul-Muqadim-Arbisoft wants to merge 7 commits into
Conversation
Abdul-Muqadim-Arbisoft
requested review from
a team,
feanil and
kdmccormick
as code owners
September 6, 2026 20:16
Abdul-Muqadim-Arbisoft
requested review from
Faraz32123,
feanil and
taimoor-ahmed-1
and removed request for
a team,
feanil and
kdmccormick
September 6, 2026 20:16
…ons (ADR 0038) Register the CourseKeyConverter / UsageKeyConverter path converters — added to edx-drf-extensions 10.8.0 (openedx/edx-drf-extensions#573) per ADR 0038's 'Code examples' section — once per service in lms/urls.py and cms/urls.py, as <course_key:...> / <usage_key:...>. ADR 0038 rule 9: conforming routes resolve opaque keys in the URLconf, views receive parsed keys, and malformed or deprecated (Org/Course/Run, i4x://) keys become routing-level 404s. Bumps edx-drf-extensions 10.7.0 -> 10.8.0, the release that adds the converters (plus the ADR 0029/0032/0036 building blocks this API series already consumes). Converter unit tests live in the library; the per-API URL tests in the following commits cover resolve/reverse integration through the real routes.
Abdul-Muqadim-Arbisoft
force-pushed
the
feat/apply_url_structure_adr_on_apis
branch
from
September 7, 2026 07:22
0f1ccfa to
4ef11af
Compare
Mount the conforming routes beside the legacy /api/contentstore/v1/xblock/ ones (OEP-21), serving the same XblockViewSet: the collection becomes plural (rule 2), the API name describes the domain rather than the implementing Django app (rule 3), the usage key is resolved by the shared usage_key converter, which turns malformed and deprecated i4x:// keys into routing-level 404s (rule 9), and URL names are snake_case, version-free, and unique (rule 11). The viewset's initial() coerces a parsed UsageKey back to the string form the action methods expect, so both mounts share one contract. The legacy routes stay live for their deprecation window and are marked deprecated: true in the OpenAPI schema via the new cms_mark_migrated_paths post-processing hook; cms_api_filter now also admits /api/authoring/ paths. Tests pin reverse() literals, same-view resolution for both mounts, the routing-level 404, and handler parity on the conforming routes. ADR 0038 (implementation note 4) asks that /api/authoring/v1/xblocks/ be reconciled with the Learning Core /api/xblock/v2/xblocks/ rather than leaving two names for what looks like one API; that reconciliation is an API-owner decision tracked with the DEPR work, not part of this mechanical migration.
…ernal BFF) Mount the conforming home/, home/courses/, and home/libraries/ routes at /api/authoring/v3/ beside the legacy /api/contentstore/v3/home/ ones (OEP-21), serving the same HomeViewSet, with snake_case version-free URL names (rule 11) and the domain-named api_name (rule 3). home is a BFF aggregate for the Studio home screen. Rule 4 disfavors screen names as resources, but the ADR's BFF provision applies: the surface keeps the /api/ prefix and one canonical conforming mount, and is marked x-internal in the OpenAPI schema — on both mounts — so clients can tell it apart from a stable resource contract. The legacy routes are additionally marked deprecated: true. Tests pin reverse() literals, same-view resolution for all three action pairs, and the ADR 0029 envelope on the conforming mount.
Mount the conforming courses/ collection at /api/authoring/v4/ beside the legacy /api/contentstore/v4/home/courses/ route (OEP-21), serving the same HomeCoursesViewSet: the screen-shaped home/courses/ address becomes the concrete plural collection of authorable courses (rule 4), filtered, sorted, and paginated in the query string, under the domain-named api_name (rule 3) with a snake_case version-free URL name (rule 11). The legacy route stays live for its deprecation window and is marked deprecated: true in the OpenAPI schema. Tests pin the reverse() literal, same-view resolution for both mounts, and 401/200 contract parity on the conforming mount.
Mount the conforming /api/authoring/v3/courses/{course_key}/details/ route beside the legacy /api/contentstore/v3/course_details/{course_id}/ one (OEP-21), serving the same CourseDetailsViewSet: the screen-shaped collection becomes a sub-resource of the plural courses/ collection, one level deep — the ADR's own target for these endpoints (rules 4 and 8) — with the course key resolved by the shared course_key converter, which turns malformed and deprecated Org/Course/Run keys into routing-level 404s (rule 9).
resolve_course_key() now also accepts an already-parsed CourseKey, so both mounts funnel through one code path and share one contract. The legacy route stays live for its deprecation window and is marked deprecated: true in the OpenAPI schema. Tests pin the reverse() literal, same-view resolution, the routing-level 404, and 401/403 parity on the conforming mount.
Mount the conforming /api/authoring/v3/courses/{course_key}/grading/ route beside the legacy /api/contentstore/v3/authoring_grading/{course_key}/ one (OEP-21), serving the same AuthoringGradingViewSet: the app-flavored authoring_grading collection becomes the grading sub-resource of the plural courses/ collection, one level deep (rules 3, 4 and 8) — the authoring_ prefix is dropped because the namespace already says it — with the course key resolved by the shared course_key converter (rule 9). Both mounts funnel through resolve_course_key(), which already accepts parsed keys, so they share one contract.
The legacy route stays live for its deprecation window and is marked deprecated: true in the OpenAPI schema. Tests pin the reverse() literal, same-view resolution, the routing-level 404, and 401/200 PATCH parity on the conforming mount.
…nforming URL names)
/api/enrollment/v2/ already conforms in API name and version position; this fixes the remaining rule 6 and rule 11 violations. Conforming routes are dual-mounted (OEP-21) beside the legacy slashless ones, serving the same views: GET /enrollments/ (the admin list's optional-slash pattern — the ADR's own rule 6 example — is split into an exact slashed route plus a slashless legacy route, so every address that resolved before still resolves), GET /enrollments/{username},{course_key}/ under the plural collection (rule 2), GET /courses/{course_key}/ (plural, slashed), and roles/ renamed from the versioned kebab-case enrollment-v2-roles to user_roles (rule 11; path unchanged). Conforming member routes resolve course keys with the shared course_key converter (rule 9); the views coerce a parsed CourseKey back to the string form their bodies expect, so both mounts share one contract.
The two legacy retrieve forms no longer share one URL name — Django resolved that only by argument signature, the fragility rule 11 calls out — and the slashless legacy addresses are marked deprecated: true in the OpenAPI schema via a post-processing hook scoped to /v2/ (deprecating v1 is its own DEPR decision). Deeper ADR 0038 targets — collapsing the singular enrollment/ collection into enrollments/, replacing the unenroll verb (rule 10) with DELETE on the member address, and addressing the requesting user as me (rule 9) — are contract changes and belong to a future v3 per ADR 0037.
Tests pin the reverse() literals, same-view resolution for every legacy/conforming pair, the optional-slash coverage split, the unique legacy names, the routing-level 404, and 401 parity on both admin-list addresses.
Abdul-Muqadim-Arbisoft
force-pushed
the
feat/apply_url_structure_adr_on_apis
branch
from
September 7, 2026 08:12
4ef11af to
1ad2f0e
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Applies ADR 0038 — Standardize REST API URL Structure (#39003) to the 6 APIs previously standardized under the FC-0118 effort, per the ADR's implementation note 4: "Migrate the APIs already standardized under FC-0118 first … each keeping its current version number."
Every migration follows the ADR's OEP-21 rule: the conforming path is mounted as an additional route to the same view, the legacy path stays live for its deprecation window and is marked
deprecated: truein the OpenAPI schema. No existing address changes behaviour and none is removed.Depends on
edx-drf-extensions==10.8.0, whose sharedcourse_key/usage_keypath converters (edx-drf-extensions#573) this PR consumes (ADR 0038 rule 9).<course_key:…>/<usage_key:…>per service/api/contentstore/v1/xblock/{usage_key}//api/authoring/v1/xblocks/{usage_key}//api/contentstore/v3/home/…/api/authoring/v3/home/…+x-internal/api/contentstore/v4/home/courses//api/authoring/v4/courses//api/contentstore/v3/course_details/{id}//api/authoring/v3/courses/{course_key}/details//api/contentstore/v3/authoring_grading/{id}//api/authoring/v3/courses/{course_key}/grading/snake_caseURL namesChanges
feat— shared opaque-key URL converters (commit 1)Registers the
CourseKeyConverter/UsageKeyConverterfrom edx-drf-extensions 10.8.0 once per service (lms/urls.py,cms/urls.py). Conforming routes resolve opaque keys in the URLconf, views receive parsed keys, and malformed or deprecated (Org/Course/Run,i4x://) keys become routing-level 404s. Bumpsedx-drf-extensions10.7.0 → 10.8.0.feat— the five CMS APIs (commits 2–6)New
authoring_urls.pymodules per version, mounted fromcms/urls.pyunder their fullapi/authoring/v{N}/prefix (rule 5), serving the same viewsets as the legacy mounts:api_name(authoring), not the implementing Django app (contentstore);course_details/authoring_gradingbecomecourses/{course_key}/details/and…/grading/(the ADR's own target), one level deep;home/courses/(v4) becomes the concrete pluralcourses/;homestays screen-named under the ADR's BFF provision: one canonical conforming mount, markedx-internalin the schema;snake_case, version-free, unique URL names.cms/lib/spectacular.pygains a post-processing hook marking the legacy addressesdeprecated: trueand the home BFFx-internal;cms_api_filternow also admits/api/authoring/paths.feat— Enrollment v2 (commit 7)/api/enrollment/v2/already conforms in name and version position; this fixes the remaining rule 6/11 violations, dual-mounted beside the legacy slashless routes:^enrollments/?$(optional slash — the ADR's rule 6 example)enrollments/(exact) + slashless legacy — every address that resolved before still resolvesenrollment/{username},{course_key}enrollments/{username},{course_key}/(plural collection, rule 2)course/{course_key}courses/{course_key}/enrollment-v2-rolesuser_roles(path unchanged)The two legacy retrieve forms no longer share one URL name (the argument-signature fragility rule 11 calls out). The LMS schema hook marks slashless
/v2/addresses deprecated — scoped to v2, since deprecating v1 is its own DEPR decision. Deeper targets (collapsing singularenrollment/,DELETEon the member address for unenroll,meaddressing) are contract changes that belong to a future v3 per ADR 0037.