openedx.core.djangoapps.enrollments.v2 package

Contents

openedx.core.djangoapps.enrollments.v2 package#

Submodules#

openedx.core.djangoapps.enrollments.v2.forms module#

Forms for validating user input to the Course Enrollment v2 views.

ADR 0033 (OEP-68 parameter naming standardization) — accepts both the preferred parameter names (course_key, course_keys) and the legacy aliases (course_id, course_ids). When both are present, the preferred name wins. Use legacy_param_aliases_used() from the view layer to emit the ADR 0033 Deprecation HTTP header when a legacy alias was sent.

Internally the cleaned_data continues to expose course_id / course_ids (the names the queryset code reads) — the form coalesces the preferred values onto those fields before the rest of validation runs.

class openedx.core.djangoapps.enrollments.v2.forms.EnrollmentsAdminListForm(query_params, *args, **kwargs)#

Bases: Form

Validates the query string parameters for the v2 admin enrollments list endpoint (GET /api/enrollment/v2/enrollments/).

MAX_INPUT_COUNT = 100#
base_fields = {'course_id': <django.forms.fields.CharField object>, 'course_ids': <django.forms.fields.CharField object>, 'course_key': <django.forms.fields.CharField object>, 'course_keys': <django.forms.fields.CharField object>, 'email': <django.forms.fields.CharField object>, 'username': <django.forms.fields.CharField object>}#
clean_course_id()#

Parse and validate the course_id (or aliased course_key) parameter.

clean_course_ids()#

Split the course_ids CSV (or aliased course_keys) and enforce MAX_INPUT_COUNT.

clean_email()#

Split the email CSV and enforce MAX_INPUT_COUNT.

clean_username()#

Split the username CSV, validate each entry, and enforce MAX_INPUT_COUNT.

declared_fields = {'course_id': <django.forms.fields.CharField object>, 'course_ids': <django.forms.fields.CharField object>, 'course_key': <django.forms.fields.CharField object>, 'course_keys': <django.forms.fields.CharField object>, 'email': <django.forms.fields.CharField object>, 'username': <django.forms.fields.CharField object>}#
legacy_param_aliases_used()#

Return the list of legacy parameter names that were actually present in the request, in declaration order. The view layer uses this to emit the ADR 0033 Deprecation header.

property media#

Return all media required to render the widgets on this form.

openedx.core.djangoapps.enrollments.v2.paginators module#

Pagination for the Enrollment API — v2.

ADR 0032 — uses DefaultPagination from edx-rest-framework-extensions, which provides the standard 7-field envelope: count, num_pages, current_page, start, next, previous, results.

Distinct from v1’s openedx.core.djangoapps.enrollments.paginators.CourseEnrollmentsApiListPagination (which is a CursorPagination subclass with a 3-field envelope). v2 introduces the new shape — clients that need the legacy shape stay on /api/enrollment/v1/ until they migrate.

class openedx.core.djangoapps.enrollments.v2.paginators.EnrollmentsAdminListPagination#

Bases: DefaultPagination

ADR 0032 — standard pagination for the admin enrollments list API (GET /api/enrollment/v2/enrollments/).

Defaults sized for an admin-facing bulk-query endpoint: page_size 100, max 100.

max_page_size = 100#
page_size = 100#
page_size_query_param = 'page_size'#

openedx.core.djangoapps.enrollments.v2.serializers module#

Serializers for the Enrollment API — v2.

Only contains the serializers introduced by ADR 0025 (replacing inline dict construction in role-listing endpoints). The other v1 serializers (CourseEnrollmentSerializer, CourseSerializer, CourseEnrollmentAllowedSerializer, CourseEnrollmentsApiListSerializer) are unchanged in shape between v1 and v2 — v2 view code imports them directly from openedx.core.djangoapps.enrollments.serializers.

If a future v3 needs to break any of those response shapes, fork them into a new v3/serializers.py at that time.

class openedx.core.djangoapps.enrollments.v2.serializers.UserRoleSerializer(*args, **kwargs)#

Bases: Serializer

Serializes a single course-level role entry for a user (ADR 0025).

get_course_id(obj)#

Return course_id as a string.

