How to prefetch_related fields for a related field in Django
Asked Answered
K

1

7

I have seem some examples of how to prefetch_related fields in a forward and backward relationship in Django, but I have doubts about how can this be applied if we want to prefetch all the fields of a related model.

For instance if I want to fetch all content from the following models, using HealthCheck as the starting point. Which would be the most optimized query to achieve that?

class HealthCheck(models.Model):
    id = models.Integer()
    person = models.ForeignKey('Person')


class Person(models.Model):
    profile = models.ForeignKey('Profile')
    vaccines = models.ManyToManyField('vaccines', through='PersonVaccines')


class Profile(models.Model):
    name = models.CharField(max_length=16)


class PersonVaccines(models.Model):
    person = models.ForeignKey(Person)
    vaccine = models.ForeignKey('Vaccine')


class Vaccine(models.Model):
    name = models.CharField(max_length=16)

I have tried something like this but doesn't seems to work:

from django.db.models import Prefetch

HealthCheck.objects.filter(id=1).prefetch_related(
    Prefetch(
        'person__vaccines',
        queryset=PersonVaccines.objects.select_related('person', 'person__profile', 'vaccine')
    )
)

How can I prefetch all the related content?

Keelby answered 27/5, 2021 at 9:24 Comment(0)
T
6

It should be possible with:

HealthCheck.objects.filter(
    id=1,
).select_related(
    'person__profile',
).prefetch_related(
    'person__vaccines',
)

select_related will fetch all the related fields until profile.

prefetch_related should then prefetch all the related objects until vaccines

EDIT: Tested and confirmed that only 2 queries are generated when I created 3 vaccine objects for one health check person (n is my function to count queries executed):

In [2]: health_check = HealthCheck.objects.filter(id=3).select_related('person__profile').prefetch_related('person__vaccines').get()
     ...:

In [3]: n()
2

In [4]: for v in health_check.person.vaccines.all():
     ...:     print(v.name)
     ...:
v1
v2
v3

In [5]: n()
0

 
Talmud answered 28/5, 2021 at 5:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.