如何在 django rest 框架中的嵌套序列化器相关对象上使用 prefetch_related?

Posted

技术标签:

【中文标题】如何在 django rest 框架中的嵌套序列化器相关对象上使用 prefetch_related?【英文标题】:How to use prefetch_related on nested serializer related objects in django rest framework? 【发布时间】:2021-06-06 15:15:48 【问题描述】:

从下面的模型中,我尝试在课程序列化程序上返回特定大学不同课程类型的计数,并尽可能减少查询次数。

型号

class University(models.Model):
    name = models.CharField(max_length=255)

    class Meta:
        ordering = ['name']
        verbose_name_plural = 'Universities'

    def __str__(self):
        return "%s" % self.name



class CourseType(models.Model):
    name = models.CharField("Course Name", max_length=255)

    def __str__(self):
        return self.name


class Course(models.Model):
    name = models.CharField("Course Name", max_length=255)
    course_type = models.ForeignKey(
        CourseType,
        null=True,
        blank=True,
        related_name='course_type',
        on_delete=models.SET_NULL)
    university = models.ForeignKey(
        University,
        null=True,
        blank=True,
        related_name='courses',
        on_delete=models.SET_NULL
    )

    def __str__(self):
        return self.name

序列化器

class UniversitySerializer(serializers.ModelSerializer):
    course_type_count = serializers.SerializerMethodField(
        'get_course_type_count')

    class Meta:
        model = University
        fields = "__all__"

    def get_course_type_count(self, obj):
        course_count = 

        # This one is causing extra query
        courses_type = obj.courses.all().values('course_type__name').annotate(
            total=Count('id')).order_by('course_type')
        for course_type in courses_type:
            course_count[f"course_type['course_type__name']"] = course_type['total']
        return course_count


class CourseSerializer(serializers.ModelSerializer):
    course_type = serializers.CharField(source='course_type.name')
    university = UniversitySerializer(read_only=True)

    @staticmethod
    def setup_eager_execution(qs):
        qs = qs.select_related('university')
        qs = qs.select_related('course_type')

        # This is what i've tried
        qs = qs.prefetch_related(
            Prefetch('university__courses__course_type',
                     queryset=qs.prefetch_related('course_type'))
        )


        return qs

    class Meta:
        model = Course
        fields = "__all__"

我试过了。

在课程序列化程序中,我将预取 CourseType 为:

qs = qs.prefetch_related(
        Prefetch('university__courses__course_type',
                   queryset=qs.prefetch_related('course_type'))
    )

在上面的 ORM 调用中,我预取课程类型并遍历对象。如下UniversitySerializer

courses_type = obj.courses.all().values('course_type__name').annotate(
   total=Count('id')).order_by('course_type')

调试工具栏中仍有重复查询。

【问题讨论】:

【参考方案1】:

您应该注意的一件事是,只要您访问.all() 或与Prefetch 对象查询集匹配的查询集,prefetch_related 就可以工作。在您的情况下,get_course_type_count 进行注释以计算计数,因此无法使用预取对象。我的建议是在 python 中进行计数计算。

from collections import defaultdict


class UniversitySerializer(serializers.ModelSerializer):
    ...

    def get_course_type_count(self, obj):
        course_count = defaultdict(int)

        for course in obj.courses.all():
            course_count[course.course_type.name] += 1

        return course_count

只使用这个预取就足够了

qs = qs.prefetch_related(
    Prefetch('university__courses__course_type')
)

【讨论】:

以上是关于如何在 django rest 框架中的嵌套序列化器相关对象上使用 prefetch_related?的主要内容,如果未能解决你的问题,请参考以下文章

嵌套序列化程序 django rest 框架中的上下文

如何获取主键相关字段嵌套序列化器django rest框架的所有值

django rest框架嵌套模型序列化器

django rest 框架 POST 请求因嵌套序列化而失败

python django-rest-framework 3.3.3 更新嵌套序列化程序

Django Rest Framework,如何更新序列化程序中的嵌套值