Давайте начнем с основ
Сериализатор может работать только с данными, которые ему даны
Так что это означает, что для получения сериализатора, который может сериализовать список ItemGroup
и Item
объекты во вложенном представлении, этот список должен быть задан в первую очередь.Вы уже достигли этого, используя запрос к модели ItemGroup
, который вызывает prefetch_related
, чтобы получить связанные Item
объекты.Вы также определили, что prefetch_related
запускает второй запрос для получения этих связанных объектов, и это не является удовлетворительным.
prefetch_related
используется для получения нескольких связанных объектов
Что делаетэто значит точно?Когда вы запрашиваете один объект, например, один ItemGroup
, вы используете prefetch_related
, чтобы получить отношение, содержащее несколько связанных объектов, например обратный внешний ключ (один-ко-многим) или отношение многие-ко-многимэто было определено.Django намеренно использует второй запрос для получения этих объектов по нескольким причинам
- Соединение, которое потребуется в
select_related
, часто не работает, когда вы заставляете его выполнять соединение со вторымТаблица.Это связано с тем, что для обеспечения того, чтобы ни один объект ItemGroup
, не содержащий Item
, не был пропущен, потребовалось бы правое внешнее объединение. - Запрос, используемый
prefetch_related
, - это IN
на индексированное поле первичного ключа , которое является одним из наиболее производительных запросов. - Запрос запрашивает только идентификаторы
Item
объектов, которые, как он знает, существуют, поэтому он может эффективно обрабатывать дубликаты.(в случае отношений «многие ко многим») без необходимости делать дополнительный подзапрос.
Все это - способ сказать: prefetch_related
делает именно то, что должен, ион делает это по причине.
Но я хочу сделать это с select_related
в любом случае
Хорошо, хорошо.Это то, о чем просили, так что давайте посмотрим, что можно сделать.
Есть несколько способов сделать это, каждый из которых имеет свои плюсы и минусы и ни один из которых не работает без какой-либо ручной «сшивки» работы вконец.Я предполагаю, что вы не используете встроенный ViewSet или универсальные представления, предоставляемые DRF, но если это так, то сшивание должно происходить в методе filter_queryset
, чтобы позволить встроенной фильтрации работать.Да, и это, вероятно, нарушает нумерацию страниц или делает его практически бесполезным.
Сохранение исходных фильтров
Исходный набор фильтров применяется к объекту ItemGroup
.И поскольку это используется в API, они, вероятно, являются динамическими, и вы не хотите их потерять.Итак, вам нужно применить фильтры одним из двух способов:
Сгенерируйте фильтры, а затем добавьте к ним префикс с именем
Таким образом, вы сгенерируете своиобычные foo=bar
фильтры, а затем префикс их перед передачей filter()
, так что это будет related__foo=bar
.Это может повлиять на производительность, поскольку теперь вы фильтруете отношения.
Создайте исходный подзапрос и затем передайте его непосредственно в запрос Item
Этовозможно, самое «чистое» решение, за исключением того, что вы генерируете запрос IN
с производительностью, сопоставимой с prefetch_related
.За исключением того, что это худшая производительность, так как вместо этого он обрабатывается как некэшируемый подзапрос.
Реализация обоих из них реально выходит за рамки этого вопроса, поскольку мы хотим иметь возможность «перевернуть»и сшиваем "Item
и ItemGroup
объекты, чтобы сериализатор работал.
Переверните запрос Item
, чтобы получить список ItemGroup
объектов
Принимая запрос, заданный вИсходный вопрос, где select_related
используется для захвата всех объектов ItemGroup
рядом с объектами Item
, возвращается набор запросов, полный объектов Item
.На самом деле нам нужен список из ItemGroup
объектов, поскольку мы работаем с ItemGroupSerializer
, поэтому нам придется «перевернуть его».
from collections import defaultdict
items = Item.objects.filter(**filters).select_related('item_group')
item_groups_to_items = defaultdict(list)
item_groups_by_id = {}
for item in items:
item_group = item.item_group
item_groups_by_id[item_group.id] = item_group
item_group_to_items[item_group.id].append(item)
Я намеренно использую id
из ItemGroup
в качестве ключа для словарей, поскольку большинство моделей Django не являются неизменяемыми, и иногда люди переопределяют метод хеширования, чтобы он отличался от первичного ключа.
Это даст вам сопоставление ItemGroup
объектов с их связанными Item
объектами, что в конечном итоге вам и понадобится для того, чтобы снова "сшить" их вместе.
Сшивание ItemGroup
объектов обратносо связанными Item
объектами
Эта часть на самом деле не трудна для выполнения, поскольку у вас уже есть все связанные объекты.
for item_group_id, item_group_items in item_group_to_items.items():
item_group = item_groups_by_id[item_group_id]
item_group.item_set = item_group_items
item_groups = item_groups_by_id.values()
Это даст вам все ItemGroup
объекты, которые были запрошены и хранятся как list
в переменной item_groups
.Каждый объект ItemGroup
будет иметь список связанных объектов Item
, установленных в атрибуте item_set
.Вы можете переименовать его, чтобы он не конфликтовал с автоматически сгенерированным обратным внешним ключом с тем же именем.
Здесь вы можете использовать его, как обычно, в ItemGroupSerializer
, и он должен работатьза сериализацию.
Бонус: универсальный способ «перевернуть и сшить»
Вы можете сделать этот универсальный (и нечитаемый) довольно быстро, для использования в других подобных сценариях:
def flip_and_stitch(itmes, group_from_item, store_in):
from collections import defaultdict
item_groups_to_items = defaultdict(list)
item_groups_by_id = {}
for item in items:
item_group = getattr(item, group_from_item)
item_groups_by_id[item_group.id] = item_group
item_group_to_items[item_group.id].append(item)
for item_group_id, item_group_items in item_group_to_items.items():
item_group = item_groups_by_id[item_group_id]
setattr(item_group, store_in, item_group_items)
return item_groups_by_id.values()
И вы бы просто назвали это как
item_groups = flip_and_stitch(items, 'item_group', 'item_set')
Где:
items
- это набор запросов элементов, которые вы запросили изначально, с select_related
вызов уже применен. item_group
- это атрибут объекта Item
, в котором хранится связанный ItemGroup
. item_set
- это атрибут объекта ItemGroup
, гдесписок связанных Item
объектов должен быть сохранен.