Quickstart
Installation
Install using pip:
pip install drf-polymorphic
Then add drf_polymorphic to installed apps in settings.py:
INSTALLED_APPS = [
...
"drf_polymorphic",
...
]
Usage
For example, you have data for pets where the structure depends on the pet species:
from dataclasses import dataclass
@dataclass
class Pet:
name: str
pet_type = ""
@dataclass
class Cat(Pet):
hunting_skill: str
pet_type = "cat"
@dataclass
class Dog(Pet):
bark: str
pet_type = "dog"
@dataclass
class Lizard(Pet):
loves_rocks: bool
pet_type = "lizard"
cat = Cat(name="Snowball", hunting_skill="lazy")
dog = Dog(name="Lady", bark="soft")
lizard = Lizard(name="John", loves_rocks=True)
pets = [cat, dog, lizard]
You can use drf-polymorphic to show all this data in one endpoint.
Define regular serializers for each pet_type:
# serializers.py
from rest_framework import serializers
class CatSerializer(serializers.Serializer):
hunting_skill = serializers.ChoiceField(
choices=[("lazy", "lazy"), ("active", "active")]
)
class DogSerializer(serializers.Serializer):
bark = serializers.ChoiceField(choices=[("soft", "soft"), ("loud", "loud")])
class LizardSerializer(serializers.Serializer):
loves_rocks = serializers.BooleanField()
Now a polymorphic serializer can be created, which maps the values of pet_type with the
serializers defined above:
# serializers.py
from drf_polymorphic.serializers import PolymorphicSerializer
class PetPolymorphicSerializer(PolymorphicSerializer):
name = serializers.CharField()
pet_type = serializers.ChoiceField(
choices=[("cat", "cat"), ("dog", "dog"), ("lizard", "lizard")]
)
discriminator_field = "pet_type"
serializer_mapping = {
"cat": CatSerializer,
"dog": DogSerializer,
"lizard": LizardSerializer,
}
def create(self, validated_data):
pet_type = validated_data.pop("pet_type")
pet_class = import_string(f"testapp.data.{pet_type.capitalize()}")
new_pet = pet_class(**validated_data)
pets.append(new_pet)
return new_pet
Tip
Use None in the mapping value if there are no additional fields for that
value of the discriminator.
Create APIView which uses this polymorphic serializer:
from rest_framework.response import Response
from rest_framework.views import APIView
from .serializers import PetPolymorphicSerializer
class PetView(APIView):
serializer_class = PetPolymorphicSerializer
def get(self, request, *args, **kwargs):
serializer = self.serializer_class(pets, many=True)
return Response(serializer.data)
def post(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
After the path is added to urls.py the endpoint is ready to use.
Let’s display all the pets with GET request:
GET /pets/ HTTP/1.1
HTTP/1.0 200 OK
Content-Type: application/json
[
{
"name": "Snowball",
"pet_type": "cat",
"hunting_skill": "lazy"
},
{
"name": "Lady",
"pet_type": "dog",
"bark": "soft"
},
{
"name": "John",
"pet_type": "lizard",
"loves_rocks": true
}
]
The same endpoint can be used to change the data. In this example the request body can include data of any predefined pet species:
POST /pets/ HTTP/1.1
{
"name": "Felix",
"pet_type": "cat",
"hunting_skill": "active"
}
HTTP/1.0 201 Created
{
"name": "Felix",
"pet_type": "cat",
"hunting_skill": "active"
}
Now the pets list will include one more pet, which is the instance of Cat class.
API schema generation
drf-polymorphic has support for drf-spectacular. You can install all the required tooling in one go with:
pip install drf-polymorphic[spectacular]