Эффективное моделирование сложных отношений модели django
Я программирую сайт на django для орагнизации мероприятий, где мне нужно иметь возможность хранить информацию о том, кто из посетителей останется на обед. В настоящее время у меня это работает не самым оптимальным образом, и я хотел бы это улучшить. Я буду рад услышать любые идеи о том, как вы могли бы это сделать.
Что мне нужно
Итак, для каждого события (которое может иметь продолжительность переменного числа дней) мне нужно иметь возможность отображать то, что я называю таблицей блюд. В этой таблице будет храниться информация о питании каждого дня. Каждый день имеет три приема пищи (завтрак, обед и ужин). Каждый прием пищи должен содержать информацию о количестве людей, которые остаются на этот прием пищи, плюс блюдо, которое каждый из этих людей будет есть из меню. Меню также содержит переменное количество блюд, которое может быть изменено администратором данного мероприятия (меню одинаково для каждого приема пищи).
Итак, общая концептуальная схема будет выглядеть примерно так:
Event
|_ Meal Table
|_ Day 1
|_ Breakfast
\_ Dish 1 -> Nº of people
\_ Dish 2 -> Nº of people
\_ Dish n -> Nº of people
...
|_ Lunch
\_ Dish 1 -> Nº of people
\_ Dish 2 -> Nº of people
\_ Dish n -> Nº of people
...
|_ Dinner
\_ Dish 1 -> Nº of people
\_ Dish 2 -> Nº of people
\_ Dish n -> Nº of people
...
|_ Day 2
|_ Day n
...
Итак, для каждого блюда каждого дня каждого события мне нужно хранить, сколько человек выбрали каждое из доступных блюд, учитывая, что один человек не может выбрать более одного блюда в каждом приеме пищи.
Мое решение на данный момент
(очень неэффективный) способ, которым я делал это до сих пор, следующий:
- Я создал следующие модели:
Dish
:from django.db import models class Dish(models.Model): name = models.CharField(max_length=256, null=False, blank=False, unique=True) def __str__(self): return self.nombre def get_default(self): return Dish.objects.get(id=1) def get_default_id(self): return self.get_default().id
Day
:import auto_prefetch from django.db import models class Day(auto_prefetch.Model): event = auto_prefetch.ForeignKey(Event, on_delete=models.CASCADE) person = auto_prefetch.ForeignKey(Person, on_delete=models.CASCADE) date = models.DateField('Date') breakfast = auto_prefetch.ForeignKey( Dish, null=True, blank=True, default=Dish.get_default_id, on_delete=models.SET_DEFAULT, related_name='breakfast' ) lunch = auto_prefetch.ForeignKey( Dish, null=True, blank=True, default=Dish.get_default_id, on_delete=models.SET_DEFAULT, related_name='lunch' ) dinner = auto_prefetch.ForeignKey( Dish, null=True, blank=True, default=Dish.get_default_id, on_delete=models.SET_DEFAULT, related_name='dinner' )
Event
: Эта модель имеет много методов и свойств, но среди них есть функцияget_meal_table
, которая возвращает всеDay
объекты, принадлежащие данному событию. Например, когда я хочу узнать для определенного дня события, сколько людей едят определенное блюдо на обед, я использую этот методget_meal_table
для получения всехDay
объектов события и затем фильтрую этот набор запросов, чтобы найтиDay
объекты с нужнымиdate
иlunch
свойствами.Person
: Представляет слушателей.
Проблема с таким способом заключается в том, что количество объектов модели, необходимых для представления полных данных о событии, увеличивается как с количеством дней, в течение которых длится событие, так и с количеством его участников. Поэтому:
- Если в какой-то момент продолжительность мероприятия изменится, мне придется создавать дополнительные
Day
объекты для каждого человека, который уже присутствует на мероприятии. Это очень неэффективно, потому что если мы продлеваем на 10 дней продолжительность мероприятия со 100 участниками, нам нужно создать не менее 1000 новыхDay
объектов. - Если мероприятие длится очень долго (например, один месяц), то для каждого нового участника нам нужно создать около 30 новых
Day
объектов.
Я хотел бы найти решение для представления тех же данных, но более "масштабируемым" способом, чтобы количество объектов не увеличивалось так сильно с количеством дней и количеством посетителей.
Другая идея
Я придумал другой возможный способ сделать это, но не знаю, сильно ли он улучшит ситуацию. Диаграмма будет выглядеть следующим образом:
Event
\_ Meal Table
\_ Day 1
\_ Dish 1
\_ People for breakfast: Person 1, Person 2
\_ People for lunch: Person 1, Person 2
\_ People for dinner: Person 1, Person 2
\_ Day 1
\_ Dish 2
\_ People for breakfast: Person 3
\_ People for lunch: Person 3
\_ People for dinner: Person 3
\_ Day 1
\_ Dish n
\_ People for breakfast: Person 4, Person 5
\_ People for lunch: Person 4, Person 5
\_ People for dinner: Person 4, Person 5
...
\_ Day 2
\_ Dish 1
\_ People for breakfast: Person 1
\_ People for lunch: Person 1
\_ People for dinner: Person 1
\_ Day 2
\_ Dish 2
\_ People for breakfast: Person 2
\_ People for lunch: Person 2
\_ People for dinner: Person 2
\_ Day 2
\_ Dish n
\_ People for breakfast: Person 3, Person 4, Person 5
\_ People for lunch: Person 3, Person 4, Person 5
\_ People for dinner: Person 3, Person 4, Person 5
...
\_ Day n
\_ Dish 1
\_ People for breakfast: Person 1
\_ People for lunch: Person 1
\_ People for dinner: Person 1
\_ Day n
\_ Dish 2
\_ People for breakfast: Person 2, Person 5
\_ People for lunch: Person 2, Person 5
\_ People for dinner: Person 2, Person 5
\_ Day n
\_ Dish n
\_ People for breakfast: Person 3, Person 4
\_ People for lunch: Person 3, Person 4
\_ People for dinner: Person 3, Person 4
...
В принципе, для представления полных данных потребуется только объектов, так что зависимость от количества участников снимается. Каждое событие имеет, для каждого
Day
и для каждого Dish
, один объект с отношением многие-ко-многим к Person
.
Я еще не тестировал это, но код может быть примерно таким:
from django.db import models
import auto_prefetch
class Day(auto_prefetch.Model):
event = auto_prefetch.ForeignKey(Event, on_delete=models.CASCADE)
date = models.DateField('Date')
dish = auto_prefetch.ForeignKey(
Dish,
null=True, blank=True,
on_delete=models.CASCADE
)
breakfast = models.ManyToManyField(
Persona, blank=True, related_name="breakfast")
lunch = models.ManyToManyField(
Persona, blank=True, related_name="lunch")
dinner = models.ManyToManyField(
Persona, blank=True, related_name="dinner")
С аналогичными Person
, Event
и Dish
моделями, которые были раньше.
Проблема, которую я вижу с этим методом, заключается в том, что мне нужно будет найти способ избежать того, чтобы Person
был, например, в двух breakfast
атрибутах двух разных Day
объектов разных dish
, но одинаковых date
.
Есть идеи?
Если постановка задачи такова
Итак, для каждого приема пищи каждого дня каждого мероприятия мне нужно хранить данные о том, сколько человек выбрали каждое из имеющихся блюд, учитывая, что один человек не может выбрать более одного блюда в каждом приеме пищи.
тогда я бы выбрал базовые модели
Event
- событие, как у вас.Dish
- блюдо, имеющееся на мероприятии, как у вас.Person
- участник мероприятия, как у вас.EventDay
- один день мероприятия, содержащий физическуюdate
(и другую информацию по мере необходимости)
а для выбора блюд просто
class MealSelection(models.Model):
day = models.ForeignKey('EventDay')
person = models.ForeignKey('Person')
meal = models.CharField() # could add choices breakfast/lunch/dinner, or make this an FK
dish = models.ForeignKey('Dish')
class Meta:
unique_together = [('day', 'person', 'meal')]
Утверждение unique_together
на уровне базы данных запретит кому-либо выбирать несколько блюд на одно блюдо в день на одно событие.
Затем, для запроса подсчетов, вы можете просто
dish_counts = dict( # make dict out of tuples
MealSelection.objects.filter(event=..., day=...)
.values('dish') # group by dish
.annotate(n=Count('*')) # select group counts
.values_list('dish', 'n') # select dish/n as tuple
)
и вы получите дикту, отображающую идентификаторы блюд на их количество.
Да, у вас будет до N_DAYS * N_ATTENDEES * N_MEALs MealSelection
s, но базы данных SQL оченьэффективно справляются с этим.