viewset route framework detail python django django-rest-framework

python - route - django rest framework response



django-rest-framework+Django-polimorphic ModelSerialization (3)

Me preguntaba si alguien tenía una solución Pythonic de combinar el framework Django REST con django-polimórfico.

Dado:

class GalleryItem(PolymorphicModel): gallery_item_field = models.CharField() class Photo(GalleryItem): custom_photo_field = models.CharField() class Video(GalleryItem): custom_image_field = models.CharField()

Si quiero una lista de todos los GalleryItems en django-rest-framework, solo me daría los campos de GalleryItem (el modelo principal), por lo tanto: id, gallery_item_field y polymorphic_ctype. Eso no es lo que quiero. Quiero el custom_photo_field si es una instancia de Photo y custom_image_field si es un Video.


Aquí hay una solución general y reutilizable. Es para un Serializer genérico, pero no sería difícil modificarlo para usar ModelSerializer . Tampoco maneja la serialización de la clase padre (en mi caso, uso la clase padre más como una interfaz).

from typing import Dict 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, 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)

Y para usarlo:

class ParentClassSerializer(PolymorphicSerializer): """ Serializer for ParentClass objects """ def get_serializer_map(self) -> Dict[str, serializers.Serializer]: """ Return serializer map """ return { ChildClass1.__name__: ChildClass1Serializer, ChildClass2.__name__: ChildClass2Serializer, }


Hasta ahora solo he probado esto para una solicitud GET, y esto funciona:

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)

Para las solicitudes POST y PUT, es posible que desee hacer algo similar al anular la definición de to_representation con la definición de to_internal_value.


Para completar, estoy agregando la implementación to_internal_value() , ya que necesitaba esto en mi proyecto reciente.

Cómo determinar el tipo

Es útil tener la posibilidad de distinguir entre diferentes "clases"; Así que he agregado la propiedad de tipo en el modelo polimórfico base para este propósito:

class GalleryItem(PolymorphicModel): gallery_item_field = models.CharField() @property def type(self): return self.__class__.__name__

Esto permite llamar al type como "campo" y "campo de solo lectura".

type contendrá el nombre de la clase python.

Añadiendo tipo al serializador

Puede agregar el type en "campos" y "campos de solo lectura" (debe especificar el campo de tipo en todos los serializadores, si desea usarlos en todos los modelos secundarios)

class PhotoSerializer(serializers.ModelSerializer): class Meta: model = models.Photo fields = ( ..., ''type'', ) read_only_fields = ( ..., ''type'', ) class VideoSerializer(serializers.ModelSerializer): class Meta: model = models.Video fields = ( ..., ''type'', ) read_only_fields = ( ..., ''type'', ) class GalleryItemModuleSerializer(serializers.ModelSerializer): class Meta: model = models.GalleryItem fields = ( ..., ''type'', ) read_only_fields = ( ..., ''type'', ) def to_representation(self, obj): pass # see the other comment def to_internal_value(self, data): """ Because GalleryItem is Polymorphic """ if data.get(''type'') == "Photo": self.Meta.model = models.Photo return PhotoSerializer(context=self.context).to_internal_value(data) elif data.get(''type'') == "Video": self.Meta.model = models.Video return VideoSerializer(context=self.context).to_internal_value(data) self.Meta.model = models.GalleryItem return super(GalleryItemModuleSerializer, self).to_internal_value(data)