Actualmente tengo estos modelos de Django que quiero serializar:
class Result(models.Model): ... routes = models.ManyToManyField(Route) ... class Route(models.Model): ... class Feature(models.Model): result = models.ForeignKey(Result) route = models.ForeignKey(Route) description = models.TextField()
Y los serializadores DRF se ven así:
class ResultSerializer(serializers.ModelSerializer): ... route = RouteSerializer(many=True, required=False) ... class Meta: model = Result fields = '__all__' class FeatureField(serializers.CharField): """ Accepts text in the writes and looks up the correct feature for the reads. """ def get_attribute(self, obj): # We pass the object instance onto `to_representation`, not just the field attribute. return obj def to_representation(self, obj): try: search_result = self.root.child.instance # FIXME: this is the problem. feature = Feature.objects.get(route=obj.id, search_result=search_result) feature = feature.description except Feature.DoesNotExist: feature = None return feature class RouteSerializer(serializers.ModelSerializer): description = FeatureField(required=False) class Meta: model = Route fields = '__all__'
El problema al que me refiero en el código es que esto funciona cuando estoy usando un ResultSerializer con solo una instancia, pero si quiero serializar varias instancias en una vista de lista, por ejemplo, y paso un conjunto de consultas al serializador, DRF aplica un ListSerializer encima y ahora self.root.instance es una lista de los registros, y no puedo acceder a los resultados individuales que llaman al RouteSerializer anidado, por lo que no puedo recuperar la característica correcta.
Salté al código DRF y finalmente entendí lo que estaba pasando:
Si serializa solo una instancia con serializer = ResultSerializer(result)
, serializer.instance
contiene solo esta única instancia de result
particular, y los serializadores y campos anidados pueden acceder a ella sin problemas usando self.root.instance
.
Ahora, si serializa varias instancias, como lo hace la acción de list
predeterminada, lo que realmente sucede es lo siguiente:
serializer = ResultSerializer(queryset, many=True)
many=True
en los argumentos activa el método many_init()
de BaseSerializer
, y esto crea un solo ResultSerializer
con el conjunto de consultas como instancia, por lo que serializer.instance
es el conjunto de consultas.ListSerializer
que extiende ResultSerializer
y su instancia nuevamente es el conjunto de consultas. Lo que me equivoqué es pensar que ListSerializer
crearía ResultSerializer
separados para cada elemento en el conjunto de consultas.
La forma en que finalmente resolví esto es anular el método ResultSerializer.to_representation()
:
class ResultSerializer(serializers.ModelSerializer): def to_representation(self, instance): # When we call Results with many=True, the serializer.instance is a list with several records, # we can't know which particular instance is spawning the nested serializers so we add it here. self._instance = instance return super(ResultSerializer, self).to_representation(instance)
y finalmente consumirlo en FeatureField así:
class FeatureField(serializers.CharField): """ Accepts text in the writes and looks up the correct feature for the reads. """ def get_attribute(self, obj): # We pass the object instance onto `to_representation`, not just the field attribute. return obj def to_representation(self, obj): # If the root is a ListSerializer, retrieve the right Result instance using the `_instance` attribute. try: if isinstance(self.root, serializers.ListSerializer): search_result = self.root.child._instance else: search_result = self.root.instance feature = Feature.objects.get(route=obj.id, search_result=search_result) feature = feature.pickup_instructions except Feature.DoesNotExist: feature = None return feature