Repository navigation
feat: serve course group configurations from authoring v2 - #39308
taimoor-ahmed-1 wants to merge 13 commits into
Conversation
The v0 advanced settings view checks course existence with the @verify_course_exists decorator and access with an inline check_course_advanced_settings_access call in each handler. The conforming views need these as permission classes. IsExistingCourse answers 404 for a course key that names no course run: an unknown course, a key that names only a version, or a library key. CanViewAdvancedSettings and CanManageAdvancedSettings make the same check_course_advanced_settings_access(read|write) call the v0 view makes, so the policy-permission and legacy Studio-access branches are unchanged. Existing classes in the module are untouched.
GET/PATCH /api/contentstore/v0/advanced_settings/{course_id} answers
errors with developer_message bodies and some failures with a 500,
accepts Bearer tokens, returns every settings field by default, and
reports a refused value with the field parser's own text, which can be
Python exception text.
GET/PATCH /api/authoring/v1/courses/{course_key}/advanced_settings/ is
its successor. Errors use the standard envelope; a refused value is
reported under its setting name with one fixed message and the original
text is logged. The default read is the set of settings course authors
edit (v0 with fetch_all=0); view=full returns every field and fields
narrows either view. Authentication keeps the session class that admits
inactive users, as the legacy endpoint did; Bearer is dropped. Access is
IsAuthenticated, IsExistingCourse and a per-action class making v0's
read or write check, and an action without a declared permission is
never served. Parity tests compare v1 with v0 through the test client;
the declared differences are the error envelope and the fixed message.
GET/PATCH /api/contentstore/v0/advanced_settings/{course_id} now has a
conforming successor at /api/authoring/v1/courses/{course_key}/advanced_settings/.
Both v0 operations are marked deprecated in the CMS schema through an
exact SUPERSEDED_PATHS entry, and the view's docstrings name the
successor, how each v0 read maps to a v1 read, and the deprecation
ticket. The view itself is unchanged. The schema hook test that used
this path as its example of an unrelated contentstore path now uses the
heartbeat, which is not deprecated.
Importing cms.djangoapps.contentstore.rest_api.v1.authoring_urls at module level builds its routes before cms/urls.py registers the course_key path converter, so collecting the module failed with an ImproperlyConfigured error. The URL-name test now imports it when it runs, as the youtube transcript tests do. Also wraps a line pylint reported as too long.
The first CI run of the module showed three expectations that did not match the platform: - The legacy role checks consult the policy engine, which admits superusers, so a superuser who is not global staff is served by v0 and v1 alike. The test now expects that. - The proctoring provider field keeps its default unless proctored exams are enabled, so the proctoring tests enable them. - The JWT middleware appends NotJwtRestrictedApplication to the permissions of views that accept JWTs, so the wiring test compares only the classes the view declares.
The side-effect recorder, the body comparison helpers, the schema helpers and the client helpers in the advanced settings tests are not specific to advanced settings. They move unchanged, under public names, into v1/views/tests/helpers.py so the other authoring v1 endpoint tests reuse one copy instead of each carrying its own.
edx-drf-extensions' DefaultPagination returns seven envelope keys but publishes a schema with four, so generated clients drop num_pages, current_page and start. DocumentedDefaultPagination keeps the library's pagination and publishes all seven keys, each with an example. It lives in an unversioned module for every authoring list to reuse, and is to be deleted once the library documents the envelope itself.
The v0 tab views check courses.manage_pages_and_resources inline with a legacy write-access fallback before reordering custom pages or changing a tab. CanManagePagesAndResources makes the same call, beside the existing CanViewPagesAndResources. Existing classes are unchanged.
The v0 tabs endpoints return the custom pages as a bare array, reorder
them through a POST verb that answers a missing page with 500 and stores
a repeated page twice, change a tab's visibility through a POST verb that
echoes its input in errors, and accept Bearer tokens.
GET /api/authoring/v1/custom_pages/?course_key=... lists a course's
custom pages a page at a time in course order; a custom page is
addressed by its own usage key, so the list is top level with the course
in the query. PUT /api/authoring/v1/courses/{course_key}/custom_page_order/
replaces their order, with pages left out kept after the listed ones.
PATCH /api/authoring/v1/courses/{course_key}/tabs/{tab_id}/ shows or
hides a built-in tab and returns it. Errors use the standard envelope
with fixed messages. Access makes v0's view and manage checks. Parity
tests compare the stored tabs and each refusal with v0.
The three v0 tab operations now have conforming successors under /api/authoring/v1/. They are marked deprecated in the CMS schema through exact SUPERSEDED_PATHS entries, and the views' docstrings name the successors and the deprecation ticket. The views themselves are unchanged.
CanViewGroupConfigurations makes the same check the v1 group configurations endpoint applies through its decorator (view group configurations, falling back to Studio read access), so the authoring v2 endpoint can declare it as a permission class.
Adds GET /api/authoring/v2/courses/{course_key}/group_configurations/,
replacing GET /api/contentstore/v1/group_configurations/{course_id}.
The two legacy lists become one paginated list in the legacy order. Whether
the caller may manage configurations moves onto each item; where each
configuration and group is used is returned only with view=full. The
course-level flags of the legacy response are not part of this resource.
The endpoint uses the standard error envelope, drops Bearer
authentication, answers 404 for an unknown course before checking
permissions, and is tagged for the SDK. The legacy path is marked
deprecated in the published schema.
…of authoring v2 Docstring-only: names the successor address and the deprecation ticket.
|
Thanks for the pull request, @taimoor-ahmed-1! This repository is currently maintained by Once you've gone through the following steps feel free to tag them in a comment and let them know that your changes are ready for engineering review. 🔘 Get product approvalIf you haven't already, check this list to see if your contribution needs to go through the product review process.
🔘 Provide contextTo help your reviewers and other members of the community understand the purpose and larger context of your changes, feel free to add as much of the following information to the PR description as you can:
🔘 Get a green buildIf one or more checks are failing, continue working on your changes until this is no longer the case and your build turns green. 🔘 Update the status of your PRYour PR is currently marked as a draft. After completing the steps above, update its status by clicking "Ready for Review", or removing "WIP" from the title, as appropriate. Where can I find more information?If you'd like to get more details on all aspects of the review process for open source pull requests (OSPRs), check out the following resources: When can I expect my changes to be merged?Our goal is to get community contributions seen and reviewed as efficiently as possible. However, the amount of time that it takes to review and merge a PR can vary significantly based on factors such as:
💡 As a result it may take up to several weeks or months to complete a review and merge your PR. |
Summary
Standardizes the group configurations resource of the Course Management area (#39059, part of #38137). This is PR G of the eight resource-group PRs for the area; #39303 (advanced settings) is PR A, #39305 (custom pages and tabs) is PR C, and #39307 (course settings and rerun defaults) is PR F.
One conforming operation replaces the one v1 group configurations endpoint:
deprecated: true)authoring_v2:)GET /api/contentstore/v1/group_configurations/{course_id}GET /api/authoring/v2/courses/{course_key}/group_configurations/course_group_configuration_listIt applies ADRs 0025–0038 and OEP-69.
One list of configurations. v1 returns one object: two lists,
all_group_configurationsandexperiment_group_configurations, next to four facts about the course and the caller (can_manage,should_show_enrollment_track,should_show_experiment_groups,mfe_proctored_exam_settings_url). v2 returns one paginated list:all_group_configurations(the enrollment track configuration when the course has two tracks or more, the content type gating configuration when Studio may set it, then content groups and the other configurations), followed by the experiment configurations when the course has content experiments turned on. They come back in the shared seven-key page envelope, 10 per page by default and at most 100. v1 was unbounded.view=minimal(default) leavesusageout, on configurations and on groups alike;view=fulladds it, identical to v1's. The minimal view saves payload size only: the shared Studio helper computes usage either way (Known gaps).can_manageis on every item. It answers for the whole course: whether the caller may change the course's group configurations. It is asked once per request, with the same permission call v1 makes, and copied onto every item. It does not overrideread_only: a configuration the platform maintains, such as the enrollment track configuration, stays read only even whencan_manageis true. The schema says so.The course-level flags are not part of this resource.
should_show_enrollment_trackis answered by the list itself: theenrollment_trackconfiguration is listed exactly when it was true.should_show_experiment_groupsandmfe_proctored_exam_settings_urldescribe the course, not its group configurations; they are served by the course settings resource of #39307 (content_experiments_enabledandmfe_proctored_exam_settings_urlonGET /api/authoring/v2/courses/{course_key}/settings/). The list cannot stand in forshould_show_experiment_groups: a course with experiments turned on and none defined lists the same configurations as one with experiments off.Why a new version and not a fix in place. Each of these changes the contract for v1's callers (the Group Configurations page in frontend-app-authoring calls it), so they ride v2 (ADR 0037):
usageis opt-in withview=full;can_managemoves onto every item, and the three course-level flags leave the response;Merge order and the course flags
GET /api/authoring/v2/courses/{course_key}/settings/, which carriescontent_experiments_enabled(v1'sshould_show_experiment_groups, the sameare_content_experiments_enabledcheck) andmfe_proctored_exam_settings_url. Its field descriptions point at this resource.group_configurations/as the successor and says the two flags "are not part of that resource"; the v2 description says the same.settings/is gated on the permission to view the course, this resource on the permission to view group configurations. A caller holding the second without the first could list configurations but not readcontent_experiments_enabled. In the pinned openedx-authz 1.34.0 policy, every role that may view group configurations (course auditor, editor, staff, admin) may also view the course, so no shipped role has that combination. A custom policy could still create one.Changes
Commits:
feat: add the group configurations view permission class@authz_permission_requireddecorator unchangedCanViewGroupConfigurationsinviews/permissions.py: the sameuser_has_course_permission(user, courses.view_group_configurations, course_key, READ)call v1's decorator makesfeat: serve course group configurations from authoring v2contentstore.utils.get_group_configurations_context, which is not editeddocs: mark the v1 group configurations endpoint deprecated in favour of authoring v2deprecated: truein the schema comes from the exactSUPERSEDED_PATHSentry, with its test, added in the feat commit.)ADRs applied
GroupConfiguration.v2,GroupConfigurationGroup.v2,GroupConfigurationUsage.v2,GroupConfigurationValidation.v2), a query serializer forview,help_texton every field, explicit.v2component names; read-only, so no request body serializerIsAuthenticated,IsExistingCourse, thenCanViewGroupConfigurations(the check v1's decorator makes); the viewset refuses any action it was not written for; deny matrix with policy permissions off and on@extend_schemawith summary, description, parameters, aview=fullexample and every 4xx asErrorResponse; taggedopenedx-platform-sdkViewSetover a service module, mounted withpath(...)like every route inv2/authoring_urls.py, because a router's regex routes cannot use the shared path convertersinternal, and a malformed course key or an address no route matches gets the CMS HTML 404 (library and routing gaps, pinned by tests); the publishedErrorResponsecomponent's text comes from edx-drf-extensionsPolicyCacheControlrow on the first permission check against a database that lacks it, as on v1page,page_size10 by default, at most 100) through the sharedDocumentedDefaultPaginationfrom feat: serve course custom pages and tabs from authoring v1 #39305; v1 was unbounded(JwtAuthentication, SessionAuthenticationAllowInactiveUser)declared with its reason, as ADR 0034 requires for an endpoint that usedSessionAuthenticationAllowInactiveUser; Bearer droppedusage(per configuration and per group) only withview=full; the defaultview=minimalleaves it out of the payload. The minimal view saves payload size only, because the shared Studio helper computes usage either way; a usage-free read is a future-version itemdeprecated: truein the schema; the DEPR entry for [DEPR]: Contentstore and enrollment API versions superseded by FC-0118 #39128 is to be posted with this PR, and no removal release is named yetcourses/{course_key}/group_configurations/(a collection of configurations with ids, where a future{id}/member would take Studio's writes); shared converter, trailing slash, version-free URL name; the v1 name keptget_queryset(); configurations are user partitions stored with the course in the modulestore; the layering is shown in the matrixCompliance matrix
GroupConfigurationSerializer(GroupConfiguration.v2:id,name,scheme,description,parameters,groups, optional nullableusage,version,active, optionalread_only,can_manage) gives v1's item pluscan_manage.GroupConfigurationGroupSerializer(id,name, optional nullableusage,version),GroupConfigurationUsageSerializer(label,url, optional nullablevalidation, present only on experiment usage, as on v1),GroupConfigurationValidationSerializer(type,text).GroupConfigurationListQuerySerializervalidatesview. Explicit.v2ref_names, because v1'sGroupConfigurationGroupandGroupConfigurationUsagecomponents already exist.can_manage's help text says it is course-wide and does not overrideread_only;id's says the unsaved configuration's id changes per requestGroupConfigurationsSchemaTest(test_usage_validation_and_read_only_are_optional_and_usage_is_nullable,test_every_member_is_described);GroupConfigurationsListTest.test_the_body_matches_the_published_schema(both views); hygiene PASSCanViewGroupConfigurationsreads the parsedcourse_keyand refuses a view without one. The viewset serves onlylist, OPTIONS and unrouted methods; any other action raisesImproperlyConfigured, answered as a 500 envelope instead of being served uncheckedGroupConfigurationsAccessLegacyPermissionsTest,GroupConfigurationsAccessAuthzTest(each access case asserts v1's answer too);GroupConfigurationsPermissionWiringTest(test_the_listing_checks_the_course_then_the_permission_to_view,test_any_other_action_is_refused,test_a_routed_action_the_viewset_does_not_check_is_never_served,test_the_view_permission_asks_for_its_policy_and_studio_read_access,test_the_view_permission_refuses_a_view_without_a_course)v2_courses_group_configurations_list.course_keyis a path parameter with the converter's pattern;viewis a query enum (minimal,full; defaultminimal); the paginator addspageandpage_size. Responses 200, 400, 401, 403, 404, every 4xx anErrorResponse. TheContentGroupsexample (view=full) is published as a page that validates against the published envelope. Taggedopenedx-platform-sdk; the deprecated v1 operation loses the tag throughuntag_deprecated_operations. The description says the course flags are not part of the resource and the placeholder's id changesGroupConfigurationsSchemaTest(test_the_listing_is_published_for_the_sdk,test_the_parameters,test_the_example_is_a_valid_page,test_the_description_leaves_out_the_course_flags_and_says_the_placeholder_id_changes,test_the_v1_operation_is_deprecated_and_left_out_of_the_sdk)CourseGroupConfigurationsViewSet(StandardizedErrorMixinfirst, thenIterablePaginationMixin,viewsets.ViewSet) overv2/group_configurations_service.py. A plain ViewSet, because configurations are user partitions stored with the course in the modulestore, not rows this view owns. Deviation: mounted withpath(), not a routerGroupConfigurationsRoutingTest.test_the_address;GroupConfigurationsCost{LegacyPermissions,Authz}TestStandardizedErrorMixinfirst. 400validationfor an unknownview(errors.view); 401authn; 403authz; 404not-found(unknown, version-only or library course key; a course the modulestore cannot load; a page out of range, zero or not a number). Gaps pinned by tests that flip when fixed: 405 and 406 are typedinternal(edx-drf-extensions 10.9.0 does not catalogue them); a malformed or slash-form key gets the CMS HTML 404 at routing. The publishedErrorResponsecomponent's description is the library's own text, which names ADR 0029; changing it is a library changeGroupConfigurationsListTest(test_an_unknown_view_is_refused,test_a_page_out_of_range_is_404);GroupConfigurationsResponseDifferencesTest.test_a_course_the_modulestore_returns_nothing_for;GroupConfigurationsRoutingTest(test_other_methods_are_not_allowed,test_unsupported_accept_is_refused_with_406,test_an_unparsable_or_slash_form_key_is_an_html_404)PolicyCacheControlrow, which inserts it (one INSERT, itspre_save/post_save). That write belongs to the permission library, happens on v1 too, and is allow-listed by nameGroupConfigurationsSideEffects{LegacyPermissions,Authz}Test(test_listings_have_no_side_effectsoverbare,rich,team_groups;test_the_v1_listing_makes_the_same_first_lookup_write);GroupConfigurationsListTest.test_a_course_without_content_groups_lists_an_unsaved_content_group_configurationIterablePaginationMixinwith the sharedDocumentedDefaultPagination(#39305), which publishes all seven envelope keys. The service builds the full ordered list before paging, so pages are stable (apart from the unsaved configuration's id)GroupConfigurationsListTest(test_the_envelope_has_the_seven_keys,test_page_size_splits_the_pages_in_order,test_the_default_page_size_is_ten,test_a_page_out_of_range_is_404);GroupConfigurationsSchemaTest.test_the_listing_publishes_the_seven_envelope_keysGroupConfigurationsListTest(test_the_configurations_are_listed_in_studio_order,test_other_query_parameters_are_ignored)(JwtAuthentication, SessionAuthenticationAllowInactiveUser), commented at the declaration: an inactive account's session stays authenticated, so the course permission decides; staff status admits it, and its roles count only on courses where policy-based permissions are on. Bearer is dropped. Same as #39303 and #39305GroupConfigurationsAuthenticationTest(test_a_jwt_caller_may_list,test_a_session_caller_may_list,test_an_inactive_session_is_authenticated,test_a_bearer_token_is_refused);GroupConfigurationsAccessAuthzTest.test_an_inactive_session_with_a_role_is_acceptedusageon configurations and groups is the heavy part (a scan of the course's content per configuration).view=minimal, the default, leaves it out of the response;view=fullincludes it, identical to v1's; both variants are published (usageoptional and nullable). The minimal view does not save the work: the frozen helperget_group_configurations_contextcomputes usage for every configuration on either view, and the view drops it at serialization. A usage-free read needs a new helper, a future-version itemGroupConfigurationsListTest(test_the_default_view_leaves_out_usage,test_the_minimal_view_is_the_default,test_the_full_view_reports_where_each_configuration_and_group_is_used);GroupConfigurationsResponseDifferencesTest.test_usage_is_returned_only_with_view_full;GroupConfigurationsSchemaTest.test_the_parametersv2/. v1 frozen apart from docstring-only markers: the syntax tree with docstrings stripped equals the base.deprecated: truethrough one exactSUPERSEDED_PATHSentry; no runtime header (frozen route). DEPR: an entry for #39128, to be posted with this PR; no removal release is namedCmsMarkSupersededPathsTest.test_the_group_configurations_operation_is_marked_and_its_successor_is_not;GroupConfigurationsSchemaTest(test_the_v1_operation_is_deprecated_and_left_out_of_the_sdk,test_only_superseded_or_migrated_addresses_are_deprecated)group_configurationsunder the course; sharedcourse_keyconverter; trailing slash; version-free URL nameGroupConfigurationsRoutingTest(test_the_address,test_the_v1_address_is_unchanged,test_the_url_names_are_unique_in_the_namespace,test_the_address_without_its_trailing_slash_redirects_to_it,test_v1_still_takes_a_slash_separated_key_that_v2_does_not); the urls gate itself was skippedview,pageandpage_size, none of which can widen what is listedGroupConfigurationsListTest(test_the_enrollment_track_configuration_is_listed_with_two_tracks_or_more,test_content_type_gating_is_listed_only_when_studio_may_set_it,test_a_team_set_is_listed_only_with_content_groups_for_teams_on_and_a_team_in_it,test_experiment_configurations_are_listed_only_when_experiments_are_on)Backward compatibility
v1 is unchanged.
v1/views/group_configurations.pygains docstring text only; its syntax tree with docstrings stripped equals the base. Its route, view and URL name are untouched, and v1's own tests are not edited.Parity. v2 is compared with v1 through the Django test client, with policy permissions off (course staff) and on (course auditor), on six courses that between them cover every way the listing changes (only the unsaved configuration; experiments on with none defined; experiment configurations with experiments off; one enrollment track; two tracks, Studio-settable gating, content groups and experiments; a team-set with content groups for teams on):
view=full, the items are v1's two arrays in order, identical apart fromcan_manage;usage;can_manageis v1'scan_manage;The only declared body differences, verbatim from the test module:
The comparison fails both on an undeclared difference and on a declared difference that never occurs (
_ParityMixin.test_errors_differ_only_by_the_envelope_and_every_declared_difference_occurs);test_the_comparison_sees_a_usage_that_turns_null_or_is_droppedchecks that the diff notices ausagethat changes.Status and request differences from v1. Each is listed in the test module and observed against v1's own answer;
test_every_listed_difference_has_its_testfails on a row without a test:As a table, with the request-shape change that carries no status difference (tests in
GroupConfigurationsResponseDifferencesTestunless named):GET v1/group_configurations/{course_id}, one object: two arrays and four flagsGET v2/courses/{course_key}/group_configurations/, 7-key envelope, 10 per page by default; experiment configurations lasttest_the_two_lists_become_one_pagecan_managetest_can_manage_is_on_every_configurationusageview=fulltest_usage_is_returned_only_with_view_fullshould_show_enrollment_trackenrollment_trackitem is listed exactly when it was truetest_should_show_enrollment_trackshould_show_experiment_groupscontent_experiments_enabledon feat: serve course rerun defaults and settings from authoring v2 #39307'ssettings/)test_should_show_experiment_groupsmfe_proctored_exam_settings_urlsettings/)test_mfe_proctored_exam_settings_url?view=default(any value butminimal/full)validationtest_view_defaultpagepast the last,0or not a numbertest_an_invalid_pagetest_a_course_the_modulestore_returns_nothing_fortest_optionstest_a_bearer_tokencourse_does_not_exist)test_a_slash_separated_course_keyGroupConfigurationsRoutingTest.test_the_address_without_its_trailing_slash_redirects_to_itSchema. One new operation, and
deprecated: trueon the v1 operation.docs/cms-openapi.yamlis not edited; thegenerate_openapi_schemasworkflow regenerates it.Known gaps
bare,richandteam_groupscourses). From one content group configuration to eight, the SQL count stays the same on both views, and at each size neither view makes more Mongo reads than v1 (GroupConfigurationsCost{LegacyPermissions,Authz}Test).GroupConfiguration.update_partition_usage_info→get_partitions_usage_info). So Mongo reads grow with the number of configurations, on v1 and v2 alike, on either view. v2 does not add to them; the growth test pins SQL only and holds Mongo to v1's count.get_group_configurations_context(incontentstore/utils.py, shared and protected here) always computes usage; v2 drops it at serialization. The area plan said the default would skip the scans; that is corrected here. A usage-free helper is a future-version item.contentstore/utils.py). With the flag off, the teams partition generator produces no team-set configuration at all (and such a configuration has no groups), so the flag half of that condition is never the one that excludes it; the team count alone decides. v2 inherits the condition unchanged; the tests cover flag on with teams, flag off with teams, and flag on with no team (test_a_team_set_is_listed_only_with_content_groups_for_teams_on_and_a_team_in_it).In-place edits
cms/djangoapps/contentstore/rest_api/v1/views/group_configurations.pyget)cms/djangoapps/contentstore/rest_api/v2/authoring_urls.pyGroupConfigurationsRoutingTestcms/djangoapps/contentstore/views/permissions.py(shared, unversioned)CanViewGroupConfigurations); theopenedx_authz.constants.permissionsimport gainsCOURSES_VIEW_GROUP_CONFIGURATIONSand is reflowed onto several lines; existing classes unchangedGroupConfigurationsPermissionWiringTestand the access testscms/lib/spectacular.py,cms/lib/tests/test_spectacular.pySUPERSEDED_PATHSentry; the exact-set test updated and a test for the group configurations path addedCmsMarkSupersededPathsTestNew files inside the pre-existing
v2/:group_configurations_service.py,serializers/group_configurations.py,views/group_configurations.py,views/tests/test_group_configurations.py. The production modules import nothing fromrest_api/v0/orrest_api/v1/.Test-only imports from v1. The test module imports these shared helpers from
rest_api/v1/views/tests/helpers.py(added by #39303):FIRST_LOOKUP_SIGNALS,FIRST_LOOKUP_WRITES,UNKNOWN_COURSE_KEY,VERSION_ONLY_KEY,api_client,assert_matches_published_schema,assert_refused,changed_paths,measure,published_schema,record_effects,session_client,undeclared_paths. They are the area's shared test helpers (clients, the side-effect recorder, the parity diff, the cost measure, the schema check); importing them keeps one copy rather than a second one inv2/.ENVELOPE_KEYScomes fromrest_api/pagination.py(#39305).Error-format decision
Versioned. v1 keeps its
developer_message/error_codebodies unchanged. v2 uses the envelope. The Group Configurations page in frontend-app-authoring calls the v1 endpoint (src/group-configurations/data/api.ts); the list's pagination, the opt-inusageand the move of the course flags already force a new version, and the error format rides with it.Deprecations
The v1 operation is superseded by the v2 operation in the Summary. It is
deprecated: truein the CMS schema, and the docstrings name the successor. The view is otherwise unchanged: no runtime header. Tracked on the FC-0118 umbrella DEPR ticket, #39128; this resource's entry is posted there with the PR. Window per #39128: removed one named release after the release this ships in.Migration notes:
GET /api/authoring/v2/courses/{course_key}/group_configurations/. It is paginated: readresults, and follownextor passpage/page_size(at most 100) to get every configuration. The items are v1'sall_group_configurationsfollowed by v1'sexperiment_group_configurations, in the same order and shape.view=fullto getusageon configurations and groups, as v1 returns it.can_manage: read it from any item; it is the same on all of them and does not override an item'sread_only.should_show_enrollment_track: true exactly when an item hasschemeenrollment_track.should_show_experiment_groupsandmfe_proctored_exam_settings_url: readcontent_experiments_enabledandmfe_proctored_exam_settings_urlfromGET /api/authoring/v2/courses/{course_key}/settings/(feat: serve course rerun defaults and settings from authoring v2 #39307). Do not migrate before feat: serve course rerun defaults and settings from authoring v2 #39307 is merged.viewaccepts onlyminimalandfull; an out-of-range or non-numericpageis a 404.Authorization
@view_auth_classes(is_authenticated=True)IsAuthenticatedtest_anonymous_caller_is_refused_with_401(both permission modes);test_anonymous_caller_of_an_unknown_course_is_refused_with_401@verify_course_exists()IsExistingCourse(also 404 for version-only and library keys)test_unknown_course_is_404_before_permission_is_checked,test_a_version_only_course_key_is_404,test_a_library_is_not_a_course)@authz_permission_required(courses.view_group_configurations, READ)CanViewGroupConfigurations(new), sameuser_has_course_permissioncallcan_manage=user_has_course_permission(courses.manage_group_configurations, WRITE), non-gatingtest_whether_the_caller_may_manage_is_asked_once_per_request;test_a_role_that_may_only_view_lists_without_can_manage;test_roles_that_may_manage_list_with_can_manageOrder: 401, then 404 for a missing course, then 403. Allowed, with policy permissions off: course staff and instructor, org staff and org instructor, global staff (all with
can_managetrue). With them on: the course auditor role (can_managefalse); the course editor, course staff and course admin roles (can_managetrue); global staff and superusers. Each case asserts v1's status andcan_managetoo.Superusers. Saving a superuser sets
is_staff: thepre_savereceivergrant_staff_access_to_superusers(common/djangoapps/student/signals/receivers.py) makes every saved superuser global staff, so such a superuser lists and manages like global staff. A superuser whose staff status was cleared without saving through the model is refused with policy permissions off (Studio read access comes from staff status or a course or org role) and admitted, withcan_managetrue, with them on (the permission policy admits superusers). v1 decides the same in both cases (test_a_superuser_saved_through_the_model_is_global_staff_and_may_list_and_manage,test_a_superuser_without_staff_status_is_refused,test_staff_and_superuser_may_list_and_manage_without_a_role,test_a_superuser_without_staff_status_may_list_and_manage_without_a_role).Corrections to the area plan, found in review and checked against the code. The tests follow the platform's behaviour, which v2 keeps:
GroupConfigurationsAuthenticationTest.test_an_inactive_session_is_authenticated,GroupConfigurationsAccessAuthzTest.test_an_inactive_session_with_a_role_is_accepted).pre_savereceiver, not from the policy engine, contrary to what feat: serve course advanced settings from authoring v1 #39303 and feat: serve course custom pages and tabs from authoring v1 #39305 say.Gate report
Run on the host against
8721326a74(the head of #39305, which this branch stacks on). No platform environment was available, so four gates were skipped. The test module has not been run locally, and CI is its first run.v1/views/group_configurations.pydocstring marker,v2/authoring_urls.py), 4 ADDED in the existing v2 directories, nothing protected touched (23 shared legacy modules protected, includingcontentstore/utils.py, whoseget_group_configurations_contextv2 reuses)== []comparisons), the same message #39303 carries with its CI pylint passingFollow-ups
openedx-platform-sdk; the next openedx-platform-sdk regeneration gains it.src/group-configurations/data/api.ts) can move to the v2 list (requestpage_size=100or follownext, andview=fullfor usage), readcan_managefrom the items, and read the experiments flag and the proctored exam settings link fromsettings/. Its writes still go to Studio'sgroup_configurations/{course}/…handlers, which have no REST successor in this area; agroup_configurations/{id}/member is where they would migrate.view=minimalsaves the per-configuration content scans as well as the payload (future version).MethodNotAllowedandNotAcceptable.ErrorResponsecomponent's description isErrorResponseSerializer's docstring, which names ADR 0029 and carries a usage example; it should describe the error body instead. Issue to be filed.DefaultPagination.get_paginated_response_schemawith all seven keys (from feat: serve course custom pages and tabs from authoring v1 #39305), after which the shared paginator can be deleted./api/that match no route, instead of the CMS HTML page. Issue to be filed.PolicyCacheControl: the first permission check on a fresh database inserts the cache row on a read; seeding it by migration would make reads write-free. Issue to be filed./api/authoring/v2/: v2 here and the feat: apply FC-0118 ADRs to course content APIs #39157 v2 views keepSessionAuthenticationAllowInactiveUser; feat: serve the course outline from authoring v2 #39229 and feat: serve course containers from authoring v2 #39274 use the default on the same mount. One policy for new versions of such endpoints should be agreed.views/permissions.py,v2/authoring_urls.py,cms/lib/spectacular.pyand its test. The conflicts are additive, resolved by rebasing in merge order. Inviews/permissions.py, H's class and this PR's are both appended at the end of the file; the reflowedopenedx_authzimport line is also the line feat: serve the course outline from authoring v2 #39229 (which feat: serve course rerun defaults and settings from authoring v2 #39307 stacks on) edits to addCOURSES_VIEW_COURSE.