让我们从基础开始
序列化程序只能处理给定的数据
所以这意味着为了获得一个可以序列化嵌套表示中的对象列表的序列化程序ItemGroup
,Item
必须首先给出该列表。到目前为止,您已经使用ItemGroup
模型上的查询来调用prefetch_related
以获取相关Item
对象。您还发现prefetch_related
触发了第二个查询以获取这些相关对象,这并不令人满意。
prefetch_related
用于获取多个相关对象
这到底是什么意思?当您查询单个对象时,例如 single ItemGroup
,您可以使用prefetch_related
获取包含多个相关对象的关系,例如反向外键(一对多)或已定义的多对多关系。出于几个原因,Django 故意使用第二个查询来获取这些对象
- 当您强制它对第二个表进行连接时,a 中所需的连接
select_related
通常是非性能的。这是因为需要右外连接以确保不会遗漏ItemGroup
不包含 an 的对象。Item
- 使用的查询
prefetch_related
是索引主键字段IN
上的,这是目前性能最高的查询之一。
- 该查询只请求它知道存在的对象的 ID
Item
,因此它可以有效地处理重复项(在多对多关系的情况下),而无需执行额外的子查询。
所有这一切都是一种说法:prefetch_related
正在做它应该做的事情,并且这样做是有原因的。
select_related
但无论如何我想这样做
好吧好吧。这就是所要求的,所以让我们看看可以做什么。
有几种方法可以实现这一点,所有这些方法都有其优点和缺点,并且最终如果没有一些手动“缝合”工作,它们都不起作用。我假设您没有使用 DRF 提供的内置 ViewSet 或通用视图,但如果您使用了,则必须在filter_queryset
方法中进行拼接以允许内置过滤工作。哦,它可能会破坏分页或使其几乎无用。
保留原始过滤器
原始过滤器集正在应用于ItemGroup
对象。由于这是在 API 中使用的,因此这些可能是动态的,您不想丢失它们。因此,您将需要通过以下两种方式之一应用过滤器:
生成过滤器,然后在它们前面加上相关名称
因此,您将生成普通foo=bar
过滤器,然后在将其传递给之前为它们添加前缀,filter()
这样就可以了related__foo=bar
。这可能会对性能产生一些影响,因为您现在正在跨关系进行过滤。
生成原始子查询,然后直接传给Item
查询
这可能是“最干净”的解决方案,除了您生成的IN
查询性能与该解决方案相当prefetch_related
。除了性能更差,因为这被视为不可缓存的子查询。
实现这两个实际上超出了这个问题的范围,因为我们希望能够“翻转和缝合” Item
andItemGroup
对象,以便序列化程序工作。
翻转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
ofItemGroup
作为字典的键,因为大多数 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