Skip to content

feat: axim api improvements#38755

Open
Faraz32123 wants to merge 17 commits into
masterfrom
feat/axim-api_improvements
Open

feat: axim api improvements#38755
Faraz32123 wants to merge 17 commits into
masterfrom
feat/axim-api_improvements

Conversation

@Faraz32123

@Faraz32123 Faraz32123 commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR applies a set of API standardization ADRs (0025–0029) to a batch of
contentstore endpoints, introducing new versioned API endpoints rather than
modifying existing ones.

ADRs applied: see related ADR PR #38738

Why new API versions instead of updating existing endpoints?

The ADRs mandate structural changes that are breaking for existing consumers:

  • ADR 0029 (standardized error envelope): Changes the error response format
    from the legacy {"developer_message": ..., "error_code": ...} shape to a
    structured RFC 7807 envelope (type, title, status, detail, instance).
    Any client that parses error responses from the existing endpoint would break.

  • ADR 0028 (ViewSet + DefaultRouter): Replaces APIView with a ViewSet
    registered via DefaultRouter, which generates slightly different URL patterns
    (trailing slash, router-managed routing). Existing URL patterns and named routes
    are preserved in the old version.

  • ADR 0026 (explicit auth classes): Standardizes authentication — removes
    deprecated BearerAuthentication. While not strictly breaking for JWT/session
    clients, Bearer-only consumers would lose access.

Introducing new versions allows existing consumers to stay on the current
endpoint and migrate on their own timeline, rather than absorbing a silent
breaking change.

Changes in this PR

Old endpoint New endpoint Change
GET/POST /api/contentstore/v0/xblock/ GET/POST/PATCH/DELETE /api/contentstore/v1/xblock/{usage_key}/ ADRs 0025–0029
GET /api/contentstore/v1/home/ GET /api/contentstore/v3/home/ ADRs 0025–0029
GET /api/contentstore/v2/home/ GET /api/contentstore/v4/home/ ADRs 0025–0029
GET/PUT /api/contentstore/v1/course_details/{id}/ GET/PUT /api/contentstore/v3/course_details/{course_key}/ ADRs 0025–0029
POST /api/contentstore/v0/grading/{id}/ PATCH /api/contentstore/v3/authoring_grading/{course_key}/ ADRs 0025–0029
GET/POST /api/contentstore/v0/enrollments/ GET/POST /api/contentstore/v1/enrollments/ ADRs 0025–0029

Testing

Each new endpoint has a dedicated test module covering:

  • ADR 0026: permission boundary tests (401/403)
  • ADR 0028: routing tests
  • ADR 0029: error envelope shape tests
  • Regression test confirming old endpoint error shape is unaffected

@Faraz32123 Faraz32123 requested a review from a team as a code owner June 15, 2026 08:07
return response_data

except InvalidEnrollmentAttribute as error:
raise ValidationError(str(error)) from error
except InvalidEnrollmentAttribute as error:
raise ValidationError(str(error)) from error
except EnrollmentNotAllowed as error:
raise PermissionDenied(str(error)) from error
@Faraz32123 Faraz32123 force-pushed the feat/axim-api_improvements branch 2 times, most recently from 148eb3c to 21b6e30 Compare June 15, 2026 08:29
@Faraz32123 Faraz32123 changed the title Feat/axim api improvements feat: axim api improvements Jun 15, 2026
@feanil

feanil commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

@Faraz32123, there is not a lot of context in the description or in the commit messages about WHY decisions are made. I know that for example the xblock API went from V0 to V1 but it's unclear to me what prevents us from making updates to the existing api enpoint requiring us to introduce a new one. Can you make sure the relevant commit message and the PR description have this reasoning so we can find it in the future. The ADR is the broad guide but we still need to provide the specific reasoning for given changes to make it easier for us to understand later.

@Faraz32123 Faraz32123 force-pushed the feat/axim-api_improvements branch from 21b6e30 to b8df48f Compare June 22, 2026 13:46
taimoor-ahmed-1 and others added 12 commits June 24, 2026 16:55
Standardize HomePageCoursesView
Co-authored-by: Taimoor  Ahmed <taimoor.ahmed@A006-01711.local>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: apply ADRs to home v1 apis

* feat: apply ADR 0029 error envelope to home v3 viewset

Wires HomeViewSet (v3) into openedx/core/lib/api/exceptions's
standardized_error_exception_handler via a v3-local StandardizedErrorMixin
that overrides DRF's per-view get_exception_handler. Project-wide
EXCEPTION_HANDLER setting is intentionally left unchanged so v0/v1/v2
endpoints are unaffected.

