Проблема
У нас есть такая схема:# категория афиши class CategoryPlaybill(models.Model): ... # афиша class Playbill(models.Model): categories = models.ManyToManyField(CategoryPlaybill, ...) ... # проведение афиши class ConductingPlaybill(models.Model): playbill = models.ForeignKey(Playbill, ...) ...Итак, есть типы событий. Есть события по многие-ко-многим (событие может входить в несколько категорий). А есть их так называемые проведения — событие с конкретной датой, временем, местом. Мне надо получить проведения событий с разбивкой по категориям: категория1=>(список проведений), категория2 =>(список проведений).
Приходит в голову решение в лоб:
cond_playbill_list = ConductingPlaybill.objects.select_related().all() cond_playbill_map = {} for cond_playbill in cond_playbill_list: cats = cond_playbill.playbill.categories.all() for cat in cats: if cat not in cond_playbill_map: cond_playbill_map[cat] = [] cond_playbill_map[cat].append(cond_playbill)Но это решение ожидаемо даёт кучу запросов в БД на предмет
SELECT FROM categoryplaybill WHERE playbill_categories.playbill_id = N
для каждой итерации внешнего цикла. Дело в том, что select_related не тянет playbill_categories и ниже. Мысли
Далее думаем: чтобы сделать одним запросом, надо в каждой строке resulset иметь 'playbill__categories__*', но при условии m2m получится дубли по числу связей playbill<->categories, очевидно. django orm такое не умеет, походу.На raw-sql я бы написал что-то типа такого:
SELECT clazz_conductingplaybill.id as 'conducting-id', clazz_conductingplaybill.date_from as 'conducting-date_from', clazz_playbill.id as 'playbill-id', clazz_playbill.title as 'playbill-title', clazz_playbill_categories.id as 'playbill-categories-id', clazz_categoryplaybill.title as 'categoryplaybill-title' FROM clazz_conductingplaybill, clazz_playbill_categories JOIN clazz_playbill ON clazz_playbill.id=clazz_conductingplaybill.playbill_id LEFT JOIN clazz_categoryplaybill ON clazz_categoryplaybill.id=clazz_playbill_categories.categoryplaybill_id WHERE clazz_playbill_categories.playbill_id=clazz_playbill.id ORDER BY clazz_playbill.id, clazz_conductingplaybill.idПотом прошёл и собрал просто в map. В том числе все дубли clazz_conductingplaybill для каждой категории.
Решение
А решение тут простое: сначала собрать все связки playbill__categories, а потом обойти ConductingPlaybill, не вытягивая cond_playbill.playbill.categories и всё что дальше. Заодно используем defaultdict, крайне удобный и эффективный в нашем случае.cond_playbill_map = defaultdict(list) # связи выбираем категория-событие byplaybill = defaultdict(list) for pr in Playbill.categories.through.objects.select_related().all(): byplaybill[pr.playbill.id].append(pr.categoryplaybill) # выбираем проведения cond_playbill_list = ConductingPlaybill.objects.select_related().all() for cond_playbill in cond_playbill_list: cats = byplaybill[cond_playbill.playbill.id] for cat in cats: cond_playbill_map[cat].append(cond_playbill)Если Playbill.categories.through - явно указанная модель, то можно использовать её. Не забудьте принять меры перед использованием defaultdict в django шаблонах :)
Комментариев нет:
Отправить комментарий