python - primarykeyrelatedfield - django-rest-framework, herencia de modelos múltiples, ModelSerializers y serializadores anidados
nested relationships django rest framework (3)
No puedo encontrar esta información en los documentos o en los interwebs.
último django-rest-framework, django 1.6.5
¿Cómo se crea un ModelSerializer que puede manejar un serializador anidado donde el modelo anidado se implementa utilizando herencia múltiple?
p.ej
##### MODELS
class OtherModel(Models.Model):
stuff = models.CharField(max_length=255)
class MyBaseModel(models.Model):
whaddup = models.CharField(max_length=255)
other_model = models.ForeignKey(OtherModel)
class ModelA(MyBaseModel):
attr_a = models.CharField(max_length=255)
class ModelB(MyBaseModel):
attr_b = models.CharField(max_length=255)
#### SERIALIZERS
class MyBaseModelSerializer(serializers.ModelSerializer):
class Meta:
model=MyBaseModel
class OtherModelSerializer(serializer.ModelSerializer):
mybasemodel_set = MyBaseModelSerializer(many=True)
class Meta:
model = OtherModel
Esto, obviamente, no funciona, pero ilustra lo que estoy tratando de hacer aquí.
En OtherModelSerializer, me gustaría que mybasemodel_set serialice representaciones específicas de ModelA o ModelB dependiendo de lo que tengamos.
Si es importante, también estoy usando django.model_utils y inheritencemanager para poder recuperar un queryset donde cada instancia ya es una instancia de la subclase adecuada.
Gracias
Estoy intentando usar una solución que involucra diferentes subclases de serializadores para las diferentes subclases de modelos:
class MyBaseModelSerializer(serializers.ModelSerializer):
@staticmethod
def _get_alt_class(cls, args, kwargs):
if (cls != MyBaseModel):
# we''re instantiating a subclass already, use that class
return cls
# < logic to choose an alternative class to use >
# in my case, I''m inspecting kwargs["data"] to make a decision
# alt_cls = SomeSubClass
return alt_cls
def __new__(cls, *args, **kwargs):
alt_cls = MyBaseModel.get_alt_class(cls, args, kwargs)
return super(MyBaseModel, alt_cls).__new__(alt_cls, *args, **kwargs)
class Meta:
model=MyBaseModel
class ModelASerializer(MyBaseModelSerializer):
class Meta:
model=ModelA
class ModelBSerializer(MyBaseModelSerializer):
class Meta:
model=ModelB
Es decir, cuando intentas crear una instancia de un objeto de tipo MyBaseModelSerializer
, en realidad terminas con un objeto de una de las subclases, que se serializa (y, fundamentalmente, para mí, deserializa) correctamente.
Acabo de empezar a usar esto, por lo que es posible que haya problemas con los que aún no me he encontrado.
He resuelto este problema de una manera ligeramente diferente.
Utilizando:
- DRF 3.5.x
- django-model-utils 2.5.x
Mi models.py
ve así:
class Person(models.Model):
first_name = models.CharField(max_length=40, blank=False, null=False)
middle_name = models.CharField(max_length=80, blank=True, null=True)
last_name = models.CharField(max_length=80, blank=False, null=False)
family = models.ForeignKey(Family, blank=True, null=True)
class Clergy(Person):
category = models.IntegerField(choices=CATEGORY, blank=True, null=True)
external = models.NullBooleanField(default=False, null=True)
clergy_status = models.ForeignKey(ClergyStatus, related_name="%(class)s_status", blank=True, null=True)
class Religious(Person):
religious_order = models.ForeignKey(ReligiousOrder, blank=True, null=True)
major_superior = models.ForeignKey(Person, blank=True, null=True, related_name="%(class)s_superior")
class ReligiousOrder(models.Model):
name = models.CharField(max_length=255, blank=False, null=False)
initials = models.CharField(max_length=20, blank=False, null=False)
class ClergyStatus(models.Model):
display_name = models.CharField(max_length=255, blank=True, null=True)
description = models.CharField(max_length=255, blank=True, null=True)
Básicamente, el modelo base es el modelo de "Persona" y una persona puede ser Clero, Religiosa o ninguna y simplemente ser una "Persona". Mientras que los modelos que heredan Person
tienen relaciones especiales.
En mi views.py
utilizo un mixin para "inyectar" las subclases en el conjunto de preguntas de esta manera:
class PersonSubClassFieldsMixin(object):
def get_queryset(self):
return Person.objects.select_subclasses()
class RetrievePersonAPIView(PersonSubClassFieldsMixin, generics.RetrieveDestroyAPIView):
serializer_class = PersonListSerializer
...
Y luego, la parte real "unDRY" viene en serializers.py
donde declaro el PersonListSerializer "base", pero to_representation
método to_representation
para devolver los filamentos especiales basados en el tipo de instancia así:
class PersonListSerializer(serializers.ModelSerializer):
def to_representation(self, instance):
if isinstance(instance, Clergy):
return ClergySerializer(instance=instance).data
elif isinstance(instance, Religious):
return ReligiousSerializer(instance=instance).data
else:
return LaySerializer(instance=instance).data
class Meta:
model = Person
fields = ''__all__''
class ReligiousSerializer(serializers.ModelSerializer):
class Meta:
model = Religious
fields = ''__all__''
depth = 2
class LaySerializer(serializers.ModelSerializer):
class Meta:
model = Person
fields = ''__all__''
class ClergySerializer(serializers.ModelSerializer):
class Meta:
model = Clergy
fields = ''__all__''
depth = 2
El "cambio" ocurre en el método de representación del serializador principal ( PersonListSerializer
). Mira el tipo de instancia y luego "inyecta" el serializador necesario. Debido a que todos los Religious
son heredados de la Person
que recibe a una Person
que también es miembro de la Clergy
, devuelve todos los campos de Person
y todos los campos de Clergy
. Lo mismo vale para Religious
. Y si la Person
no es Clergy
o Religious
, los campos del modelo base solo se devuelven.
No estoy seguro de si este es el enfoque adecuado, pero parece muy flexible y se ajusta a mi caso de uso. Tenga en cuenta que guardo / actualizo / creo Person
través de diferentes vistas / serializadores, así que no tengo que preocuparme por eso con este tipo de configuración.
Pude hacer esto creando un campo relacionado personalizado
class MyBaseModelField(serializers.RelatedField):
def to_native(self, value):
if isinstance(value, ModelA):
a_s = ModelASerializer(instance=value)
return a_s.data
if isinstance(value, ModelB):
b_s = ModelBSerializer(instance=value)
return b_s.data
raise NotImplementedError
class OtherModelSerializer(serializer.ModelSerializer):
mybasemodel_set = MyBaseModelField(many=True)
class Meta:
model = OtherModel
fields = # make sure we manually include the reverse relation (mybasemodel_set, )
Me preocupa que la creación de un Serializador para cada objeto sea una relación inversa. El conjunto de consulta es caro, así que me pregunto si existe una mejor manera de hacerlo.
Otro enfoque que probé fue cambiar dinámicamente el campo del modelo en MyBaseModelSerializer dentro de __init__, pero me encontré con el problema que aquí se describe:
django rest framework anidado modelserializer