Split Read/Write Serializers Safely
This guide sets up one viewset that serializes list and retrieve with a read serializer, and create, update, and partial_update with a write serializer. Use the split when reads need computed or nested data that writes do not accept, or when write validation needs other fields. The steps keep the default views working, including the redirect after a save.
Before You Start
The model needs a Canonical Registration with a viewset. Model Info builds its field and expand sections from the Canonical Serializer only, as Canonical Registration and Model Discovery describes. Per-action serializers do not change model info. So every view's default View Field Lists and default expand come from the serializer that you register, whichever action serves the request.
That page also describes what a model registered with info.registration.register_serializer lacks. With no viewset, model info lists no actions, and the client blocks every route for the model.
Register the Canonical Serializer
Register the serializer whose fields the forms should show. This is usually the write serializer, because the create and update forms submit the fields that model info lists. Pass the viewset to info.registration.register:
from django.apps import AppConfig
class MyAppConfig(AppConfig):
name = "myapp"
def ready(self):
from vueda.info.registration import register
from .serializers import MyWriteSerializer
from .viewsets import MyViewSet
register(MyWriteSerializer, MyViewSet)Map Actions to Serializers
Add PerActionSerializerMixin to the viewset. Set an <action>_serializer_class attribute for each action:
from vueda.core.viewsets import PerActionSerializerMixin
from vueda.core.viewsets import VuedaViewSet
class MyViewSet(PerActionSerializerMixin, VuedaViewSet):
serializer_class = MyWriteSerializer
list_serializer_class = MyReadSerializer
retrieve_serializer_class = MyReadSerializer
create_serializer_class = MyWriteSerializer
update_serializer_class = MyWriteSerializer
partial_update_serializer_class = MyWriteSerializerThe mixin returns the class that the current action's attribute names. An action with no attribute uses serializer_class.
- VuedaViewSet does not include the mixin, so add it yourself.
- List the mixin before
VuedaViewSet. When the mixin comes afterVuedaViewSet, DRF'sget_serializer_classanswers first, and the mixin never runs. - Set
partial_update_serializer_classas well asupdate_serializer_class. A PATCH request runs thepartial_updateaction. Without its own attribute, that action usesserializer_class.
Match the Fields Each View Requests
The default views send canonical field names in the f query parameter and canonical expand names in e. Each action's serializer must answer those names. Field and Expand Semantics describes how f, e, and om behave on reads and writes.
Reads. list and retrieve check f and e against the read serializer. A canonical field or expand that the read serializer lacks fails the request with 400. ViewUpdate loads its object with retrieve, so the update view fails the same way. Declare every canonical field and expand on the read serializer.
A list request accepts no expand until the viewset sets permit_list_expands. Use Expand and Sparse Field Controls gives the steps.
Writes. A create or update save sends the view's fetchFields and the pk in f. A save also sends in e each configured expand whose form value is set. The write action checks both against the write serializer, with the same rules as a read. A field or expand that the write serializer lacks, or an expand that the action's permit list leaves out, fails the save with 400. The write action answers before it validates the body, so it saves nothing. Declare every fetched field and every configured expand on the write serializer, or narrow the view's fetchFields.
The pk. ViewCreate redirects to the update view by default. That redirect, and a redirect to the read view, take the pk from the save response. Include the pk in the write serializer's Meta.fields.
To narrow what one view requests, set its fetchFields prop, such as fetchFields on ViewUpdate. A per-view config that you set with setConfig works too. The update view uses one fetchFields list for its retrieve and its save, so both serializers must declare those fields.
Check the Split
- Request the model's model info, and confirm that its fields and expands match the canonical serializer.
- Open the list, read, and update views. Each loads with no
400. - Create an object. The save redirects to the update view.
- Save the update view. The save succeeds with no
400. - Send a PUT and a PATCH for one object. Both run the write serializer's validation.