Django REST框架:设置嵌套序列化程序的预取

dkh*_*upt 15 django django-rest-framework

我的Django驱动的应用程序与DRF API工作正常,但我已经开始遇到性能问题,因为数据库填充了实际数据.我已经使用Django Debug Toolbar进行了一些分析,发现我的许多端点在返回数据的过程中会发出数十到数百个查询.

我期待这一点,因为我之前没有对数据库查询进行任何优化.现在我正在设置预取,但是当串行器嵌套在不同的串行器中时,我在使用正确预取的串行器数据时遇到了麻烦.我一直在使用这篇精彩的帖子作为如何考虑预取的不同方法的指南.

目前,ReadingGroup当我点击/api/readinggroups/端点时,我的序列化程序会正确预取.我的问题是/api/userbookstats/端点,它返回所有UserBookStats对象.相关的序列化程序UserBookStatsSerializer有一个嵌套的ReadingGroupSerializer.

模型,序列化程序和视图集如下:

models.py

class ReadingGroup(models.model):
    owner = models.ForeignKeyField(settings.AUTH_USER_MODEL)
    users = models.ManyToManyField(settings.AUTH_USER_MODEL)
    book_type = models.ForeignKeyField(BookType)
    ....
    <other group related fields>

   def __str__(self):
     return '%s group: %s' % (self.name, self.book_type)

class UserBookStats(models.Model):
    reading_group = models.ForeignKey(ReadingGroup)
    user = models.ForeignKey(settings.AUTH_USER_MODEL)
    alias = models.CharField()

    total_books_read = models.IntegerField(default=0)
    num_books_owned = models.IntegerField(default=0)
    fastest_read_time = models.IntegerField(default=0)
    average_read_time = models.IntegerField(default=0)
Run Code Online (Sandbox Code Playgroud)

serializers.py

class ReadingGroupSerializer(serializers.ModelSerializer):
    users = UserSerializer(many = True,read_only=True)
    owner = UserSerializer(read_only=True)

    class Meta:
      model = ReadingGroup
      fields = ('url', 'id','owner', 'users')

    @staticmethod
    def setup_eager_loading(queryset):
      #select_related for 'to-one' relationships
      queryset = queryset.select_related('owner')

      #prefetch_related for 'to-many' relationships
      queryset = queryset.prefetch_related('users')

      return queryset

class UserBookStatsSerializer(serializers.HyperlinkedModelSerializer):
    reading_group = ReadingGroupSerializer()
    user = UserSerializer()
    awards = AwardSerializer(source='award_set', many=True)

    class Meta:
      model = UserBookStats
      fields = ('url', 'id', 'alias', 'total_books_read', 'num_books_owned', 
              'average_read_time', 'fastest_read_time', 'awards')

    @staticmethod
    def setup_eager_loading(queryset):
      #select_related for 'to-one' relationships
      queryset = queryset.select_related('user')

      #prefetch_related for 'to-many' relationships
      queryset = queryset.prefetch_related('awards_set')

      #setup prefetching for nested serializers
      groups = Prefetch('reading_group', queryset ReadingGroup.objects.prefetch_related('userbookstats_set'))        
      queryset = queryset.prefetch_related(groups)

      return queryset
Run Code Online (Sandbox Code Playgroud)

views.py

class ReadingGroupViewset(views.ModelViewset):

  def get_queryset(self):
    qs = ReadingGroup.objects.all()
    qs = self.get_serializer_class().setup_eager_loading(qs)
    return qs

class UserBookStatsViewset(views.ModelViewset):

  def get_queryset(self):
    qs = UserBookStats.objects.all()
    qs = self.get_serializer_class().setup_eager_loading(qs)
    return qs
Run Code Online (Sandbox Code Playgroud)

我已经优化了预取ReadingGroup终点(其实我张贴了关于消除重复查询该端点在这里),现在我工作的UserBookStats终点.

我遇到的问题是,在我当前的setup_eager_loading情况下UserBookStatsSerializer,它似乎没有使用预先设置的热切加载方法ReadingGroupSerializer.我对这个Prefetch对象的语法仍然有点模糊- 我从这个优秀的答案中受到启发,尝试这种方法.

显然get_queryset的方法UserBookStatsViewset不会调用setup_eager_loadingReadingGroup对象,但我敢肯定有一种方法来完成相同的预取.

ser*_*erg 12

prefetch_related() 支持使用双下划线语法预取内部关系:

queryset = queryset.prefetch_related('reading_group', 'reading_group__users', 'reading_group__owner') 
Run Code Online (Sandbox Code Playgroud)

我不认为Django REST提供任何优雅的解决方案,可以自动获取所有必要的字段.


dav*_*ish 8

除了手动预取所有嵌套关系之外,还有一个名为 的包,它会自动遍历模型和序列化器上的相关字段,以查找需要在和调用django-auto-prefetching中提及的所有模型。您需要做的就是添加到您的 ViewSets 中:prefetch_relatedselect_relatedAutoPrefetchViewSetMixin

from django_auto_prefetching import AutoPrefetchViewSetMixin

class ReadingGroupViewset(AutoPrefetchViewSetMixin, views.ModelViewset):

  def get_queryset(self):
    qs = ReadingGroup.objects.all()
    return qs

class UserBookStatsViewset(AutoPrefetchViewSetMixin, views.ModelViewset):

  def get_queryset(self):
    qs = UserBookStats.objects.all()
    return qs
Run Code Online (Sandbox Code Playgroud)

任何具有更复杂Prefetch对象的额外预取都可以添加到get_querysetViewSet 的方法中。