在 Django REST 框架中优化 SerializerMethodField 中的查询

Posted

技术标签:

【中文标题】在 Django REST 框架中优化 SerializerMethodField 中的查询【英文标题】:Optimizing queries in SerializerMethodField in Django REST framework 【发布时间】:2017-12-19 05:06:15 【问题描述】:

我正在访问我的 SerializerMethodField 中的相关字段的数据,并且对正在呈现的每个对象都有一个查询。我的模型看起来像(为了简洁起见):

class Listing(models.Model):

variant = models.ForeignKey(to='Variant', related_name='variant_listings')
seller = models.ForeignKey(to='user.Seller', related_name='seller_listings')
locality = models.ForeignKey(to='user.Locality', blank=True, null=True)
price = models.IntegerField(blank=True, null=True)

VariantSellerLocality 都是相关模型。

我的视图集:

class ListingViewSet(viewsets.ModelViewSet):
    """Viewset class for Listing model"""

    queryset = Listing.objects.all()
    serializer_class = ListingSerializer
    pagination_class = TbPagination
    filter_backends = (filters.DjangoFilterBackend,)
    filter_class = ListingFilter

    def get_queryset(self):

        listing_qs = Listing.objects.filter(status='active')
        listing_qs = ListingSerializer.setup_eager_loading(listing_qs)
        return listing_qs

还有我的序列化器:

class ListingSerializer(serializers.ModelSerializer):
"""Serializer class for Listing model"""

@staticmethod
def setup_eager_loading(queryset):
    queryset = queryset.prefetch_related('variant', 'seller', 'locality')
    return queryset

@staticmethod
def get_car_link(obj):
    variant_name_slug = obj.variant.name.replace(' ', '-').replace('+', '')
    return '/buy-' + obj.seller.city.name.lower() + '/' + variant_name_slug

car_link = serializers.SerializerMethodField(read_only=True)

@staticmethod
def get_car_info(obj):
    return 
        'id': obj.id,
        'variant_name': obj.variant.name,
        'localities': obj.locality.name,
    

car_info = serializers.SerializerMethodField(read_only=True)

@staticmethod
def get_image_urls(obj):
    caption_ids = [1, 2, 3, 5, 7, 8, 18]
    attachments_qs = Attachment.objects.filter(listing_id=obj.id, caption_id__in=caption_ids)
    image_urls = []
    for attachment in attachments_qs:
        url = str(obj.id) + '-' + str(attachment.file_number) + '-360.jpg'
        image_urls.append(url)

    return image_urls

image_urls = serializers.SerializerMethodField(read_only=True)

class Meta:
    model = Listing
    fields = ('car_link', 'car_info', 'sort_by', 'image_urls')

对于列表视图集返回的每个列表,在 SerializerMethodField 中访问的每个相关字段都有一个查询。

我发现了一些相关问题,例如this。但这没有帮助。此外,我尝试在我的视图集的 get_queryset 方法上执行prefetch_related,并在this 文章的帮助下实现了预加载。但没有任何帮助。

有什么办法可以避免这些查询?

编辑

上面编写的 get_car_info 函数包含更多字段(以及已经存在的字段),这些字段在嵌套 JSON 中以 car_info 的名称在前端呈现的最终序列化数据中单独需要。

【问题讨论】:

prefetch_related() 会有所帮助,但仅适用于您通过 obj 访问的数据。您不得创建其他查询集,因为它们会在评估时生成查询。 Soooo 你的附件也必须以某种方式预取。您知道使用prefetch_related 时可以附加任意查询集吗? 我们需要了解您如何使用prefetch_related。您很可能错误地使用它,或错误地访问相关数据。这绝对是前进的方向。 @Kye 我已经编辑了代码并在序列化程序和视图集中添加了 eager_loading。请看一看。 @spectras 请查看修改后的代码(在序列化程序和视图集的 get_queryset 方法中急切加载)。 【参考方案1】:

我用过这篇文章: http://ses4j.github.io/2015/11/23/optimizing-slow-django-rest-framework-performance/

我在我的序列化程序中创建了一个预置加载方法,如下所示:

class EagerGetProjectSerializer(serializers.ModelSerializer):
    lead_researcher = UserSerializer()
    participating_researcher = UserSerializer(many=True)
    client = ProjectClientSerializer()
    test_items = TestItemSerializer(many=True)


    @staticmethod
    def setup_eager_loading(queryset):
        queryset = queryset.select_related('lead_researcher', 'client')
        queryset = queryset.prefetch_related('participating_researcher', 
        'test_items')
        return queryset


    class Meta:
        model = Project
        fields = '__all__'

请注意,当引用您想要拉入序列化程序的对象时,您必须使用与外键相关的名称属性。

在我看来,在访问序列化程序之前调用:

class SingleProject(APIView):
    def get(self, request):
        ans = Project.objects.filter(id=project_id)
        qs = EagerGetProjectSerializer.setup_eager_loading(ans)
        serializer = EagerGetProjectSerializer(qs, many=True)

【讨论】:

以上是关于在 Django REST 框架中优化 SerializerMethodField 中的查询的主要内容,如果未能解决你的问题,请参考以下文章

在 Django REST 框架中优化 SerializerMethodField 中的查询

django框架学习六:优化views.py文件,使用rest_framework中的APIVew和Response返回

优化Django Rest Framework 的Token验证功能

如何在 django rest 框架中访问获取请求数据

在 django rest 框架中定义 searchFilter URL

如何在 Django REST 框架中使用事务?