I was wondering if anyone had a Pythonic solution of combining Django REST framework with django-polymorphic.
Given:
class GalleryItem(PolymorphicModel): gallery_item_field = models.CharField() class Photo(GalleryItem): custom_photo_field = models.CharField() class Video(GalleryItem): custom_image_field = models.CharField()
If I want a list of all GalleryItems in django-rest-framework it would only give me the fields of GalleryItem (the parent model), hence: id, gallery_item_field, and polymorphic_ctype. That's not what I want. I want the custom_photo_field if it's a Photo instance and custom_image_field if it's a Video.
Django-polymorphic builds on top of the standard Django model inheritance. It makes using inherited models easier. When a query is made at the base model, the inherited model classes are returned. When we store models that inherit from a Project model…
Django is the web development framework in python whereas the Django Rest Framework is the library used in Django to build Rest APIs. Django Rest Framework is especially designed to make the CRUD operations easier to design in Django. Django Rest Framework makes it easy to use your Django Server as an REST API.
The HyperlinkedModelSerializer class is similar to the ModelSerializer class except that it uses hyperlinks to represent relationships, rather than primary keys. By default the serializer will include a url field instead of a primary key field.
REST framework requires the following: Python (3.6, 3.7, 3.8, 3.9, 3.10) Django (2.2, 3.0, 3.1, 3.2, 4.0, 4.1)
So far I only tested this for GET request, and this works:
class PhotoSerializer(serializers.ModelSerializer): class Meta: model = models.Photo class VideoSerializer(serializers.ModelSerializer): class Meta: model = models.Video class GalleryItemModuleSerializer(serializers.ModelSerializer): class Meta: model = models.GalleryItem def to_representation(self, obj): """ Because GalleryItem is Polymorphic """ if isinstance(obj, models.Photo): return PhotoSerializer(obj, context=self.context).to_representation(obj) elif isinstance(obj, models.Video): return VideoSerializer(obj, context=self.context).to_representation(obj) return super(GalleryItemModuleSerializer, self).to_representation(obj)
For POST and PUT requests you might want to do something similiar as overriding the to_representation definition with the to_internal_value def.
Here's a general and reusable solution. It's for a generic Serializer
but it wouldn't be difficult to modify it to use ModelSerializer
. It also doesn't handle serializing the parent class (in my case I use the parent class more as an interface).
from typing import Dict, Type from rest_framework import serializers class PolymorphicSerializer(serializers.Serializer): """ Serializer to handle multiple subclasses of another class - For serialized dict representations, a 'type' key with the class name as the value is expected: ex. {'type': 'Decimal', ... } - This type information is used in tandem with get_serializer_map(...) to manage serializers for multiple subclasses """ def get_serializer_map(self) -> Dict[str, Type[serializers.Serializer]]: """ Return a dict to map class names to their respective serializer classes To be implemented by all PolymorphicSerializer subclasses """ raise NotImplementedError def to_representation(self, obj): """ Translate object to internal data representation Override to allow polymorphism """ type_str = obj.__class__.__name__ try: serializer = self.get_serializer_map()[type_str] except KeyError: raise ValueError( 'Serializer for "{}" does not exist'.format(type_str), ) data = serializer(obj, context=self.context).to_representation(obj) data['type'] = type_str return data def to_internal_value(self, data): """ Validate data and initialize primitive types Override to allow polymorphism """ try: type_str = data['type'] except KeyError: raise serializers.ValidationError({ 'type': 'This field is required', }) try: serializer = self.get_serializer_map()[type_str] except KeyError: raise serializers.ValidationError({ 'type': 'Serializer for "{}" does not exist'.format(type_str), }) validated_data = serializer(context=self.context) \ .to_internal_value(data) validated_data['type'] = type_str return validated_data def create(self, validated_data): """ Translate validated data representation to object Override to allow polymorphism """ serializer = self.get_serializer_map()[validated_data['type']] return serializer(context=self.context).create(validated_data)
And to use it:
class ParentClassSerializer(PolymorphicSerializer): """ Serializer for ParentClass objects """ def get_serializer_map(self) -> Dict[str, Type[serializers.Serializer]]: """ Return serializer map """ return { ChildClass1.__name__: ChildClass1Serializer, ChildClass2.__name__: ChildClass2Serializer, }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With