Skip to content

Commit 42d7241

Browse files
Taimoor  AhmedTaimoor  Ahmed
authored andcommitted
docs: add ADR for modulestore crud api with custom serializers
1 parent 2e2ebb9 commit 42d7241

File tree

1 file changed

+127
-0
lines changed

1 file changed

+127
-0
lines changed
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
ADR-016: Provide Modulestore CRUD APIs via Custom DRF Layers
2+
============================================================
3+
4+
:Status: Proposed
5+
:Date: 2026-04-08
6+
:Deciders: API Working Group
7+
8+
Context
9+
=======
10+
11+
Open edX currently lacks comprehensive REST APIs to create, view, update, and delete modulestore
12+
entities (courses, blocks). Modulestore is not backed by standard Django models, so DRF cannot be
13+
applied with model serializers directly.
14+
15+
Decision
16+
========
17+
18+
Implement modulestore APIs using DRF **with custom serializers and service methods**:
19+
20+
1. Create DRF ViewSets for modulestore resources (course, block).
21+
2. Use explicit, non-model serializers for validation and representation.
22+
3. Enforce permissions and visibility rules appropriate for authoring roles.
23+
4. Provide OpenAPI schemas and examples for all operations.
24+
25+
Relevance in edx-platform
26+
=========================
27+
28+
* **Modulestore is not ORM-backed**: Courses and blocks live in modulestore
29+
(MongoDB/split); ``xmodule.modulestore`` exposes ``get_course()``, ``get_item()``,
30+
``update_item()``, etc. DRF model serializers do not apply directly.
31+
* **Existing read-only APIs**: ``openedx/core/djangoapps/olx_rest_api/views.py``
32+
uses ``@api_view(['GET'])`` and ``view_auth_classes()``, calls
33+
``modulestore().get_item()`` and ``serialize_modulestore_block_for_learning_core()``,
34+
and returns a custom JSON shape (no ModelSerializer). Contentstore course API
35+
(``cms/djangoapps/contentstore/api/views/utils.py``) uses ``BaseCourseView`` and
36+
``modulestore().get_course()`` with custom depth handling.
37+
* **Studio/course authoring**: Contentstore views (e.g. ``contentstore/views/block.py``,
38+
``course.py``) perform create/update/delete via Python APIs, not REST; this ADR
39+
proposes exposing CRUD via DRF with custom serializers and service-layer methods.
40+
41+
Code examples
42+
=============
43+
44+
**Custom serializer (no model):**
45+
46+
.. code-block:: python
47+
48+
from rest_framework import serializers
49+
50+
class ModulestoreBlockSerializer(serializers.Serializer):
51+
id = serializers.CharField(read_only=True)
52+
block_type = serializers.CharField()
53+
display_name = serializers.CharField(required=False)
54+
parent = serializers.CharField(required=False)
55+
56+
def create(self, validated_data):
57+
return modulestore_service.create_block(
58+
self.context["course_key"], validated_data
59+
)
60+
61+
def update(self, instance, validated_data):
62+
return modulestore_service.update_block(instance, validated_data)
63+
64+
**ViewSet with custom get_queryset / get_object:**
65+
66+
.. code-block:: python
67+
68+
from rest_framework import viewsets
69+
from openedx.core.lib.api.view_utils import view_auth_classes
70+
71+
@view_auth_classes()
72+
class ModulestoreBlockViewSet(viewsets.ModelViewSet):
73+
serializer_class = ModulestoreBlockSerializer
74+
permission_classes = [IsAuthenticated, HasStudioWriteAccess]
75+
76+
def get_object(self):
77+
usage_key = UsageKey.from_string(self.kwargs["usage_key"])
78+
if not has_studio_read_access(self.request.user, usage_key.course_key):
79+
raise PermissionDenied()
80+
return modulestore().get_item(usage_key)
81+
82+
def get_queryset(self):
83+
course_key = CourseKey.from_string(self.kwargs["course_id"])
84+
return modulestore().get_items(course_key, ...) # or service method
85+
86+
# Router: /api/modulestore/courses/<course_id>/blocks/ list, retrieve, create, update, destroy
87+
88+
**Service layer (recommended):**
89+
90+
.. code-block:: python
91+
92+
# services.py
93+
def get_block(usage_key, user):
94+
if not has_studio_read_access(user, usage_key.course_key):
95+
raise PermissionDenied()
96+
return modulestore().get_item(usage_key)
97+
98+
def update_block(usage_key, user, data):
99+
block = get_block(usage_key, user)
100+
if not has_studio_write_access(user, usage_key.course_key):
101+
raise PermissionDenied()
102+
# Apply data to block, then modulestore().update_item(...)
103+
return block
104+
105+
Consequences
106+
============
107+
108+
* Pros
109+
110+
* Enables cleaner authoring/integration flows for Studio/MFEs and external tools.
111+
* Standardizes modulestore interactions behind documented REST APIs.
112+
113+
* Cons / Costs
114+
115+
* Significant implementation effort; careful security/authorization required.
116+
* Backing store migrations must be abstracted behind stable service interfaces.
117+
118+
Implementation Notes
119+
====================
120+
121+
* Start with read-only endpoints (GET) for course structure/blocks, then add write operations.
122+
* Ensure stable contracts independent of modulestore backend.
123+
124+
References
125+
==========
126+
127+
* “Modulestore APIs” recommendation in the Open edX REST API standardization notes.

0 commit comments

Comments
 (0)