* feat: refactor mixin so that both v3 and v4 can use it
…d APIs (#38773)

* feat: apply ADR 0036 to Xblock v1 (?view=minimal)

Adds a ``?view=minimal`` query parameter on ``XblockViewSet.retrieve``
that filters the (tree-shaped) xblock response down to a small set of
structural fields — id, display_name, category, children, has_children,
studio_url — dropping heavy/contextual fields such as data, metadata,
fields, student_view_data, edited_on, published.

Default response shape is unchanged (full xblock payload) to avoid
breaking the existing Studio frontend. The pre-existing ``?fields=...``
query parameter retains its legacy "type of response" semantics
(?fields=graderType, ?fields=ancestorInfo, ?fields=customReadToken);
ADR 0036's CSV-subset interpretation is deferred to a future API
version to avoid breaking those callers.

Adds 4 regression tests covering: default response untouched, minimal
strips heavy fields, minimal keeps structural fields, minimal is a
no-op for non-dict response bodies (legacy ?fields=graderType path).

* feat: apply ADR 0036 to CourseHome v3 (?fields= on list action)

The v3 ``HomeViewSet.list`` action returns a wide ``StudioHomeSerializer``
payload with ~25 top-level keys (courses, archived_courses, libraries,
allowed_organizations, allowed_organizations_for_libraries, plus Studio
settings). Adds a ``?fields=`` query parameter so clients can request a
subset of those keys explicitly.

The flat-list ``courses`` and ``libraries`` actions are out of scope
(each returns a single-key dict wrapping a list of small items — no
nested or wide structure to filter).

Adds a shared ``apply_field_selection`` helper to ``v3/utils.py`` so
future v3 viewsets can opt into the same convention without
re-implementing it. Adds 2 regression tests: default keeps all keys,
``?fields=courses,libraries`` restricts to exactly those keys.

* docs: audit CourseHome v4 against ADR 0036 (out of scope)

Adds an ADR 0036 entry to ``HomeCoursesViewSet``'s compliance list
explicitly marking this endpoint as out of scope.

Rationale: the v4 home endpoint returns a flat paginated list
governed by ADR 0032 (DefaultPagination 7-field envelope). ADR 0036
excludes flat lists from its ``?view=`` / ``?depth=`` / minimal-by-default
requirements — those apply to tree-shaped responses or wide flat
objects with embedded sub-objects. Each course item is a thin 9-field
record with no nested children, no embedded sub-objects, and no tree
structure to bound.

Per-item ``?fields=`` subset filtering remains a possible follow-up
(would require a dynamic-fields serializer mixin and per-field schema
documentation) but is deferred to keep the v4 contract stable for the
existing Studio frontend.

* feat: apply ADR 0036 to Enrollment v2 (?view=minimal flattens course_details)

Each enrollment record returned by the v2 ``EnrollmentViewSet.list`` and
``EnrollmentRetrieveView.get`` actions embeds a full ``course_details``
sub-object (which itself includes a ``course_modes`` list and other
heavy course-overview fields). Server-to-server callers and AI agents
that only need to know which courses a user is enrolled in shouldn't
have to parse the embedded sub-object on every row.

Adds a ``?view=minimal`` query parameter on both actions that collapses
the embedded ``course_details`` to a single ``course_id`` string. The
enrollment-level fields (``created``, ``mode``, ``is_active``, ``user``)
are preserved. Default response shape is unchanged.

Adds 2 regression tests (mocked, MongoDB-free): default list keeps the
full shape, ``?view=minimal`` collapses each row's course_details to a
flat course_id and the heavy fields are dropped.

* feat: apply ADR 0036 to Course Detail v3 (?view=minimal + ?fields=)

The v3 ``CourseDetailsViewSet.retrieve`` action returns a wide
``CourseDetailsSerializer`` payload with ~40 top-level fields plus a
nested ``instructor_info`` sub-object (instructor names, bios, image
URLs) and a ``learning_info`` long-form list. The default full payload
is preserved; two new opt-in query parameters apply ADR 0036:

  * ``?view=minimal``  drops heavy fields (overview, syllabus,
    description, short_description, instructor_info, learning_info,
    banner_image_name / banner_image_asset_path, video_thumbnail
    assets, license) — leaving only identification (course_id, org,
    run, title, subtitle, language), schedule (start_date, end_date,
    enrollment_start, enrollment_end, certificate_available_date),
    and flags (self_paced, certificates_display_behavior, has_changes).

  * ``?fields=a,b,c``  keeps an explicit CSV subset of top-level keys.
    Composes with ``?view=minimal`` — the preset is applied first,
    then the explicit subset.

Reuses ``apply_field_selection`` from ``v3/utils.py`` (introduced in
the CourseHome v3 commit) so the convention is consistent across v3.

Adds 3 regression tests (mocked, MongoDB-free): default keeps all
fields, ?view=minimal drops the heavy/embedded ones and keeps
identification/schedule/flags, ?fields=course_id,title restricts to
exactly those keys.

* docs: audit AuthorGrading v3 against ADR 0036 (largely out of scope)

Adds an ADR 0036 entry to ``AuthoringGradingViewSet``'s compliance list.

Rationale: the v3 grading response is a single top-level ``graders``
list of small fixed-shape objects (type, min_count, drop_count,
short_label, weight, id) — no tree nesting, no embedded sub-objects,
no ``children`` field, no wide flat object. ``?view=minimal`` and
``?fields=`` would have no fields to drop; the only ADR 0036 concern
that applies is anti-pattern #3 (unbounded child list).

In practice each course has ≤8 graders (Homework, Lab, Exam, etc.)
and the update flow is exercised only by course-authoring staff,
so the real-world payload is always small. The hard cap is enforced
upstream by ``CourseGradingModel.update_from_json``; documented as a
note rather than re-implemented at the view layer.
Add openai schema tag for our new versioned APIs for distinguishment, So that we can filter out the /auhoring-api/schema/ on the basis of tags and create/update our sdk.
@Faraz32123 Faraz32123 force-pushed the feat/axim-api_improvements branch 3 times, most recently from 8289204 to cc04d6b Compare June 24, 2026 12:24
- Add CourseHomeTabSerializer to v1/serializers/home.py (was imported by
  v3/views/home.py but never defined, causing ImportError on startup)
- Export it from v1/serializers/__init__.py
- Remove stale HomePageCoursesView from v1/views/__init__.py (class was
  dropped by revert commit during rebase but export remained)
- Remove unused UserFactory and CourseOverviewFactory imports from
  v1/views/tests/test_home.py (ruff F401 violations from rebase conflict)
@Faraz32123 Faraz32123 force-pushed the feat/axim-api_improvements branch from cc04d6b to b1c8acb Compare June 24, 2026 12:38
Faraz32123 and others added 3 commits June 24, 2026 19:15
fix: test as v1 home/courses endpoint was removed from master 33b3480,
During rebase, this test was missed.
Follow-up from PR #38773 review: Xblock v1 previously had no OpenAPI
schema decoration, which was flagged as a pre-existing gap. This adds
drf-spectacular @extend_schema to all five viewset actions (create,
retrieve, update, partial_update, destroy) so the endpoints show up
with proper request/response bodies, path/query parameters, and error
codes in the generated OpenAPI 3.x schema.

Notes:
- Documents the ADR 0036 ``?view=minimal`` query parameter in the
  schema (enum=["minimal"]) so consumers can discover it.
- Documents the legacy ``?fields=`` selector (graderType /
  ancestorInfo / customReadToken) as ``deprecated=True`` — its
  semantics are pass-through / type-of-response, not the ADR 0036
  CSV subset convention.
- Shared parameter and response building blocks are declared at
  module scope to keep the per-action decorators readable.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* feat: fix OpenAPI schemas for SDK compatibility

Three changes driven by integration testing with openedx-platform-sdk:

1. authoring_grading serializer — expose grade_cutoffs, grace_period,
   and minimum_grade_credit as proper typed schema fields.
   CourseGradingModel.update_from_json requires all three, but they were
   absent from the serializer so the generated SDK had no typed fields for
   them, forcing callers to smuggle values in via additional_properties.
   Adds GracePeriodSerializer (hours / minutes / seconds) and documents
   that grade_cutoffs and grace_period must be present in every PATCH.

2. course_details serializer — mark certificate_available_date as
   allow_null=True. The field is legitimately null for many courses, but
   the missing flag caused the SDK client to crash with
   fromisoformat(None) when deserialising the response.

3. v4 HomeCoursesViewSet — add _HomeCoursesAutoSchema (same pattern as
   the existing v3 _HomeAutoSchema). The viewset builds its paginator
   manually inside list() rather than via pagination_class, so
   drf-spectacular's _is_list_view() returns True and generates an array
   schema. Overriding _is_list_view() for the list action and using an
   inline_serializer makes the schema reflect the actual paginated object
   shape (count, num_pages, current_page, start, next, previous,
   results).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: sort imports in v4 home view to satisfy ruff I001

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: wrap long lines in v4 home view inline_serializer fields

Fixes pylint C0301 (line-too-long) — lines 175 and 177 exceeded the
120-character limit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
#38849)

* feat: add drf-spectacular on LMS for enrollment API schema generation

Adds drf-spectacular support to LMS so the openedx-platform-sdk can
generate enrollment v2 API bindings from a proper LMS schema rather than
mounting enrollment URLs in CMS as a workaround.

Changes:
- openedx/envs/common.py: add DEFAULT_SCHEMA_CLASS to REST_FRAMEWORK so
  drf-spectacular's AutoSchema is used across all LMS environments
- lms/envs/common.py: add drf_spectacular to INSTALLED_APPS
- lms/lib/spectacular.py: preprocessing hook that filters to enrollment
  v2 endpoints only (/api/enrollment/v\d+/)
- lms/envs/devstack.py, lms/envs/production.py: SPECTACULAR_SETTINGS with
  path prefix trim and enrollment-only title
- lms/urls.py: expose schema at /lms-api/schema/ via SpectacularAPIView
- cms/lib/spectacular.py: revert enrollment URL workaround (no longer needed)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: move settings to common.py

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
@Faraz32123 Faraz32123 requested a review from kdmccormick as a code owner July 6, 2026 10:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants