From e950bb8de538eddf942eee94a45dea5f7865fdf1 Mon Sep 17 00:00:00 2001 From: Muhammad Faraz Maqsood Date: Mon, 6 Jul 2026 12:54:55 +0500 Subject: [PATCH 1/2] 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 --- cms/lib/spectacular.py | 9 ++++++--- lms/envs/common.py | 1 + lms/envs/devstack.py | 13 +++++++++++++ lms/envs/production.py | 13 +++++++++++++ lms/lib/spectacular.py | 17 +++++++++++++++++ lms/urls.py | 6 ++++++ openedx/envs/common.py | 1 + 7 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 lms/lib/spectacular.py diff --git a/cms/lib/spectacular.py b/cms/lib/spectacular.py index 57a11513cb60..90bce5668fec 100644 --- a/cms/lib/spectacular.py +++ b/cms/lib/spectacular.py @@ -12,9 +12,12 @@ def cms_api_filter(endpoints): CMS_PATH_PATTERN = re.compile(r"^/api/contentstore/v\d+/") for path, path_regex, method, callback in endpoints: - if CMS_PATH_PATTERN.match(path) or ( - path.startswith("/api/courses/") - and "bulk_enable_disable_discussions" in path + if ( + CMS_PATH_PATTERN.match(path) + or ( + path.startswith("/api/courses/") + and "bulk_enable_disable_discussions" in path + ) ): filtered.append((path, path_regex, method, callback)) diff --git a/lms/envs/common.py b/lms/envs/common.py index 828c7874152b..efaf175eb44d 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -2040,6 +2040,7 @@ # API Documentation 'drf_yasg', + 'drf_spectacular', # edx-drf-extensions 'csrf.apps.CsrfAppConfig', # Enables frontend apps to retrieve CSRF tokens. diff --git a/lms/envs/devstack.py b/lms/envs/devstack.py index 1043016a6b2b..ba3e58949d58 100644 --- a/lms/envs/devstack.py +++ b/lms/envs/devstack.py @@ -582,6 +582,19 @@ def should_show_debug_toolbar(request): # pylint: disable=missing-function-docs 'COMPLETE', ] +###################### drf-spectacular (LMS enrollment schema) ###################### +SPECTACULAR_SETTINGS = { + 'TITLE': 'LMS Enrollment API', + 'VERSION': '0.1.0', + 'SERVE_INCLUDE_SCHEMA': False, + 'PREPROCESSING_HOOKS': ['lms.lib.spectacular.lms_api_filter'], + 'SCHEMA_PATH_PREFIX': '/api/enrollment', + 'SCHEMA_PATH_PREFIX_TRIM': '/api/enrollment', + 'SERVERS': [ + {'url': LMS_ROOT_URL, 'description': 'Local'}, # noqa: F405 + ], +} + ################# New settings must go ABOVE this line ################# ######################################################################## # See if the developer has any local overrides. diff --git a/lms/envs/production.py b/lms/envs/production.py index b954841d59bc..f60ae0b8850d 100644 --- a/lms/envs/production.py +++ b/lms/envs/production.py @@ -397,6 +397,19 @@ def get_env_setting(setting): MIDDLEWARE.extend(_YAML_TOKENS.get('EXTRA_MIDDLEWARE_CLASSES', [])) # noqa: F405 +###################### drf-spectacular (LMS enrollment schema) ###################### +SPECTACULAR_SETTINGS = { + 'TITLE': 'LMS Enrollment API', + 'VERSION': '0.1.0', + 'SERVE_INCLUDE_SCHEMA': False, + 'PREPROCESSING_HOOKS': ['lms.lib.spectacular.lms_api_filter'], + 'SCHEMA_PATH_PREFIX': '/api/enrollment', + 'SCHEMA_PATH_PREFIX_TRIM': '/api/enrollment', + 'SERVERS': [ + {'url': LMS_ROOT_URL, 'description': 'Local'}, # noqa: F405 + ], +} + ####################################################################################################################### #### DERIVE ANY DERIVED SETTINGS #### diff --git a/lms/lib/spectacular.py b/lms/lib/spectacular.py new file mode 100644 index 000000000000..433b05f6db11 --- /dev/null +++ b/lms/lib/spectacular.py @@ -0,0 +1,17 @@ +"""Helper functions for drf-spectacular (LMS schema).""" + +import re + + +def lms_api_filter(endpoints): + """ + Pre-processing hook: keep only enrollment v2 endpoints tagged for the SDK. + """ + filtered = [] + ENROLLMENT_PATH_PATTERN = re.compile(r"^/api/enrollment/v\d+/") + + for path, path_regex, method, callback in endpoints: + if ENROLLMENT_PATH_PATTERN.match(path): + filtered.append((path, path_regex, method, callback)) + + return filtered diff --git a/lms/urls.py b/lms/urls.py index 280c739c9bce..032dbb8b1c60 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -10,6 +10,7 @@ from django.urls import include, path, re_path from django.utils.translation import gettext_lazy as _ from django.views.generic.base import RedirectView +from drf_spectacular.views import SpectacularAPIView from edx_api_doc_tools import make_docs_urls from edx_django_utils.plugins import get_plugin_url_patterns from submissions import urls as submissions_urls @@ -1068,3 +1069,8 @@ urlpatterns += [ path('xqueue/', include((submissions_urls, 'submissions'), namespace='submissions')), ] + +# LMS API schema for openedx-platform-sdk generation. +urlpatterns += [ + path('lms-api/schema/', SpectacularAPIView.as_view(), name='lms-schema'), +] diff --git a/openedx/envs/common.py b/openedx/envs/common.py index 31b0015d34d9..673b915f1d0a 100644 --- a/openedx/envs/common.py +++ b/openedx/envs/common.py @@ -836,6 +836,7 @@ def add_optional_apps(optional_apps, installed_apps): 'registration_validation': '30/minute', 'high_service_user': '2000/minute', }, + 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema', } # .. setting_name: REGISTRATION_VALIDATION_RATELIMIT From f6d2773ce3f2ffdb674f5e1d21a5fc7bf09f538e Mon Sep 17 00:00:00 2001 From: Muhammad Faraz Maqsood Date: Mon, 6 Jul 2026 14:47:21 +0500 Subject: [PATCH 2/2] chore: move settings to common.py --- lms/envs/common.py | 12 ++++++++++++ lms/envs/devstack.py | 14 +++----------- lms/envs/production.py | 14 +++----------- 3 files changed, 18 insertions(+), 22 deletions(-) diff --git a/lms/envs/common.py b/lms/envs/common.py index efaf175eb44d..2d763827203c 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -2126,6 +2126,18 @@ 'DEEP_LINKING': True, } +###################### drf-spectacular (LMS enrollment schema) ###################### +SPECTACULAR_SETTINGS = { + 'TITLE': 'LMS Enrollment API', + 'VERSION': '0.1.0', + 'SERVE_INCLUDE_SCHEMA': False, + 'PREPROCESSING_HOOKS': ['lms.lib.spectacular.lms_api_filter'], + 'SCHEMA_PATH_PREFIX': '/api/enrollment', + 'SCHEMA_PATH_PREFIX_TRIM': '/api/enrollment', + # SERVERS is environment-specific (LMS_ROOT_URL differs per env) and is + # set in devstack.py / production.py. +} + ######################### MARKETING SITE ############################### MKTG_URL_LINK_MAP.update({ # noqa: F405 diff --git a/lms/envs/devstack.py b/lms/envs/devstack.py index ba3e58949d58..68fbd5cc1017 100644 --- a/lms/envs/devstack.py +++ b/lms/envs/devstack.py @@ -583,17 +583,9 @@ def should_show_debug_toolbar(request): # pylint: disable=missing-function-docs ] ###################### drf-spectacular (LMS enrollment schema) ###################### -SPECTACULAR_SETTINGS = { - 'TITLE': 'LMS Enrollment API', - 'VERSION': '0.1.0', - 'SERVE_INCLUDE_SCHEMA': False, - 'PREPROCESSING_HOOKS': ['lms.lib.spectacular.lms_api_filter'], - 'SCHEMA_PATH_PREFIX': '/api/enrollment', - 'SCHEMA_PATH_PREFIX_TRIM': '/api/enrollment', - 'SERVERS': [ - {'url': LMS_ROOT_URL, 'description': 'Local'}, # noqa: F405 - ], -} +SPECTACULAR_SETTINGS['SERVERS'] = [ # noqa: F405 + {'url': LMS_ROOT_URL, 'description': 'Local'}, # noqa: F405 +] ################# New settings must go ABOVE this line ################# ######################################################################## diff --git a/lms/envs/production.py b/lms/envs/production.py index f60ae0b8850d..b38a26822f98 100644 --- a/lms/envs/production.py +++ b/lms/envs/production.py @@ -398,17 +398,9 @@ def get_env_setting(setting): ###################### drf-spectacular (LMS enrollment schema) ###################### -SPECTACULAR_SETTINGS = { - 'TITLE': 'LMS Enrollment API', - 'VERSION': '0.1.0', - 'SERVE_INCLUDE_SCHEMA': False, - 'PREPROCESSING_HOOKS': ['lms.lib.spectacular.lms_api_filter'], - 'SCHEMA_PATH_PREFIX': '/api/enrollment', - 'SCHEMA_PATH_PREFIX_TRIM': '/api/enrollment', - 'SERVERS': [ - {'url': LMS_ROOT_URL, 'description': 'Local'}, # noqa: F405 - ], -} +SPECTACULAR_SETTINGS['SERVERS'] = [ # noqa: F405 + {'url': LMS_ROOT_URL, 'description': 'Local'}, # noqa: F405 +] ####################################################################################################################### #### DERIVE ANY DERIVED SETTINGS