class openedx.core.djangoapps.enrollments.v2.serializers.UserRolesResponseSerializer(*args, **kwargs)#

Bases: Serializer

Serializes the full response payload for UserRolesViewSet (ADR 0025).

openedx.core.djangoapps.enrollments.v2.urls module#

URLs for the Enrollment API — v2.

Mounted at /api/enrollment/v2/ (see lms/urls.py).

ADR 0028 — EnrollmentViewSet is registered via DefaultRouter (actions: list, create, unenroll, allowed). The other v2 endpoints (singleton retrieve by URL form, roles, course-detail-by-id, admin enrollments list) cannot be expressed as router-generated URLs, so they remain as standalone APIView classes routed via path() / re_path().

URL surface#

Router-generated (basename enrollment):

GET /enrollment/ POST /enrollment/ POST /enrollment/unenroll/ GET /enrollment/enrollment_allowed/ POST /enrollment/enrollment_allowed/ DELETE /enrollment/enrollment_allowed/

Explicit paths:

GET /enrollment/{username},{course_key} (name: enrollment-v2-retrieve) GET /enrollment/{course_key} (name: enrollment-v2-retrieve) GET /enrollments/ (name: enrollment-v2-admin-list) GET /course/{course_key} (name: enrollment-v2-course-detail) GET /roles/ (name: enrollment-v2-roles)

openedx.core.djangoapps.enrollments.v2.view_services module#

Shared service layer for enrollment v2 HTTP operations.

ADR 0031 (Merge Similar Endpoints) — consolidates the business logic behind the three v2 viewset actions that previously had partially duplicated implementations in v1’s EnrollmentListView / UnenrollmentView / EnrollmentAllowedView.

Authorization model#

Each operation is enforced in two layers:

  1. The viewset declares a coarse permission class (IsAuthenticated, IsAdminUser, CanRetireUser, ApiKeyHeaderPermissionIsAuthenticated) on the action.

  2. The service method enforces the per-operation rules — e.g. only API-key callers or global staff may deactivate enrollments, downgrade modes, or force-enroll a user.

ADR 0029 — service methods raise DRF exceptions (NotFound, ValidationError, PermissionDenied, Conflict) instead of returning Response objects with non-2xx status. The exceptions flow through the viewset’s StandardizedErrorMixin to produce the standardized envelope.

class openedx.core.djangoapps.enrollments.v2.view_services.EnrollmentOperationsService#

Bases: object

Operation handlers for the v2 EnrollmentViewSet.

All methods raise DRF exceptions on error paths so the viewset’s StandardizedErrorMixin can produce the ADR 0029 envelope.

create_allowed_enrollment(serializer)#

Persist the allowed-enrollment described by serializer.

Raises:

Conflict – if a row already exists for the (email, course_id) pair.

create_or_update_enrollment(request, has_api_key, course_id)#

Handle the POST /enrollment/ create-or-update flow.

course_id is a parsed CourseKey. The viewset is responsible for the up-front InvalidKeyError ValidationError translation before calling this method.

Returns the enrollment dict on success. Raises DRF exceptions on any error path.

delete_allowed_enrollment(email, course_id)#

Delete the allowed-enrollment row identified by (email, course_id).

Raises:

NotFound – if no such row exists.

list_allowed_for_email(email)#

Return the CourseEnrollmentAllowed queryset for email.

list_enrollments_for_user(request_user, target_username, has_api_key)#

Return enrollments visible to request_user for target_username.

  • Self / global staff / api-key requests → full list.

  • Otherwise filtered to courses request_user staffs.

unenroll_user_for_retirement(username)#

Handle the retirement-pipeline /enrollment/unenroll/ flow.

Returns:

  • None if the user has no active enrollments (caller should return 204 No Content).

  • A dict (the unenroll-result payload) on success (caller returns 200).

Raises:
  • ValidationError – if username is missing.

  • NotFound – if no retirement-status row exists for the user.

  • APIException – on any other unexpected error (mapped to 500).

openedx.core.djangoapps.enrollments.v2.views module#

API Views for the Enrollment API — v2.

