1+ """
2+ API Views for Content Groups (Group Configurations) V2 API.
3+
4+ Content groups enable course creators to assign different course content to different
5+ learner cohorts. This pure JSON API provides access to content group configurations.
6+
7+ API paths:
8+
9+ /api/contentstore/v2/courses/{course_id}/group_configurations
10+
11+ GET: List all content groups for a course.
12+ 200: Successfully retrieved content groups. Returns ContentGroupsListResponse.
13+ 401: Authentication required.
14+ 403: User does not have permission to access this course.
15+ 404: Course not found.
16+
17+ /api/contentstore/v2/courses/{course_id}/group_configurations/{configuration_id}
18+
19+ GET: Retrieve a specific content group configuration.
20+ 200: Content group configuration details. Returns ContentGroupConfiguration.
21+ 401: Authentication required.
22+ 403: User does not have permission to access this course.
23+ 404: Content group configuration not found or course not found.
24+ """
25+
26+ import edx_api_doc_tools as apidocs
27+ import logging
28+
29+ from opaque_keys import InvalidKeyError
30+ from opaque_keys .edx .keys import CourseKey
31+ from rest_framework .exceptions import NotFound , ValidationError , PermissionDenied
32+ from rest_framework .response import Response
33+ from rest_framework .views import APIView
34+ from rest_framework import status
35+
36+ from openedx .core .lib .api .view_utils import view_auth_classes
37+ from xmodule .modulestore .django import modulestore
38+ from xmodule .modulestore .exceptions import ItemNotFoundError
39+
40+ from cms .djangoapps .contentstore .views .course import get_course_and_check_access
41+ from cms .djangoapps .contentstore .utils import get_group_configurations_context
42+ from cms .djangoapps .contentstore .course_group_config import COHORT_SCHEME
43+ from cms .djangoapps .contentstore .rest_api .v2 .serializers import (
44+ ContentGroupConfigurationSerializer ,
45+ ContentGroupsListResponseSerializer ,
46+ )
47+
48+
49+ log = logging .getLogger (__name__ )
50+
51+
52+ @view_auth_classes (is_authenticated = True )
53+ class GroupConfigurationsListView (APIView ):
54+ """
55+ API view for listing content group configurations.
56+
57+ **GET Example Response:**
58+ ```json
59+ {
60+ "all_group_configurations": [
61+ {
62+ "id": 50,
63+ "name": "Content Groups",
64+ "scheme": "cohort",
65+ "description": "The groups in this configuration can be mapped to cohorts...",
66+ "parameters": {},
67+ "groups": [
68+ {"id": 1, "name": "Content Group A", "version": 1, "usage": []},
69+ {"id": 2, "name": "Content Group B", "version": 1, "usage": []}
70+ ],
71+ "active": true,
72+ "version": 3,
73+ "read_only": false
74+ }
75+ ],
76+ "should_show_enrollment_track": false,
77+ "should_show_experiment_groups": true,
78+ "group_configuration_url": "/api/contentstore/v2/courses/...",
79+ "course_outline_url": "/api/contentstore/v1/courses/..."
80+ }
81+ ```
82+ """
83+
84+ @apidocs .schema (
85+ parameters = [
86+ apidocs .string_parameter (
87+ "course_key_string" ,
88+ apidocs .ParameterLocation .PATH ,
89+ description = "The course key (e.g., course-v1:org+course+run)" ,
90+ ),
91+ ],
92+ responses = {
93+ 200 : ContentGroupsListResponseSerializer ,
94+ 401 : "Authentication required" ,
95+ 403 : "User does not have permission to access this course" ,
96+ 404 : "Course not found" ,
97+ },
98+ )
99+ def get (self , request , course_key_string ):
100+ """
101+ List all content groups for a course.
102+
103+ Returns all content group configurations (scheme='cohort') along with
104+ context about whether to show enrollment tracks and experiment groups.
105+
106+ If no content group exists, an empty content group partition is automatically created.
107+ """
108+ try :
109+ course_key = CourseKey .from_string (course_key_string )
110+ except InvalidKeyError as exc :
111+ raise ValidationError (f"Invalid course key: { course_key_string } " ) from exc
112+
113+ store = modulestore ()
114+
115+ try :
116+ course = get_course_and_check_access (course_key , request .user )
117+ except ItemNotFoundError as exc :
118+ raise NotFound (f"Course not found: { course_key_string } " ) from exc
119+ except PermissionDenied :
120+ raise
121+
122+ # Use existing helper to get context
123+ context = get_group_configurations_context (course , store )
124+
125+ # Filter to only cohort-scheme partitions for v2 API
126+ cohort_configs = [
127+ config for config in context ['all_group_configurations' ]
128+ if config .get ('scheme' ) == COHORT_SCHEME
129+ ]
130+ context ['all_group_configurations' ] = cohort_configs
131+
132+ # Set context_course to None for JSON API (it's only needed for HTML rendering)
133+ context ['context_course' ] = None
134+
135+ # Serialize and return
136+ serializer = ContentGroupsListResponseSerializer (context )
137+ return Response (serializer .data , status = status .HTTP_200_OK )
138+
139+
140+ @view_auth_classes (is_authenticated = True )
141+ class GroupConfigurationDetailView (APIView ):
142+ """
143+ API view for retrieving a specific content group configuration.
144+ """
145+
146+ @apidocs .schema (
147+ parameters = [
148+ apidocs .string_parameter (
149+ "course_id" ,
150+ apidocs .ParameterLocation .PATH ,
151+ description = "The course key" ,
152+ ),
153+ apidocs .path_parameter (
154+ "configuration_id" ,
155+ int ,
156+ description = "The ID of the content group configuration" ,
157+ ),
158+ ],
159+ responses = {
160+ 200 : ContentGroupConfigurationSerializer ,
161+ 401 : "Authentication required" ,
162+ 403 : "User does not have permission to access this course" ,
163+ 404 : "Content group configuration not found" ,
164+ },
165+ )
166+ def get (self , request , course_key_string , configuration_id ):
167+ """
168+ Retrieve a specific content group configuration.
169+
170+ Returns all metadata including groups, partition scheme, and usage information.
171+ """
172+ try :
173+ course_key = CourseKey .from_string (course_key_string )
174+ except InvalidKeyError as exc :
175+ raise ValidationError (f"Invalid course key: { course_key_string } " ) from exc
176+
177+ store = modulestore ()
178+
179+ try :
180+ course = get_course_and_check_access (course_key , request .user )
181+ except ItemNotFoundError as exc :
182+ raise NotFound (f"Course not found: { course_key_string } " ) from exc
183+ except PermissionDenied :
184+ raise
185+
186+ # Find the configuration
187+ partition = None
188+ for p in course .user_partitions :
189+ if p .id == int (configuration_id ) and p .scheme .name == COHORT_SCHEME :
190+ partition = p
191+ break
192+
193+ if not partition :
194+ raise NotFound (f"Content group configuration { configuration_id } not found" )
195+
196+ # Serialize and return
197+ response_data = partition .to_json ()
198+ serializer = ContentGroupConfigurationSerializer (response_data )
199+ return Response (serializer .data , status = status .HTTP_200_OK )
0 commit comments