#django-rest-framework #query-optimization #django-serializer
Вопрос:
Из приведенной ниже модели я пытаюсь вернуть количество различных типов курсов для конкретного университета в сериализаторе курсов с минимальным количеством запросов, насколько это возможно.
Модель
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__"
То, что я пробовал.
В сериализаторе курсов я предварительно выбираю тип курса как:
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:
Одна вещь, о которой вы должны знать, это то, что prefetch_related
она работает до тех пор, пока вы обращаетесь .all()
к набору запросов или к набору запросов, который соответствует Prefetch
набору запросов объектов. В вашем случае 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')
)