This module is the v2 incarnation of the v1 enrollment views, restructured to apply the FC-0118 ADRs from the start:

  • ADR 0025 – serializer_class on every viewset/view

  • ADR 0026 – explicit authentication_classes + permission_classes

  • ADR 0034 – auth standardization (OEP-0042). All four v2 viewsets/views use (JwtAuthentication, EnrollmentCrossDomainSessionAuth); BearerAuthenticationAllowInactiveUser has been removed per the deprecation policy. EnrollmentCrossDomainSessionAuth is retained (rather than relying on the platform-default SessionAuthentication) because these endpoints must accept cross-domain Studio/LMS CSRF-validated session cookies.

  • ADR 0027 – drf_spectacular for OpenAPI schema generation

  • ADR 0028 – consolidated into ViewSet classes registered via DefaultRouter where the URL shape allows it

  • ADR 0029 – standardized error envelope via StandardizedErrorMixin

  • ADR 0031 – business logic centralized in EnrollmentOperationsService (v2.view_services)

  • ADR 0032 – DefaultPagination 7-field envelope on list endpoints

  • ADR 0033 – OEP-68 parameter naming (course_key preferred, course_id as deprecated alias) plus standard ordering whitelist

  • ADR 0036 – ?view=minimal on the enrollment list and singleton retrieve actions. By default each enrollment record embeds the full course_details sub-object (which itself includes a course_modes list and other heavy fields). When ?view=minimal is requested, the embedded sub-object is flattened to a single course_id string so callers that only need to know which courses a user is enrolled in (AI agents, sync pipelines) can skip the per-row sub-object payload.

Existing v1 endpoints at /api/enrollment/v1/ are unchanged — v2 is a parallel new version mounted at /api/enrollment/v2/.

class openedx.core.djangoapps.enrollments.v2.views.CourseEnrollmentDetailView(**kwargs)#

Bases: StandardizedErrorMixin, APIView

Get enrollment information about a particular course.

authentication_classes = ()#
get(request, course_id=None)#

Return enrollment-related details for the specified course.

Public (no authentication required). The response includes the course schedule and supported enrollment modes; pass ?include_expired=1 to include expired enrollment modes.

permission_classes = ()#
schema#
serializer_class#

alias of CourseSerializer

throttle_classes = (<class 'openedx.core.djangoapps.enrollments.views.EnrollmentUserThrottle'>,)#
class openedx.core.djangoapps.enrollments.v2.views.EnrollmentRetrieveView(**kwargs)#

Bases: StandardizedErrorMixin, ApiKeyPermissionMixIn, APIView

GET enrollment for a course (and optionally a named user).

authentication_classes = (<class 'edx_rest_framework_extensions.auth.jwt.authentication.JwtAuthentication'>, <class 'openedx.core.djangoapps.enrollments.views.EnrollmentCrossDomainSessionAuth'>)#
get(request, course_id=None, username=None)#

Return the enrollment for (username, course_id).

When username is omitted (the GET /enrollment/{course_id} URL form), the request user is used. Non-staff callers may only look up their own enrollment; any cross-user lookup without has_api_key or staff privileges raises NotFound (so the caller cannot probe for the existence of other users’ enrollments).

permission_classes = (<class 'openedx.core.lib.api.permissions.ApiKeyHeaderPermissionIsAuthenticated'>,)#
schema#
serializer_class#

alias of CourseEnrollmentSerializer

throttle_classes = (<class 'openedx.core.djangoapps.enrollments.views.EnrollmentUserThrottle'>,)#
class openedx.core.djangoapps.enrollments.v2.views.EnrollmentViewSet(**kwargs)#

Bases: StandardizedErrorMixin, ViewSet, ApiKeyPermissionMixIn

Canonical ViewSet for the v2 Enrollment API.

Consolidates the v1 EnrollmentListView + UnenrollmentView + EnrollmentAllowedView into a single router-registered ViewSet (ADR 0028). Per-action permissions are declared via the @action decorator’s permission_classes kwarg.

Router URLs (registered at basename="enrollment"):

