Как мне собрать все данные Django из базы данных перед вызовом моего сериализатора?
Я использую Python 3.9 и Django 3.2. У меня есть модель Django с парой отношений "многие-ко-многим"
class Coop(models.Model):
objects = CoopManager()
name = models.CharField(max_length=250, null=False)
types = models.ManyToManyField(CoopType, blank=False)
addresses = models.ManyToManyField(Address, through='CoopAddressTags')
enabled = models.BooleanField(default=True, null=False)
phone = models.ForeignKey(ContactMethod, on_delete=models.CASCADE, null=True, related_name='contact_phone')
email = models.ForeignKey(ContactMethod, on_delete=models.CASCADE, null=True, related_name='contact_email')
web_site = models.TextField()
description = models.TextField(null=True)
approved = models.BooleanField(default=False, null=True)
proposed_changes = models.JSONField("Proposed Changes", null=True)
reject_reason = models.TextField(null=True)
Я могу искать свою модель, используя класс менеджера, который строит запрос следующим образом...
def find(
self,
partial_name,
types_arr=None,
enabled=None,
city=None,
zip=None,
street=None,
state_abbrev=None
):
"""
Lookup coops by varying criteria.
"""
q = Q()
if partial_name:
q &= Q(name__icontains=partial_name)
if enabled != None:
q &= Q(enabled=enabled)
if types_arr != None:
filter = Q(
*[('types__name', type) for type in types_arr],
_connector=Q.OR
)
q &= filter
if street != None:
q &= Q(addresses__raw__icontains=street)
if city != None:
q &= Q(addresses__locality__name__iexact=city)
if zip != None:
q &= Q(addresses__locality__postal_code=zip)
if state_abbrev != None:
q &= Q(addresses__locality__state__code=state_abbrev)
q &= Q(addresses__locality__state__country__code="US")
queryset = Coop.objects.filter(q)
print(queryset.query)
return queryset
В моем представлении я вызываю и возвращаю данные с помощью
coops = Coop.objects.find(...)
serializer = CoopSearchSerializer(coops, many=True)
в котором сериализатор выглядит как
class CoopSearchSerializer(serializers.ModelSerializer):
...
def to_representation(self, instance):
rep = super().to_representation(instance)
rep['coopaddresstags_set'] = CoopAddressTagsSerializer(instance.coopaddresstags_set.all(), many=True).data
return rep
Я заметил, что когда я ищу набор результатов, содержащий 6 результатов, и каждый из них сериализуется с использованием вышеприведенной логики, я получаю пропорциональное количество запросов, выполняемых для каждого результата ...
SELECT "directory_coopaddresstags"."id", "directory_coopaddresstags"."coop_id", "directory_coopaddresstags"."address_id", "directory_coopaddresstags"."is_public" FROM "directory_coopaddresstags" WHERE "directory_coopaddresstags"."coop_id" = 271; args=(271,)
(0.000) SELECT "directory_coopaddresstags"."id", "directory_coopaddresstags"."coop_id", "directory_coopaddresstags"."address_id", "directory_coopaddresstags"."is_public" FROM "directory_coopaddresstags" WHERE "directory_coopaddresstags"."coop_id" = 271; args=(271,)
type of instance: <class 'directory.models.CoopAddressTags'>
(0.000) SELECT "address_address"."id", "address_address"."street_number", "address_address"."route", "address_address"."locality_id", "address_address"."raw", "address_address"."formatted", "address_address"."latitude", "address_address"."longitude" FROM "address_address" WHERE "address_address"."id" = 263 LIMIT 21; args=(263,)
(0.000) SELECT "address_locality"."id", "address_locality"."name", "address_locality"."postal_code", "address_locality"."state_id" FROM "address_locality" WHERE "address_locality"."id" = 16 LIMIT 21; args=(16,)
(0.000) SELECT "address_state"."id", "address_state"."name", "address_state"."code", "address_state"."country_id" FROM "address_state" WHERE "address_state"."id" = 19313 LIMIT 21; args=(19313,)
(0.000) SELECT "address_country"."id", "address_country"."name", "address_country"."code" FROM "address_country" WHERE "address_country"."id" = 484 LIMIT 21; args=(484,)
Таким образом, если есть 6 результатов, то вышеуказанный запрос выполняется 6 раз с разными идентификаторами. Есть ли способ выполнить только один набор запросов, чтобы независимо от того, есть ли 1, 10 или 100 результатов, было выполнено одинаковое количество запросов для возврата данных?
Я вижу, вы собираете данные из многих M2M и FK отношений.
Я согласен с @Amres, вы можете использовать prefetch_related
и select_related
в ваших запросах, но простое использование этих методов не поможет вам.
Первый:
При использовании prefetch_related
и select_related
можно добиться 3 запросов на запрос. Не ниже. 1 большой запрос и 2 для m2m отношений.
Подробнее здесь:
https://docs.djangoproject.com/en/4.1/ref/models/querysets/#select-related.
https://docs.djangoproject.com/en/4.1/ref/models/querysets/#prefetch-related
Секунда:
Вы не можете использовать просто:
.prefetch_related('addresses')
ничего не делает. В вашем случае необходимо использовать объект Prefetch
.
Подробнее здесь:
https://docs.djangoproject.com/en/4.1/ref/models/querysets/#django.db.models.Prefetch
Почему? Ответ - у вас есть дополнительные FK в адресной модели:
- адреса__raw,
- addresses__locality,
- addresses__locality__state,
- addresses__locality__state__country
Как он может быть объявлен:
addressPrefetcher = Prefetch('adresses', queryset=Address.objects.select_related('raw', 'locality', 'locality__state', 'locality__state__country'))
После этого
Вы можете использовать эту конструкцию:
coops = Coop.objects.find(Your_query).select_related('phone', 'email').prefetch_related(addressPrefetcher, 'types')
Если я действительно правильно понимаю ваши модели - вы получаете только 3 попадания в БД, независимо от того, сколько объектов в CoopSearchSerializer
. Вы можете получить только 1 попадание с помощью GroupConcat
, но для данного бизнес случая это накладно.
p.s. Возможно, сохранить phone
и email
как FK, не очень хорошая идея. Но это возможно.