Repository navigation
sandbox.opencraft.com deployment tracking | DO NOT CLOSE - #824
Draft
kaustavb12 wants to merge 929 commits into
Draft
kaustavb12 wants to merge 929 commits into
kaustavb12 wants to merge 929 commits into
Conversation
kaustavb12
marked this pull request as draft
February 6, 2026 08:34
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>
…lution ENABLE_MKTG_SITE was a feature flag that selected between two URL resolution strategies: MKTG_URLS (external marketing site) vs MKTG_URL_LINK_MAP (local Django URL reversal). The local-template path is now legacy; all operators are expected to use MKTG_URLS. This commit removes the flag and its conditional branches, keeping the MKTG_URLS-based behaviour unconditionally: - Drop the setting definition and toggle annotation from openedx/envs/common.py - Remove the override from lms/envs/devstack.py and both mock.yml files - Simplify marketing_link() and is_marketing_link_set() in edxmako/shortcuts.py to always consult MKTG_URLS; drop the MKTG_URL_LINK_MAP fallback branches and the now-unused NoReverseMatch/reverse/set_custom_attribute/get_current_request_hostname imports - Update validate_marketing_site_setting() in checks.py to always validate MKTG_URLS rather than gating validation on ENABLE_MKTG_SITE - Trim edxmako/tests.py: remove False-path test variants and the MKTG_URL_LINK_MAP link-reversal test; each remaining test now exercises a single, unconditional code path Part of #37053 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
index() (served at /) now unconditionally checks MKTG_URLS['ROOT'] and
redirects when it differs from LMS_ROOT_URL, matching the previous
ENABLE_MKTG_SITE=True behaviour. The catalog MFE redirect
(ENABLE_CATALOG_MICROFRONTEND) still fires first and is unaffected.
Also fixes a pre-existing bug where log.error() in the NoReverseMatch
handler was passed multiple f-strings as positional args, causing a
TypeError when that branch was reached.
courses() (served at /courses) now resolves in priority order:
1. Redirect to the catalog MFE if ENABLE_CATALOG_MICROFRONTEND is set
2. Permanent redirect to marketing_link('COURSES') from MKTG_URLS
3. Render the local course list via courseware.views.views.courses()
The old ENABLE_MKTG_SITE gate and COURSES_ARE_BROWSABLE guard are
removed. The local course list fallback (case 3) preserves behaviour
for operators who have neither the catalog MFE nor MKTG_URLS['COURSES']
configured.
The stanford-style theme footer is updated to use marketing_link()
instead of reverse() for the 'about' and 'tos' URL routes, which are
removed in a later commit.
Tests drop their @override_settings(ENABLE_MKTG_SITE=True) decorators;
the test names and assertions are otherwise unchanged.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… API
- course_about_url in enrollment emails now uses
get_link_for_about_page(), resolving the marketing URL, social sharing
URL, or LMS about URL as a fallback
- get_link_for_about_page() uses course.marketing_url unconditionally
- has_marketing_url() returns bool(self.marketing_url) directly
- url_to_enroll() always returns marketing_link('COURSES')
- _course_home_redirect_enabled() only checks ENABLE_COURSE_HOME_REDIRECT;
default changed from True to False because the flag previously had no
effect when ENABLE_MKTG_SITE=False (the old default) — operators who
relied on ENABLE_MKTG_SITE=True to activate the course-about redirect
must now set ENABLE_COURSE_HOME_REDIRECT=True explicitly
- Remove ENABLE_MKTG_SITE patches from courseware, instructor, and
mobile API tests; merge redundant test cases where applicable
BREAKING CHANGE: ENABLE_COURSE_HOME_REDIRECT now defaults to False.
Operators who previously had ENABLE_MKTG_SITE=True and want the
course about page to redirect to the learning MFE must explicitly set
ENABLE_COURSE_HOME_REDIRECT=True in their configuration.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Email change confirmation contact link now always comes from
marketing_link('CONTACT') via MKTG_URLS. Remove the fallback that
constructed a local URL when the flag was False.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- contentstore utils: marketing_enabled is always True - registration tests: remove ENABLE_MKTG_SITE=False test cases that tested now-dead local URL generation for honor code and TOS links; remove ENABLE_MKTG_SITE=True decorators from the remaining tests - site_configuration test mixin: remove ENABLE_MKTG_SITE example key - schedules README: remove ENABLE_MKTG_SITE from example config Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Marketing navigation links (HOW_IT_WORKS, COURSES, SCHOOLS) are now always rendered in the unauthenticated nav bars. marketing_link() falls back to '#' when a URL is not configured in MKTG_URLS. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This setting was the fallback URL resolution mechanism used when ENABLE_MKTG_SITE=False. Now that the flag is gone, only MKTG_URLS is used for marketing link resolution. - Remove MKTG_URL_LINK_MAP definition from openedx/envs/common.py - Remove LMS-specific update block from lms/envs/common.py - Remove test environment override from lms/envs/test.py - Remove YAML loading and dict-update from lms and cms production envs - Remove the dynamic URL registration loop from static_template_view - Remove MKTG_URL_LINK_MAP.keys() union from the marketing link context processor in edxmako/shortcuts.py Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove the 14 template files that were served locally when ENABLE_MKTG_SITE=False. These pages (about, blog, contact, donate, faq, help, honor, jobs, media-kit, news, press, privacy, tos, sitemap.xml) should now only live on the external marketing site configured via MKTG_URLS. Also remove the stanford-style theme overrides for about.html and tos.html, the hardcoded URL patterns for the deleted templates, the honor.html iframing special case from the render view, and the tests that exercised these now-deleted routes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The E003 check required MKTG_URLS['ROOT'] to be defined, but this key
is optional. The index() view in branding/views.py already handles the
case where ROOT is absent:
root_url = marketing_urls.get("ROOT")
if root_url and root_url != getattr(settings, "LMS_ROOT_URL", None):
return redirect(root_url)
Previously, E003 was only triggered when ENABLE_MKTG_SITE=True (the
check was guarded by that flag). After removing ENABLE_MKTG_SITE in
#38720, the check runs
unconditionally and fires in any environment where MKTG_URLS has no
ROOT key (including test and CMS environments). Since ROOT is genuinely
optional, the check is incorrect and should not be enforced.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ails
With ENABLE_MKTG_SITE removed, get_email_params() now always sets
course_about_url via get_link_for_about_page(), which never returns None
(it falls back to the LMS course about URL). The instructor "allowed
enroll" and "add beta tester" ACE email templates branched on
`course_about_url is not None`, keeping an else-branch for the old
marketing-site-enabled case where course_about_url was None.
Those conditionals are now always true and their else-branches are dead:
- allowedenroll: "You can then enroll in {{ course_name }}."
- addbetatester: "Visit {{ site_name }} to enroll in this course and
begin the beta test."
Collapse the always-true `course_about_url is not None` guards to the
unconditional branch and delete the unreachable text (both .txt and .html
variants). No behavior change for any reachable input.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Commit d83c598 added display_number and display_org to CourseCommonSerializerV2 (which CourseHomeTabSerializerV4 aliases), but only updated the v2 test. The v4 test_home_page_response was left expecting the old 10-field format and now fails.
…issing (#39152) The clipboard holds a *copy* of a block, so its XBlock plugin may no longer be installed (e.g. a custom XBlock was removed from the platform). StagedContentSerializer.get_block_type_display() called xblock_type_display_name(), which raises PluginMissingError in that case, so reading the clipboard raised, and because the clipboard is embedded in the course index response (initial_user_clipboard), the whole course index request 500s -- for every course, for any user who happens to have such a clipboard. The same applies to the /api/content-staging/v1/clipboard/ endpoint, and there is no API to discard the stale clipboard, so authors get stuck. Fall back to the raw block type, mirroring openedx.core.djangoapps.xblock.api.xblock_type_display_name, which already handles a missing plugin this way.
* refactor: migrate direct drf-yasg usage to drf-spectacular Converts @swagger_auto_schema to @extend_schema in the five modules that import drf_yasg directly, following the drf-spectacular migration guide: https://drf-spectacular.readthedocs.io/en/latest/drf_yasg.html Structured openapi.Schema objects become inline_serializer so they get named components in the generated schema; untyped ones become OpenApiTypes.OBJECT. openapi.Parameter becomes OpenApiParameter. The other four drf_yasg users go through edx-api-doc-tools and will be migrated with it. * refactor: migrate openedx/core from edx-api-doc-tools to drf-spectacular Converts @apidocs.schema to @extend_schema across openedx/core, replacing the parameter helpers with OpenApiParameter and string response descriptions with OpenApiResponse. bookmarks/serializers.py inlines is_schema_request, which has no drf-spectacular equivalent, and extends it to recognise drf-spectacular's swagger_fake_view alongside drf-yasg's format=openapi. * refactor: migrate lms from edx-api-doc-tools to drf-spectacular Converts @apidocs.schema and @Schema to @extend_schema across the lms app, excluding instructor. Parameter helpers become OpenApiParameter and string response descriptions become OpenApiResponse. discussion/rest_api/views.py also drops its remaining direct drf_yasg import, which was interleaved with the apidocs decorators. * refactor: migrate instructor from edx-api-doc-tools to drf-spectacular Converts the 26 @apidocs.schema decorators in the instructor v1 and v2 APIs to @extend_schema. The course_id, problem, and exam_id path parameters were repeated verbatim across 29 decorators; those are now module-level constants. * refactor: migrate cms from edx-api-doc-tools to drf-spectacular Converts the remaining @apidocs.schema decorators across contentstore and modulestore_migrator to @extend_schema. The three class-level @apidocs.schema_for decorators become @extend_schema_view, splitting each docstring into summary and description as schema_for did. Files that already imported drf-spectacular for the FC-0118 work have their import lines merged rather than duplicated. * feat: add drf-spectacular extensions for BaseSerializer subclasses Six serializers subclass BaseSerializer, which has no `fields` attribute, so drf-spectacular raises AttributeError when generating a schema that covers them. Each extension declares the type its serializer produces. Registered from CommonInitializationConfig.ready() so they load in both services regardless of which schema is being generated. * refactor: serve /api-docs with drf-spectacular Replaces make_docs_urls with SpectacularAPIView, SpectacularSwaggerView and SpectacularRedocView, preserving the swagger.json, swagger.yaml, api-docs/ and swagger/ routes and their URL names. The UI views reverse their schema URL without arguments, so api-docs/schema/ is registered alongside the format-suffixed routes. /api-docs serves the full API surface via custom_settings, leaving SPECTACULAR_SETTINGS to the narrower Authoring and Enrollment schemas the SDK consumes. Also removes drf_yasg from INSTALLED_APPS, drops SWAGGER_SETTINGS, and converts the docs security definitions to OpenAPI 3 form. `make swagger` now runs `manage.py lms spectacular`, since generate_swagger came from drf_yasg; docs_settings applies the same unfiltered configuration as /api-docs so the generated file still covers the whole surface. edx-api-doc-tools and drf-yasg remain installed as transitive dependencies of openedx-authz and django-user-tasks respectively. * fix: resolve review comments - restore server-side caching on the OpenAPI schema endpoints, which edx-api-doc-tools provided via SchemaView.as_cached_view - point the api-docs test at /api-docs/schema/ so it exercises schema generation again, and add the CMS equivalent - serve Swagger UI and ReDoc assets from drf-spectacular-sidecar instead of drf-spectacular's unpinned jsdelivr CDN defaults - correct the /api-docs comment: edx-api-doc-tools was /api/-only, so this widens the documented surface rather than being "the opposite" - drop the LMS-only claim about API_ACCESS_MANAGER_EMAIL, which lives in openedx/envs/common.py and is shared by both services - point the schema_extensions docstring at CommonInitializationConfig, the actual registration site - remove the dead format=openapi branch from is_schema_request, since nothing in the platform serves drf-yasg any more - delete the now-empty Django Rest Framework banner in lms/envs/common.py * fix: cache the api-docs schema compressed cache_page pickles the whole Response, and this schema is over memcached's 1MB item limit. CACHES['default'] has ignore_exc set, so the oversized set failed silently and Django dropped the key — the endpoint looked cached while regenerating on every request. cached_schema_view() stores the zlib-compressed body instead, keyed on the path and the negotiated representation, which brings it to about a tenth of the limit. add_never_cache_headers restores the no-store that drf-yasg sent, and a falsy OPENAPI_CACHE_TIMEOUT still goes straight to the view so devstack is unchanged. * fix: key the api-docs schema cache on method and language The cached branch stored anything that came back 200, so DRF's OPTIONS metadata document landed under the GET key and was served to the next GET. cache_page only ever stored GET and HEAD; do the same. drf-spectacular renders the document under whatever language is active, and LocaleMiddleware sets that from the request before the view runs, so one locale's document could be served to another's. Add the active language to the key. Adds a test to both suites for the two cases. The suite runs with OPENAPI_CACHE_TIMEOUT = 0 and a DummyCache, so it overrides both to reach the cached branch at all. * fix: key the api-docs schema cache per service The LMS and the CMS serve this view at the same path, under the same Accept and language, and share one cache by default, so both landed on the same key and whichever generated first served its document at the other's URL. cache_page avoided this by hashing build_absolute_uri(), host included. Add ROOT_URLCONF to the key. * test: cover the URLconf in the api-docs schema cache key LMS and CMS serve /api-docs/schema/ at the same path and share one cache, so the key must include ROOT_URLCONF. The existing cache test runs under a single URLconf and could not detect that line being dropped; this test serves the view from a second URLconf and fails without it.
* feat: add GH workflow to generate openapi schema Add GH workflow to automatically generate openapi schema whenever view file tagged with the "openedx-platform-sdk" @extend_schema tag changes * feat: address comments * fix: address comments on generate schemas workflow address comments on generate_openapi_schemas workflow - weekly schedule - use team-reviewers param * fix: write the generated schemas to docs/ - write to docs/lms-openapi.yaml and docs/cms-openapi.yaml instead of creating new schema files at the repo root - generate the LMS schema under docs.docs_settings so the workflow writes the same full API surface `make swagger` does, rather than overwriting the docs schema with the narrow SDK-facing one * fix: address comments on the schema generation workflow uv sync installed no groups, so ora2 was missing and both schema steps died with ModuleNotFoundError. Use the docs group, which pulls in bundled, the same set .readthedocs.yaml installs. manage.py cms defaults to cms.envs.devstack, which needs a CMS_CFG file CI does not have. Run it under cms.envs.development, and give cms/envs/common.py the schema title and version — the Authoring API's SPECTACULAR_SETTINGS lives in devstack and production, so development would otherwise emit an untitled 0.0.0 document. Also align the setup-uv pin with the other workflows, and add branch-suffix and workflow_ref provenance to match the other PR-opening ones. * fix: give the schema job a database and the CMS schema its filtering Generation needs migrated tables, not just a server: drf-spectacular evaluates a queryset while building a warning for edxval's VideoList. Add the mysql service and a migrate step the way migrations-check.yml does. cms.envs.development inherited the Authoring API's title and version but none of its filtering, so docs/cms-openapi.yaml came out at 235 paths with the rest of the service in it. Only SERVERS and the long DESCRIPTION depend on CMS_BASE and AUTHORING_API_URL, so the hooks and the path prefix move down to cms/envs/common.py as well. 57 paths. * fix: give the migrate steps mongo and their settings split_modulestore_django's 0002_data_migration reads the modulestore, so migrating from empty needs Mongo even though generation doesn't. Add the service and the user setup step from migrations-check.yml. manage.py falls back to devstack without DJANGO_SETTINGS_MODULE, and devstack's DATABASES is empty without an LMS_CFG, so both migrate steps failed before reaching the service. Run each under the same settings as its generate step.
Commit generated by workflow `openedx/openedx-platform/.github/workflows/generate_openapi_schemas.yml@refs/heads/master`
…am-reviewers fix: use the requirements bot token to open the OpenAPI schema PR
…8d1866 chore: update OpenAPI schemas for SDK generation
…39156) Several log statements uses usernames, emails, or full names without checking SQUELCH_PII_IN_LOGS, so PII reached the logs even though LMS enables the setting. These now log the numeric user ID when the setting is on. When it is off, the log output is unchanged. Adds helpers to log_utils so the check lives in one place. Logging user.id directly is always considered safe; the helpers are only needed where the username or email is preferred in deployments that allow PII in logs.
Discussion topic titles come from course content: a unit's display name, or "section|subsection|unit" when a unit is no longer discussable. Either can exceed DiscussionTopicLink.title's 255-character limit. On strict MySQL that raises a DataError, and because the sync runs in a single transaction (and the error is swallowed by send_robust), one oversized title silently blocks the entire course's topic sync: new units get no topics and removed units are never disabled. Truncate the title wherever the sync sets it. Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…39192) CourseEnrollmentsApiListSerializer's Meta subclasses its parent's, so it inherited ref_name = "CourseEnrollment". Both serializers were published under one schema component, and in the full /api-docs schema the subset serializer won: the enrollment create and retrieve endpoints were documented with course_id instead of course_details, and clients generated from the schema were typed accordingly.
Commit generated by workflow `openedx/openedx-platform/.github/workflows/generate_openapi_schemas.yml@refs/heads/master`
Guard the LMS "Skip to main content" link in main.html behind `not disable_header`, so it renders only on full-chrome pages and is omitted from chromeless/iframed contexts (Learning MFE render_xblock, LTI provider, plugin_api fragments) where no in-page header exists to skip to and a global skip link is already present.
Have the CCX Coach v2 metadata endpoint include the student_grades tab only when the writable gradebook is available for the CCX — both the grades.writable_gradebook waffle flag (evaluated against the CCX key) and the WRITABLE_GRADEBOOK_URL setting (read through site configuration).
…9a94d1 chore: update OpenAPI schemas for SDK generation
* feat: apply FC-0118 ADRs to youtube transcripts APIs
Add a conforming v1 surface for the two YouTube transcript endpoints at
/api/authoring/v1/courses/{course_key}/youtube_transcript_{checks,imports}/.
The import operation becomes a POST because the legacy GET performs four
domain writes. Responses are serializer-produced and errors use the
standardized envelope. The v0 routes are untouched and keep resolving at
all four of their live spellings; they are marked deprecated in the schema.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: address review feedback on the youtube transcripts APIs
- Publish full paths from cms/envs/common.py as well. cms.envs.development
inherits these settings to generate the committed docs/cms-openapi.yaml,
which otherwise mixed trimmed /v0/... paths with untrimmed
/api/authoring/v1/... ones and marked nothing deprecated. Drop
SCHEMA_PATH_PREFIX_TRIM, widen SCHEMA_PATH_PREFIX to both mounts, and add
the hook that marks the superseded v0 paths deprecated.
- Require a "youtube" entry in the import's videos, so a missing one is
reported on videos rather than as a failure on locator.
- Test a locator that is neither a course nor a V2 library block, which the
handlers reject with TranscriptsRequestValidationException.
* fix: stop registering error types in the youtube transcripts service
The registry in edx_rest_framework_extensions.errors is process-wide, so
registering MethodNotAllowed and ParseError here changed the error type of
every standardized Studio endpoint, not only these two. Left to an
edx-drf-extensions follow-up, as in #39102; the 405 tests assert the status
only.
* docs: note why a YouTube failure is a 400 in the transcripts service
A failure on YouTube's side is reported as a 400, matching v0. It can
become a 502 once edx-drf-extensions registers an error type for
upstream failures.
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat: apply FC-0118 ADRs to course content APIs
Add a conforming v2 surface for the textbooks and video-settings
endpoints at /api/authoring/v2/courses/{course_key}/{textbooks,video_settings}/.
The video endpoint's read path no longer reconciles stale upload status,
fixing a runtime-verified write-on-GET bug; that reconciliation stays
on the unchanged legacy route. The v1 routes are untouched and keep
resolving exactly as they do today.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix: address review feedback on the course content APIs
- Publish full paths from cms/envs/common.py as well. cms.envs.development
inherits these settings to generate the committed docs/cms-openapi.yaml,
which otherwise mixed trimmed paths (/v1/textbooks/{course_id}) with full
ones (/api/authoring/v2/courses/{course_key}/textbooks/). Drop
SCHEMA_PATH_PREFIX_TRIM and widen SCHEMA_PATH_PREFIX to both mounts.
- Note when CourseTextbookPagination can be removed in favour of
DefaultPagination.
* fix: address second round of review feedback on the course content APIs
- Stop registering MethodNotAllowed and NotAcceptable error types. The
registry in edx_rest_framework_extensions.errors is process-wide, so the
registrations changed the error type of every standardized Studio endpoint.
Left to an edx-drf-extensions follow-up, as in #39102.
- Serve stored textbook chapters missing title or url, or holding null for
either, with an empty string, and a stored null chapter list as an empty
list, instead of a 500 and a null the schema does not allow.
- Drop ?view=full and previous_uploads from video_settings. #39102 adds a
paginated videos/ collection with the same uploads and status logic, and
nothing calls the full view, which was new in v2.
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
The start_date_on_or_after and start_date_on_or_before params of GET /api/contentstore/v2/home/courses take a full ISO 8601 datetime with a UTC offset or Z, for example 2027-07-01T00:00:00+04:00. The filter compares them to CourseOverview.start as instants, inclusive at both ends, so the caller decides where its own calendar day begins and ends. A user east or west of UTC now finds the courses Studio shows on the day they picked. The params used to take a YYYY-MM-DD date that the backend treated as a UTC day. A user in another time zone who picked a local day could miss a course Studio shows on that day: a course starting at 2027-06-30T22:00Z is July 1 in UTC+4, but the old filter only returned it for June 30. Naive datetimes, bare dates, and unparseable values return 400 with a message that includes the received value and asks for an offset. The backend does not assume UTC for a naive value, because that would reproduce the original bug without any visible error. A literal + in a query string decodes to a space, so a client that does not encode an offset sends "10:00:00 04:00". A space directly before a trailing HH:MM or HHMM offset is read as +, so that request still works. Clients should still percent-encode + as %2B. Both params are parsed before the early return in get_courses_accessible_to_user, so an invalid value gives a 400 whether or not the user has accessible courses. The filter only landed on master after the last named release, so this is a fix and not a deprecation. Clients that send YYYY-MM-DD now get a 400, which includes the Competency Management course search until it sends offsets. Related to openedx/openedx-core#842 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 10.1.0 to 10.2.0. - [Release notes](https://github.com/astral-sh/setup-uv/releases) - [Commits](astral-sh/setup-uv@v10.1.0...c18668a) --- updated-dependencies: - dependency-name: astral-sh/setup-uv dependency-version: 10.2.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
…dized APIs (#39078) * feat: wire shared opaque-key URL path converters from edx-drf-extensions (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. * feat: apply ADR 0038 to Xblock v1 (/api/authoring/v1/xblocks/) 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. * feat: apply ADR 0038 to CourseHome v3 (/api/authoring/v3/home/, x-internal 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. * feat: apply ADR 0038 to CourseHome v4 (/api/authoring/v4/courses/) 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. * feat: apply ADR 0038 to Course Detail v3 (courses/{course_key}/details/) 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. * feat: apply ADR 0038 to AuthorGrading v3 (courses/{course_key}/grading/) 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. * feat: apply ADR 0038 to Enrollment v2 (required trailing slashes + conforming 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. * chore: reduce redundant docstrings and comments across the ADR 0038 migration Review feedback: the new authoring_urls.py module docstrings restated ADR 0038's rules rather than describing the module, and several comments repeated what the commits already say. The three urls.py modules now carry a one-line docstring matching their siblings in the same package, the per-rule conformance lists and the "same view, same contract" notes are gone, and the test-class docstrings, Enrollment v2 docstring, and spectacular helpers are trimmed; comments that prevent a mistake are kept but shortened (conforming routes pass a parsed CourseKey/UsageKey where legacy routes pass the raw string; POSTPROCESSING_HOOKS replaces rather than extends drf-spectacular's default list). Prose only — with docstrings stripped, all 19 files parse to ASTs identical to the previous revision, ruff passes, and no view or serializer docstring is touched. * chore: drop ADR and OEP names from the code this branch adds Review feedback on #39037: the code should follow the standards without naming the documents. The three authoring urls.py modules now read "Authoring API vN URLs.", the URL-structure test banners and the Enrollment v2 module docstring lose their rule references, and the remaining comments say what the code does instead — deprecation window rather than OEP-21 window, "whose course_key path converter hands views a parsed key" rather than a rule number. Only lines this branch adds are touched; the pre-existing ADR references elsewhere in these files are left alone, the one exception being the ADR 0028 line in the Enrollment v2 module docstring, which this branch was already rewriting. Prose only apart from one assert message in the new URL-structure test, which now reads "missing error-envelope field". ruff passes. * fix: emit full paths in the OpenAPI schemas instead of trimming the prefix SCHEMA_PATH_PREFIX_TRIM is a boolean that we had set to a string in three places; it only worked because the string was truthy. More importantly the trimming itself produced wrong URLs: the LMS spec advertised /v2/enrollments/ against a bare LMS_ROOT_URL server, and adding /api/authoring/ to the CMS spec left those paths untrimmed beside trimmed contentstore ones. Widening the prefix regex would collapse /api/contentstore/v3/home/ and /api/authoring/v3/home/ onto one key. Drop the trim, drop the CMS-contentstore server that existed only to compensate, keep SCHEMA_PATH_PREFIX for tag extraction, and update both post-processing hooks to full paths. Adds tests for the hooks, which had none. * fix: deterministic operationIds, restore removed URL names, compare view actions Review feedback on the dual mounts. A legacy mount and its conforming mount serve the same view and tokenize to the same operationId, so drf-spectacular was breaking the tie with a numeral suffix in registration order: the CMS schema put _2 on the legacy path and the LMS schema put it on the conforming one, so regenerating the SDK would have renamed modules by accident. Each service now has an AutoSchema subclass wired in as DEFAULT_SCHEMA_CLASS that suffixes the legacy address with _legacy and leaves the conforming address with the clean id, so a regenerated SDK follows the same function name onto the non-deprecated address. The two Studio home viewsets carry their own schema instances, which bypass the default, so those are rebased onto the CMS class as well; tests pin the ids for each pair, assert the default wiring, and assert the per-view schemas chain the CMS class. The LMS legacy predicate is shared between the schema class and the deprecation hook. Two URL names had been dropped although every address was kept: enrollment-v2-roles and the course-only form of enrollment-v2-retrieve. Both are registered again on their paths after the new names, so reverse() keeps working for out-of-tree callers and resolution stays on the first entry. The route-sharing tests compared .func.cls, which is the same object for a ViewSet whatever actions a mount wires up; they now compare .func.actions as well, and the v1 test covers the list route too. * fix: suffix only colliding legacy operationIds; ignore DRF's head in route tests Only the legacy /api/contentstore/v3/home/ paths and the slashless /api/enrollment/v2/enrollments tokenize to the same operationId as their conforming twins, so only they get the _legacy suffix. Every other legacy operation keeps its existing operationId, and with it the SDK module generated from it. DRF adds a head entry to a mount's actions the first time it serves a GET, so the route-equivalence tests compare the action maps through routed_actions(), which drops it. The SCHEMA_PATH_PREFIX comment now says the prefix also derives every operationId, not just the tags. * fix: answer 404 for version-only course keys on the conforming routes The shared course_key converter accepts keys that name only a version (course-v1:version@..., ccx-v1:version@...+ccx@...). Looking one up raises InvalidKeyError in CourseKeyField, so the conforming v3 details and grading routes and the enrollment v2 courses/ and enrollments/{username},{course_key}/ routes answered 500 to every caller. The legacy routes never see these keys: COURSE_ID_PATTERN rejects them at routing. resolve_course_key and the enrollment views now treat a key without an org as not found. * refactor: use the parsed opaque key from the conforming routes directly The conforming routes' path converters hand views a parsed key, which XblockViewSet.initial() and the two enrollment v2 views turned back into a string only to parse it again. They now use the parsed key as is and parse only the legacy routes' raw string. XblockViewSet still passes the string to its actions, which parse it themselves.
* chore: bump openedx-forum to 0.4.8 Picks up openedx/forum#292, which fixes answered questions losing their endorsed state in MySQL-backed thread lists. * chore: lock openedx-forum 0.4.8 in uv.lock --------- Co-authored-by: Peter Pinch <pdpinch@mit.edu>
* fix: give each course import its own working directory Every import of a given course used a single working directory derived from the course key alone, shared by the import task and by both upload views. Two imports of the same course therefore overwrote each other's archive, and whichever import finished first deleted the other's extracted OLX mid-run. The surviving task failed with a bare filesystem error: "[Errno 116] Stale file handle" on an NFS-backed GITHUB_REPO_ROOT, "[Errno 2]" on a local disk. Add course_import_working_dir() and remove_course_import_working_dir(), and use them from import_olx and from both upload views so that every import and every upload gets a private subdirectory. Both upload views now drop their staging copy once the archive is in storage; previously they relied on the import task's cleanup of the shared directory. Cleanup no longer raises, so a failing cleanup can no longer mask the error that triggered it. * fix: rename test helper arg that shadowed the rest_framework status import * fix: keep course import cleanup safe for concurrent and repeated uploads Put each import working directory directly under GITHUB_REPO_ROOT instead of under a shared per-course parent. Removing that parent once it looked empty could race another import of the same course that had already checked the parent existed, making its mkdir fail with FileNotFoundError. Remember a finished chunked upload in the cache before deleting its staging file. The last chunk is sometimes sent twice (nginx 499); the repeat used to find the file at full size, but now that the staging copy is removed straight away it was rejected with a 409 "Some chunks missed" even though the import had already started. --------- Co-authored-by: Peter Pinch <pdpinch@mit.edu>
Regenerated via `make upgrade-package package=openedx-core`. Unblocks QA on the Competency-Based Education (CBE) work this release carries. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Updates openedx-core version to 1.11.0 --------- Co-authored-by: javoconsultant <jmeliev_consultant@flex.unicon.net>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
…sView` (#39196) * fix: enforce courses.manage_pages_and_resources on ProctoredExamSettingsView GET and POST on this endpoint only required studio read access, so a user who could view a course in Studio could also read or overwrite its proctored exam settings by calling the API directly, bypassing the UI's staff-only enforcement. Add the same authz check used elsewhere for pages-and-resources management, with a write-level legacy fallback when authz is disabled. Closes openedx-authz#443 * fix: use AuthZ view/manage permissions for proctored exam settings Make the AuthZ check the only gate for the proctored exam settings endpoint instead of stacking it on top of the legacy Studio read check: - GET requires courses.view_pages_and_resources and POST requires courses.manage_pages_and_resources. Without AuthZ, both fall back to legacy Studio read access, as before. - POST checks access before validating the payload. - Users with is_staff, or with the manage permission when AuthZ is enabled, can edit the fields reserved to edX staff. Replace the mocked AuthZ tests with tests that assign real roles. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> --------- Co-authored-by: Kevyn Suarez <kevyn.suarez@edunext.co> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Security release of the forum app for GHSA-c6fp-f54c-6cv4: the forum HTTP API is no longer routed unless FORUM_ENABLE_HTTP_API is set, and requires a staff caller when it is. GHSA-c6fp-f54c-6cv4 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fix: upgrade openedx-forum to 0.5.0
…al-sh/setup-uv-10.2.0 build(deps): bump astral-sh/setup-uv from 10.1.0 to 10.2.0
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.
This is a PR for tracking upstream master, and auto-deploying/updating the https://sandbox.opencraft.com/ Open edX instance, using the PR sandbox automation.
This PR should be kept open, because this sandbox is actually a long-lived instance for demo purposes.
The settings headings below are custom settings that are applied to the instance by the automation.
Internal tickets:
WARNING: use single quotes, not double quotes when quoting settings below. There is an issue with the workflow relating to quote escaping being overly zealous.
Settings
Tutor requirements