GET    /api/enrollment/v2/enrollment/                 → list
POST   /api/enrollment/v2/enrollment/                 → create
POST   /api/enrollment/v2/enrollment/unenroll/        → unenroll
GET    /api/enrollment/v2/enrollment/enrollment_allowed/  → allowed (GET)
POST   /api/enrollment/v2/enrollment/enrollment_allowed/  → allowed (POST)
DELETE /api/enrollment/v2/enrollment/enrollment_allowed/  → allowed (DELETE)
allowed(request)#

Retrieve, create, or delete CourseEnrollmentAllowed records. Admin-only.

authentication_classes = (<class 'edx_rest_framework_extensions.auth.jwt.authentication.JwtAuthentication'>, <class 'openedx.core.djangoapps.enrollments.views.EnrollmentCrossDomainSessionAuth'>)#
basename = None#
check_throttles(request)#

Check if request should be throttled. Raises an appropriate exception if the request is throttled.

create(request)#

Enroll a user in a course (or update an existing enrollment).

description = None#
detail = None#
get_serializer(*args, **kwargs)#
get_serializer_class()#
list(request)#

List enrollments for the currently logged-in user (paginated).

ADR 0036 — when ?view=minimal is supplied, each enrollment’s embedded course_details sub-object is collapsed to a single course_id string; course_modes and the other heavy course-detail fields are dropped. Default response shape is unchanged for backwards compatibility.

name = None#
pagination_class#

alias of DefaultPagination

permission_classes = (<class 'openedx.core.lib.api.permissions.ApiKeyHeaderPermissionIsAuthenticated'>,)#
schema#
serializer_class#

alias of CourseEnrollmentSerializer

suffix = None#
throttle_classes = (<class 'openedx.core.djangoapps.enrollments.views.EnrollmentUserThrottle'>,)#
unenroll(request)#

Unenroll the specified user from all courses (retirement pipeline).

class openedx.core.djangoapps.enrollments.v2.views.EnrollmentsAdminListView(**kwargs)#

Bases: StandardizedErrorMixin, ListAPIView

Admin-only paginated enrollment list with OEP-68 filter aliases.

ALLOWED_ORDERING_FIELDS = frozenset({'-created', '-id', 'created', 'id'})#
authentication_classes = (<class 'edx_rest_framework_extensions.auth.jwt.authentication.JwtAuthentication'>, <class 'openedx.core.djangoapps.enrollments.views.EnrollmentCrossDomainSessionAuth'>)#
get_queryset()#

Get the list of items for this view. This must be an iterable, and may be a queryset. Defaults to using self.queryset.

This method should always be used rather than accessing self.queryset directly, as self.queryset gets evaluated only once, and those results are cached for all subsequent requests.

You may want to override this if you need to provide different querysets depending on the incoming request.

(Eg. return a list of items that is specific to the user)

list(request, *args, **kwargs)#

Override to emit the ADR 0033 Deprecation header when legacy params used.

pagination_class#

alias of EnrollmentsAdminListPagination

permission_classes = (<class 'rest_framework.permissions.IsAdminUser'>,)#
schema#
serializer_class#

alias of CourseEnrollmentsApiListSerializer

throttle_classes = (<class 'openedx.core.djangoapps.enrollments.views.EnrollmentUserThrottle'>,)#
class openedx.core.djangoapps.enrollments.v2.views.UserRolesView(**kwargs)#

Bases: StandardizedErrorMixin, APIView

List the current user’s course-level roles.

authentication_classes = (<class 'edx_rest_framework_extensions.auth.jwt.authentication.JwtAuthentication'>, <class 'openedx.core.djangoapps.enrollments.views.EnrollmentCrossDomainSessionAuth'>)#
get(request)#

List the current user’s course-level roles.

Optionally filtered by course_key (preferred, OEP-68) or course_id (deprecated alias). When both are present, course_key wins and the response carries the ADR 0033 Deprecation HTTP header.

permission_classes = (<class 'openedx.core.lib.api.permissions.ApiKeyHeaderPermissionIsAuthenticated'>,)#
schema#
serializer_class#

alias of UserRolesResponseSerializer

throttle_classes = (<class 'openedx.core.djangoapps.enrollments.views.EnrollmentUserThrottle'>,)#

Module contents#