Как получить несколько связанных моделей с как можно меньшим количеством запросов

#django #wagtail

#django #трясогузка

Вопрос:

У меня возникли некоторые трудности с привязкой / получением данных из 3 моделей, связанных друг с другом. Я думаю, что я либо неправильно их разработал, либо я не до конца понимаю, как работать с родительскими полями и полями «многие ко многим».

Существует 3 модели (полные модели показаны в конце вопроса):

  • ClubManager Где вводятся названия клубов
  • ClubTimetables Расписание, относящееся к ClubManager
  • DaysOfTheWeek Пн-Вс, используется ClubTimetables

Для чего я пытаюсь извлечь информацию из всех из них: название клуба, его расписание (расписания) и для каждого расписания дни недели, с которыми оно связано. Однако, похоже, я не могу сделать это таким образом, чтобы постоянно не попадать в базу данных.

Первое, что я попытался, это получить ClubManager и select_related это расписание, но не смог понять, как предварительно DaysOfTheWeek выбрать то, с чем оно было связано.

Подход, который я пытаюсь сейчас, заключается в том, чтобы вместо этого получить ClubTimetable и использовать select_related и prefetch_related на обеих моделях, с которыми он связан, например

 timetable = ClubTimetables.objects.select_related('attached_to').prefetch_related('weekday')

for t in timetable:
    print(t.attached_to.name)
 

Это приводит к такому запросу

 connection.queries
 
[{'sql': 'SELECT "club_clubtimetables"."id", "club_clubtimetables"."sort_order", "club_clubtimetables"."address_line_1", "club_clubtimetables"."address_line_2", "club_clubtimetables"."address_city", "club_clubtimetables"."address_county", "club_clubtimetables"."address_country", "club_clubtimetables"."map_coord_lat", "club_clubtimetables"."map_coord_lon", "club_clubtimetables"."attached_to_id", "club_clubtimetables"."start_time", "club_clubtimetables"."end_time", "club_clubmanager"."id", "club_clubmanager"."name" FROM "club_clubtimetables" INNER JOIN "club_clubmanager" ON ("club_clubtimetables"."attached_to_id" = "club_clubmanager"."id") ORDER BY "club_clubtimetables"."sort_order" ASC',
  'time': '0.020'},
 {'sql': 'SELECT ("club_clubtimetables_weekday"."clubtimetables_id") AS "_prefetch_related_val_clubtimetables_id", "core_daysoftheweek"."id", "core_daysoftheweek"."weekday" FROM "core_daysoftheweek" INNER JOIN "club_clubtimetables_weekday" ON ("core_daysoftheweek"."id" = "club_clubtimetables_weekday"."daysoftheweek_id") WHERE "club_clubtimetables_weekday"."clubtimetables_id" IN (1, 2)',
  'time': '0.002'}]
 

Что выглядит правильно (?)

Но попытка получить связанные дни недели каждый раз попадает в базу данных.

 for t in timetable:
    for training in t.weekday.values():
        print(training['weekday'])

# Tuesday
# Sunday

In [14]: connection.queries
Out[14]: 
[{'sql': 'SELECT "core_daysoftheweek"."id", "core_daysoftheweek"."weekday" FROM "core_daysoftheweek" INNER JOIN "club_clubtimetables_weekday" ON ("core_daysoftheweek"."id" = "club_clubtimetables_weekday"."daysoftheweek_id") WHERE "club_clubtimetables_weekday"."clubtimetables_id" = 1',
  'time': '0.001'},
 {'sql': 'SELECT "core_daysoftheweek"."id", "core_daysoftheweek"."weekday" FROM "core_daysoftheweek" INNER JOIN "club_clubtimetables_weekday" ON ("core_daysoftheweek"."id" = "club_clubtimetables_weekday"."daysoftheweek_id") WHERE "club_clubtimetables_weekday"."clubtimetables_id" = 2',
  'time': '0.000'}]

# ran again...

