Django REST框架:为嵌套序列化程序设置预取
我的Django驱动的带有DRFAPI的应用程序运行良好,但随着数据库中填充了实际数据,我开始遇到性能问题。我使用Django调试工具栏进行了一些评测,发现我的许多端点在返回数据的过程中会发出数十到数百个查询 我预料到了这一点,因为我以前没有对数据库查询进行过任何优化。但是,现在我正在设置预取,当序列化程序嵌套在不同的序列化程序中时,我很难使用正确预取的序列化程序数据。我一直在用它来指导如何思考不同的预取方法 目前,当我点击Django REST框架:为嵌套序列化程序设置预取,django,django-rest-framework,Django,Django Rest Framework,我的Django驱动的带有DRFAPI的应用程序运行良好,但随着数据库中填充了实际数据,我开始遇到性能问题。我使用Django调试工具栏进行了一些评测,发现我的许多端点在返回数据的过程中会发出数十到数百个查询 我预料到了这一点,因为我以前没有对数据库查询进行过任何优化。但是,现在我正在设置预取,当序列化程序嵌套在不同的序列化程序中时,我很难使用正确预取的序列化程序数据。我一直在用它来指导如何思考不同的预取方法 目前,当我点击/api/readinggroups/端点时,我的ReadingGrou
/api/readinggroups/
端点时,我的ReadingGroup
序列化程序确实正确预取了数据。我的问题是/api/userbookstats/
端点,它返回所有userbookstats
对象。相关的序列化程序,UserBookStatsSerializer
,具有嵌套的ReadingGroupSerializer
模型、序列化器和视图集如下所示:
型号.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)
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
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
视图.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)
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
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
我已经优化了ReadingGroup
端点的预取(实际上我发布了关于消除该端点重复查询的内容),现在我正在研究UserBookStats
端点
我遇到的问题是,在UserBookStatsSerializer
中当前的设置\u eager\u加载
时,它似乎没有使用由ReadingGroupSerializer
中的eager加载方法设置的预取。我对预回迁
对象的语法仍然有点模糊,我是受了这个优秀答案的启发而尝试这种方法的
显然,UserBookStatsViewset
的get\u queryset
方法没有为ReadingGroup
对象调用setup\u eager\u loading
,但是我确信有一种方法可以完成相同的预取。prefetch\u related()
支持使用双下划线语法预取内部关系:
queryset = queryset.prefetch_related('reading_group', 'reading_group__users', 'reading_group__owner')
我不认为Django REST为自动获取所有必需字段提供了任何现成的解决方案。手动预取所有嵌套关系的替代方案,还有一个名为的包,它将自动遍历模型和序列化程序上的相关字段,以查找所有需要在
prefetch\u related
和select\u related
调用中提及的模型。您只需将AutoPrefetchViewSetMixin
添加到您的视图集中:
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
任何具有更复杂的
Prefetch
对象的额外预取都可以添加到视图集中的get\u queryset
方法中。您是在询问优雅的解决方案还是其他解决方案queryset=queryset.prefetch\u related('reading\u group'、'reading\u group\u users'、'reading\u group\u owner')
应该可以很好地工作。任何解决方案都会很好——哇,这太棒了。这很好,所以如果你把它作为答案提交,我会接受的。它取代了Prefetch
对象,这正是我希望用于实现某种解耦预取的优雅解决方案的方法,但这确实很有效。谢谢