3333import logging
3434
3535from django .http import JsonResponse
36- from drf_spectacular .utils import extend_schema
36+ from drf_spectacular .utils import OpenApiParameter , OpenApiRequest , OpenApiResponse , extend_schema
3737from edx_rest_framework_extensions .auth .jwt .authentication import JwtAuthentication
3838from edx_rest_framework_extensions .auth .session .authentication import SessionAuthenticationAllowInactiveUser
3939from opaque_keys import InvalidKeyError
5555
5656log = logging .getLogger (__name__ )
5757
58+
59+ # ---------------------------------------------------------------------------
60+ # ADR 0027 — shared OpenAPI parameter and response building blocks
61+ # ---------------------------------------------------------------------------
62+ _USAGE_KEY_PATH_PARAMETER = OpenApiParameter (
63+ name = "usage_key_string" ,
64+ description = (
65+ "Usage key identifying the xblock (e.g. "
66+ "``block-v1:edX+DemoX+Demo_Course+type@vertical+block@abcd``). Also "
67+ "accepts legacy ``i4x://`` locators."
68+ ),
69+ required = True ,
70+ type = str ,
71+ location = OpenApiParameter .PATH ,
72+ )
73+
74+ # ADR 0036 — declare the ``?view=minimal`` preset in the OpenAPI schema so
75+ # consumers (Swagger UI, generated SDK clients) can discover it. Only the
76+ # ``retrieve`` action honours this parameter today.
77+ _VIEW_QUERY_PARAMETER = OpenApiParameter (
78+ name = "view" ,
79+ description = (
80+ "ADR 0036 response preset. ``minimal`` drops heavy/contextual xblock "
81+ "fields (``data``, ``metadata``, ``fields``, ``student_view_data``, "
82+ "``edited_on``, ``published`` …) and keeps only the structural fields "
83+ "(``id``, ``display_name``, ``category``, ``children``, "
84+ "``has_children``, ``studio_url``). Omit the parameter to receive "
85+ "the full xblock response."
86+ ),
87+ required = False ,
88+ type = str ,
89+ location = OpenApiParameter .QUERY ,
90+ enum = ["minimal" ],
91+ )
92+
93+ # ADR 0036 — the underlying ``retrieve_xblock_response`` accepts a legacy
94+ # ``?fields=`` selector with **type-of-response** semantics (not the ADR 0036
95+ # CSV-subset semantics). Documented here as a deprecated parameter so callers
96+ # can see it in Swagger UI and know it's a legacy pass-through.
97+ _LEGACY_FIELDS_QUERY_PARAMETER = OpenApiParameter (
98+ name = "fields" ,
99+ description = (
100+ "**Legacy pass-through** (v0/v1 semantics). Selects a *type* of "
101+ "response rather than a subset of top-level keys:\n "
102+ " * ``fields=graderType`` — return the grader-type value directly\n "
103+ " * ``fields=ancestorInfo`` — return concise ancestor info\n "
104+ " * ``fields=customReadToken`` — include parent + children on the "
105+ "response\n "
106+ "Note: this is **not** the ADR 0036 ``?fields=`` CSV subset "
107+ "convention. New callers should use ``?view=minimal`` instead."
108+ ),
109+ required = False ,
110+ type = str ,
111+ location = OpenApiParameter .QUERY ,
112+ deprecated = True ,
113+ )
114+
115+ _COMMON_ERROR_RESPONSES = {
116+ 401 : OpenApiResponse (description = "The requester is not authenticated." ),
117+ 403 : OpenApiResponse (description = "The requester does not have permission for this xblock's course." ),
118+ 404 : OpenApiResponse (description = "The requested xblock does not exist." ),
119+ 406 : OpenApiResponse (description = "Requested representation is not available (e.g. non-JSON ``Accept``)." ),
120+ }
121+
122+
58123# ADR 0036 — top-level keys kept when ``?view=minimal`` is requested. Chosen so
59124# the response is structurally complete (callers can navigate the tree by id
60125# and fetch full nodes on demand) without any heavy/contextual fields
@@ -152,12 +217,49 @@ def initial(self, request, *args, **kwargs):
152217 self .course_key = None
153218 super ().initial (request , * args , ** kwargs )
154219
220+ @extend_schema (
221+ summary = "Create an xblock" ,
222+ description = (
223+ "Create a new xblock under a parent block. The ``parent_locator`` "
224+ "field on the request body identifies the parent and (implicitly) "
225+ "the course."
226+ ),
227+ request = OpenApiRequest (XblockSerializer ),
228+ responses = {
229+ 200 : OpenApiResponse (
230+ response = XblockSerializer ,
231+ description = "The xblock was created successfully." ,
232+ ),
233+ 400 : OpenApiResponse (description = "Request body failed validation." ),
234+ ** _COMMON_ERROR_RESPONSES ,
235+ },
236+ )
155237 @expect_json_in_class_view
156238 @validate_request_with_serializer
157239 def create (self , request ):
158240 """Create a new xblock under the given parent."""
159241 return create_xblock_response (request )
160242
243+ @extend_schema (
244+ summary = "Retrieve an xblock" ,
245+ description = (
246+ "Retrieve an xblock (and, by default, its nested tree) by usage "
247+ "key. Supports ADR 0036 ``?view=minimal`` to strip contextual "
248+ "fields, plus the legacy ``?fields=`` type-of-response selector."
249+ ),
250+ parameters = [
251+ _USAGE_KEY_PATH_PARAMETER ,
252+ _VIEW_QUERY_PARAMETER ,
253+ _LEGACY_FIELDS_QUERY_PARAMETER ,
254+ ],
255+ responses = {
256+ 200 : OpenApiResponse (
257+ response = XblockSerializer ,
258+ description = "The xblock representation." ,
259+ ),
260+ ** _COMMON_ERROR_RESPONSES ,
261+ },
262+ )
161263 @expect_json_in_class_view
162264 def retrieve (self , request , usage_key_string = None ):
163265 """
@@ -172,18 +274,58 @@ def retrieve(self, request, usage_key_string=None):
172274 response = _apply_minimal_view (response )
173275 return response
174276
277+ @extend_schema (
278+ summary = "Update an xblock" ,
279+ description = "Fully update an xblock identified by its usage key." ,
280+ parameters = [_USAGE_KEY_PATH_PARAMETER ],
281+ request = OpenApiRequest (XblockSerializer ),
282+ responses = {
283+ 200 : OpenApiResponse (
284+ response = XblockSerializer ,
285+ description = "The xblock was updated successfully." ,
286+ ),
287+ 400 : OpenApiResponse (description = "Request body failed validation." ),
288+ ** _COMMON_ERROR_RESPONSES ,
289+ },
290+ )
175291 @expect_json_in_class_view
176292 @validate_request_with_serializer
177293 def update (self , request , usage_key_string = None ):
178294 """Fully update an xblock."""
179295 return update_xblock_response (request , usage_key_string )
180296
297+ @extend_schema (
298+ summary = "Partially update an xblock" ,
299+ description = (
300+ "Partially update an xblock identified by its usage key. Only the "
301+ "fields present in the request body are updated."
302+ ),
303+ parameters = [_USAGE_KEY_PATH_PARAMETER ],
304+ request = OpenApiRequest (XblockSerializer ),
305+ responses = {
306+ 200 : OpenApiResponse (
307+ response = XblockSerializer ,
308+ description = "The xblock was updated successfully." ,
309+ ),
310+ 400 : OpenApiResponse (description = "Request body failed validation." ),
311+ ** _COMMON_ERROR_RESPONSES ,
312+ },
313+ )
181314 @expect_json_in_class_view
182315 @validate_request_with_serializer
183316 def partial_update (self , request , usage_key_string = None ):
184317 """Partially update an xblock."""
185318 return update_xblock_response (request , usage_key_string )
186319
320+ @extend_schema (
321+ summary = "Delete an xblock" ,
322+ description = "Delete an xblock identified by its usage key." ,
323+ parameters = [_USAGE_KEY_PATH_PARAMETER ],
324+ responses = {
325+ 200 : OpenApiResponse (description = "The xblock was deleted successfully." ),
326+ ** _COMMON_ERROR_RESPONSES ,
327+ },
328+ )
187329 @expect_json_in_class_view
188330 def destroy (self , request , usage_key_string = None ):
189331 """Delete an xblock."""
0 commit comments