Django REST Serializer doing N+1 database calls for multiple nested relationship, 3 levels

前端 未结 2 1232
离开以前
离开以前 2021-02-10 06:55

I have a situation where my model has a Foreign Key relationship:

# models.py
class Child(models.Model):
    parent = models.ForeignKey(Parent,)

class Parent(mo         


        
相关标签:
2条回答
  • 2021-02-10 07:03

    This question is a bit old but I just came across a very similar problem and managed to reduce the db calls drastically. It seems to me that Django-mptt would make things much easier for you.

    One way would be to define a single model with a ForeignKey to. This way you can find out the hierarchy by it's level in the tree. For example:

    class Person(MPTTModel):
        parent = TreeForeignKey('self', null=True, blank=True, related_name='children', db_index=True)  
    

    You can find out if the object is a parent by checking if Person.level = 0. If it equals 1, it's a child, 2 grandchild, etc...

    Then, you could modify your code to the following:

    # serializers.py
    class ChildSerializer(serializers.ModelSerializer):
        children = serializers.SerializerMethodField()
    
        def get_children(self, parent):
            queryset = parent.get_children()
            serialized_data = ChildSerializer(queryset, many=True, read_only=True, context=self.context)
            return serialized_data.data
    
    # views.py
    class ParentList(generics.ListAPIView):
    
        def get_queryset(self):
            queryset = cache_tree_children(Person.objects.all())
    

    With this, you'll eliminate your N+1 problem. If you want to add a new ForeignKey to a genre Model, for example, you could simply modify the last line to:

    queryset = cache_tree_children(Person.objects.filter(channel__slug__iexact=channel_slug).select_related('genre'))
    
    0 讨论(0)
  • 2021-02-10 07:15

    As far as I remember, nested serializers have access to prefetched relations, just make sure you don't modify a queryset (i.e. use all()):

    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
    
    
    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,)             
    
    0 讨论(0)
提交回复
热议问题