O Django REST Serializer que executa o banco de dados N + 1 exige vários relacionamentos aninhados, 3 níveis
Eu tenho uma situação em que meu modelo tem um relacionamento de chave estrangeira:
# models.py
class Child(models.Model):
parent = models.ForeignKey(Parent,)
class Parent(models.Model):
pass
e meu serializador:
class ParentSerializer(serializer.ModelSerializer):
child = serializers.SerializerMethodField('get_children_ordered')
def get_children_ordered(self, parent):
queryset = Child.objects.filter(parent=parent).select_related('parent')
serialized_data = ChildSerializer(queryset, many=True, read_only=True, context=self.context)
return serialized_data.data
class Meta:
model = Parent
Quando eu chamo Parent em meus modos de exibição para N número de Pais, o Django faz N número de chamadas de banco de dados dentro do serializador quando ele pega os filhos. Existe alguma maneira de obter TODOS os filhos de TODOS os Pais para minimizar o número de chamadas ao banco de dados?
Eu tentei isso, mas ele não parece resolver o meu problema:
class ParentList(generics.ListAPIView):
def get_queryset(self):
queryset = Parent.objects.prefetch_related('child')
return queryset
serializer_class = ParentSerializer
permission_classes = (permissions.IsAuthenticated,)
EDITAR
Atualizei o código abaixo para refletir o feedback de Alex .... que resolve o N + 1 para um relacionamento aninhado.
# serializer.py
class ParentSerializer(serializer.ModelSerializer):
child = serializers.SerializerMethodField('get_children_ordered')
def get_children_ordered(self, parent):
# The all() call should hit the cache
serialized_data = ChildSerializer(parent.child.all(), many=True, read_only=True, context=self.context)
return serialized_data.data
class Meta:
model = Parent
# views.py
class ParentList(generics.ListAPIView):
def get_queryset(self):
children = Prefetch('child', queryset=Child.objects.select_related('parent'))
queryset = Parent.objects.prefetch_related(children)
return queryset
serializer_class = ParentSerializer
permission_classes = (permissions.IsAuthenticated,)
Agora, digamos que eu tenho mais um modelo, que é um neto:
# models.py
class GrandChild(models.Model):
parent = models.ForeignKey(Child,)
class Child(models.Model):
parent = models.ForeignKey(Parent,)
class Parent(models.Model):
pass
Se eu colocar o seguinte no meuviews.py
para o paiqueryset
:
queryset = Parent.objects.prefetch_related(children, 'children__grandchildren')
Não parece que esses netos estão sendo levados para o ChildSerializer e, portanto, novamente estou executando outro problema de N + 1. Alguma idéia sobre este?
EDIT 2
Talvez isso forneça clareza ... Talvez o motivo pelo qual ainda estou executando chamadas de banco de dados N + 1 seja porque minhas classes de filhos e netos são polimórficas ...
# models.py
class GrandChild(PolymorphicModel):
child = models.ForeignKey(Child,)
class GrandSon(GrandChild):
pass
class GrandDaughter(GrandChild):
pass
class Child(PolymorphicModel):
parent = models.ForeignKey(Parent,)
class Son(Child):
pass
class Daughter(Child):
pass
class Parent(models.Model):
pass
e meus serializadores ficam mais ou menos assim:
# serializer.py
class ChildSerializer(serializer.ModelSerializer):
grandchild = serializers.SerializerMethodField('get_children_ordered')
def to_representation(self, value):
if isinstance(value, Son):
return SonSerializer(value, context=self.context).to_representation(value)
if isinstance(value, Daughter):
return DaughterSerializer(value, context=self.context).to_representation(value)
class Meta:
model = Child
class ParentSerializer(serializer.ModelSerializer):
child = serializers.SerializerMethodField('get_children_ordered')
def get_children_ordered(self, parent):
queryset = Child.objects.filter(parent=parent).select_related('parent')
serialized_data = ChildSerializer(queryset, many=True, read_only=True, context=self.context)
return serialized_data.data
class Meta:
model = Parent
Mais o mesmo para Grandaughter, neto, pouparei os detalhes em código, mas acho que você entendeu.
Quando executo minha visão para ParentList e monitorei consultas de banco de dados, obtive algo como milhares de consultas, apenas para alguns pais.
Se eu executar o mesmo código no shell do django, eu posso realizar a mesma consulta com no máximo 25 consultas. Eu suspeito que talvez tenha algo a ver com o fato de eu estar usando a biblioteca django-polimórfica. O motivo é que, há uma tabela de banco de dados Child e GrandChild, em acréscimos a cada tabela Filho / Filha, Neto / Neta, para um total de 6 tabelas. através desses objetos. Então meu intestino me diz que estou sentindo falta daquelas tabelas polimórficas.
Ou talvez haja uma solução mais elegante para o meu modelo de dados?