for t in timetable:
    for training in t.weekday.values():
        print(training['weekday'])

# Tuesday
# Sunday

In [16]: connection.queries
Out[16]: 
[{'sql': 'SELECT "core_daysoftheweek"."id", "core_daysoftheweek"."weekday" FROM "core_daysoftheweek" INNER JOIN "club_clubtimetables_weekday" ON ("core_daysoftheweek"."id" = "club_clubtimetables_weekday"."daysoftheweek_id") WHERE "club_clubtimetables_weekday"."clubtimetables_id" = 1',
  'time': '0.001'},
 {'sql': 'SELECT "core_daysoftheweek"."id", "core_daysoftheweek"."weekday" FROM "core_daysoftheweek" INNER JOIN "club_clubtimetables_weekday" ON ("core_daysoftheweek"."id" = "club_clubtimetables_weekday"."daysoftheweek_id") WHERE "club_clubtimetables_weekday"."clubtimetables_id" = 2',
  'time': '0.000'},
 {'sql': 'SELECT "core_daysoftheweek"."id", "core_daysoftheweek"."weekday" FROM "core_daysoftheweek" INNER JOIN "club_clubtimetables_weekday" ON ("core_daysoftheweek"."id" = "club_clubtimetables_weekday"."daysoftheweek_id") WHERE "club_clubtimetables_weekday"."clubtimetables_id" = 1',
  'time': '0.001'},
 {'sql': 'SELECT "core_daysoftheweek"."id", "core_daysoftheweek"."weekday" FROM "core_daysoftheweek" INNER JOIN "club_clubtimetables_weekday" ON ("core_daysoftheweek"."id" = "club_clubtimetables_weekday"."daysoftheweek_id") WHERE "club_clubtimetables_weekday"."clubtimetables_id" = 2',
  'time': '0.001'}]
 

Возможно, это потому, что я использую .values() ? Но я не знаю, как еще получить эти значения.

Мои модели в настоящее время выглядят примерно так

 # ClubManager

@register_snippet
class ClubManager(ClusterableModel):

    name = models.CharField('Club name',
                            max_length=255,
                            help_text='The name of the club.')

    panels = [
        FieldPanel('name'),
        InlinePanel('club_timetable', heading='Timetable Information')
    ]
 
 # ClubTimetables

class ClubTimetables(Orderable, AddressBase):

    attached_to = ParentalKey(
        'club.ClubManager', related_name='club_timetable')

    weekday = models.ManyToManyField(DaysOfTheWeek)

    start_time = models.TimeField()
    end_time = models.TimeField()

    panels = [ ... ]   AddressBase.panels
 
 # DaysOfTheWeek

class DaysOfTheWeek(models.Model):

    weekday = models.CharField(max_length=9)

    def __str__(self):
        return self.weekday
 

Что я могу сделать, чтобы получить все соответствующие данные из этих моделей за один раз, не повторяя вызовы БД?
Или, если я неправильно спроектировал эти модели, каким образом можно было бы создать их, чтобы использовать их так, как задумано?

Ответ №1:

Вы можете пройти несколько уровней в select_related prefetch_related предложении or, разделяя каждый шаг двойным подчеркиванием:

 managers = ClubManager.objects.prefetch_related('club_timetable__weekday')
 

Затем цикл по ним должен работать без дополнительных запросов, помимо начальных.

 for manager in managers:
    for timetable in manager.club_timetable.all():
        for weekday in timetable.weekday.all():
            print(weekday)
 

Комментарии:

1. Очень простое и элегантное решение. Я не знал, что prefetch_related ClubTimetable это приведет к тому, что это не будет отношением «многие ко многим ClubManager » или что использование подчеркивания приведет к получению как расписания, так и записей дня недели, я видел цепочку, упомянутую в документах Django, но подумал, что это должно было получить только последнюю часть, например, будние дни. Спасибо за помощь, это именно то, что мне было нужно, и я очень признателен!