【发布时间】:2021-03-16 21:37:12
【问题描述】:
问题的概念总结:
- 假设我们有一个带有
Author和Book模型的Django 应用,并使用BookFormSet添加/修改/删除属于给定Author的书籍。 - 问题是当
BookFormSet被验证时,ModelChoiceField.to_python()最终会调用self.queryset.get(id=123),这会导致表单集中每本书的单对象SELECT查询。 - 这意味着如果我想更新 15 本书,Django 会执行 15 次单独的 SELECT 查询,这似乎非常低效。 (我们的实际应用是一个编辑器,可以在单个表单集中更新任意数量的对象,例如 50 多个)。
我尝试了以下几点:
- 首先我尝试将查询集传递给
BookFormSet,即formset = BookFormSet(data=request.POST, queryset=Book.objects.filter(author=1)),但ModelChoiceField仍然执行其单对象SELECT 查询。 - 然后我试图查看
ModelChoiceField定义其查询集的位置,它似乎在BaseModelFormSet.add_fields()中。我尝试使用传递给表单集的相同查询集启动ModelChoiceField,例如Book.objects.filter(author=1)而不是原来的代码Book._default_manager.get_queryset()。但这无济于事,因为我定义的新查询集实际上并没有链接到传递给表单集和之前评估的内容。所以仍然会发生多个 SELECT 查询。 (注意:我意识到model._default_manager.get_queryset()可能在表单集可用于将一个模型实例切换到另一个可能不在传递给BaseModelFormset的原始查询集中的实例的情况下是必要的,但这不是我们的用例) - 我注意到
BaseModelFormSet._existing_object()实际上提供了一种方法来检查对象是否存在于提供给表单集构造函数的查询集中,这意味着查询集最多被评估一次,结果存储在BaseModelFormSet._object_dict中。我认为可能有一些方法可以让ModelChoiceField.to_python()在调用self.queryset.get(id=123)之前进行类似的检查,但我认为ModelChoiceField不知道BaseModelFormSet,这似乎是一种反模式像这样的层次结构。
在我看来,最简单的解决方案是以某种方式将 BaseModelFormSet._object_dict 传递给创建的每个 ModelForm,然后允许 ModelChoiceField 在进行另一个 SELECT 查询之前检查此 _object_dict。
附录:
这是来自 Django-Debug-Toolbar 的屏幕截图,显示了第一个 SELECT 查询(这是传递给 BaseModelFormSet 的查询集),随后是 4 个单独的 SELECT 查询(每个 form.instance 一个)
注意:我还在此处将其作为 Django 票发布: https://code.djangoproject.com/ticket/32244#comment:1
即使可以通过某种形式的缓存来解决这个问题,它仍然看起来仍然是低效的应用层逻辑。
【问题讨论】:
-
编辑:我认为这可能是一个更深层次的 Django ORM 问题,如果你有
qs = Book.objects.filter(author=1)(假设这会返回 3 本书,id = 1,2,3),然后评估它-- 即len(list(qs))-- 然后尝试book2 = qs.get(id=2)这仍然会运行 SELECT 查询,即使在评估原始查询集时已经获取了 book2
标签: django orm django-forms formset