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)