Using Different Read and Write Serializers in Django REST Framework
Tags: django, python, djangorestframework, drf
Published: 2019-08-20 16:24:38

---
canonical_url: https://www.revsys.com/tidbits/using-different-read-and-write-serializers-django-rest-framework/
---

**Versions**: 

- Python 3.7 
- Django 2.2 
- Django REST Framework 3.10 

On a recent project, we needed to use different serializers for GET vs. POST/PUT/PATCH requests to our Django REST Framework API. In our case, this was because the GET serializer contained a lot of nested data; for example, it contained expanded fields from other serializers to foreign-key relationships. The requests to update data via the API, though, didn't need these expanded fields.  

The first way we approached using different serializers for read and update actions was to override `get_serializer_class()` on each viewset to decide which serializer to return depending on the action in the request. We returned the "read" serializer for `list` and `retrieve` actions, and the "update" serializer for everything else. (The full list of API actions is [in the DRF codebase](https://github.com/encode/django-rest-framework/blob/335054a5d36b352a58286b303b608b6bf48152f8/rest_framework/schemas/coreapi.py#L39).) But we wound up repeating ourselves across several viewsets, so we wrote a mixin to take care of some of this work for us! 

A mixin is a Python class that contains custom attributes and methods ([more explanation](https://easyaspython.com/mixins-for-fun-and-profit-cb9962760556?gi=3875fd6a6ff8)). It's not very useful on its own, but when it's inherited into a class, that class has access to the mixin's special attributes and methods.  

This was our mixin: 

```python
class ReadWriteSerializerMixin(object):
    """
    Overrides get_serializer_class to choose the read serializer
    for GET requests and the write serializer for POST requests.

    Set read_serializer_class and write_serializer_class attributes on a
    viewset. 
    """

    read_serializer_class = None
    write_serializer_class = None

    def get_serializer_class(self):        
        if self.action in ["create", "update", "partial_update", "destroy"]:
            return self.get_write_serializer_class()
        return self.get_read_serializer_class()

    def get_read_serializer_class(self):
        assert self.read_serializer_class is not None, (
            "'%s' should either include a `read_serializer_class` attribute,"
            "or override the `get_read_serializer_class()` method."
            % self.__class__.__name__
        )
        return self.read_serializer_class
    
    def get_write_serializer_class(self):
        assert self.write_serializer_class is not None, (
            "'%s' should either include a `write_serializer_class` attribute,"
            "or override the `get_write_serializer_class()` method."
            % self.__class__.__name__
        )
        return self.write_serializer_class
```

This mixin defines two new attributes, `read_serializer_class` and `write_serializer_class`. Each attribute has a corresponding method to catch the error where the mixin is being used, but those attributes haven't been set. The `get_*_serializer_class()` methods will raise an `AssertionError` if your viewset hasn't set the appropriate attribute or overridden the necessary method. 

The `get_serializer_class` method makes the final decision on which serializer to use. For the "update" actions to the API, it returns `write_serializer_class`; otherwise it returns `read_serializer_class`. 

The mixin gets used in a viewset like this: 

```python
from rest_framework import viewsets

from .mixins import ReadWriteSerializerMixin
from .models import MyModel
from .serializers import ModelReadSerializer, ModelWriteSerializer


class MyModelViewSet(ReadWriteSerializerMixin, viewsets.ModelViewSet):
    queryset = MyModel.objects.all() 
    read_serializer_class = ModelReadSerializer 
    write_serializer_class = ModelWriteSerializer
```

Now the viewset `MyModelViewSet` has access to the attributes and methods from the mixin `ReadWriteSerializerMixin`. This means that when a call is made to the API that uses `MyModelViewSet`, the `get_serializer_class()` method from `ReadWriteSerializerMixin` will automatically be called and will decide, based on the kind of API request being made, which serializer to use. If we needed to make even more granular decisions about the serializer returned (maybe we want to use a more limited serializer for a `list` request and one with more data in a `retrieve` request), then our viewset can override `get_write_serializer_class()` to add that logic. 

Note: Custom DRF actions will contain actions that aren't part of the DRF list of accepted actions (because they are custom actions you're creating), so when you call `get_serializer_class` from inside your action method, it will return whatever your "default" serializer class is. In the example above, the "default" serializer is the `read_serializer_class` because it's what we return when we fall through the other conditional.  

Depending on your action, you will want to override `get_serializer_class` to change your default method or explicitly account for your custom action. 

Mixins are a DRY (Don't Repeat Yourself) way to add functionality that you wind up needing to use across several viewsets. We hope you get to experiment with using them soon!

_Thanks to Jeff Triplett for his help with this post._