django - Django ModelFormSet 在验证时对每个 formset 实例执行一个单对象 SELECT 查询(ORM 效率低下)
问题描述
问题的概念总结:
- 假设我们有一个 Django 应用程序
Author
和Book
模型,并使用 aBookFormSet
添加/修改/删除属于给定的书籍Author
。 - 问题是当
BookFormSet
验证时,ModelChoiceField.to_python()
最终调用这会导致表单集中每本书self.queryset.get(id=123)
的单对象 SELECT 查询。 - 这意味着如果我想更新 15 本书,Django 会执行 15 个单独的 SELECT 查询,这似乎非常低效。(我们的实际应用程序是一个编辑器,可以在单个表单集中更新任意数量的对象,例如 50+)。
以下是我尝试过的几件事:
- 首先,我尝试将查询集传递给
BookFormSet
, ieformset = 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()
实际上提供了一种方法来检查对象是否存在于提供给 formset 构造函数的查询集中,这意味着查询集最多被评估一次,结果存储在BaseModelFormSet._object_dict
. 我认为在调用之前可能有一些方法可以ModelChoiceField.to_python()
进行类似的检查self.queryset.get(id=123)
,但我认为不ModelChoiceField
知道BaseModelFormSet
,并且像这样达到层次结构似乎是一种反模式。
在我看来,最简单的解决方案BaseModelFormSet._object_dict
是以某种方式传递给创建的每个 ModelForm,然后允许在进行另一个 SELECT 查询之前ModelChoiceField
检查它。_object_dict
附录:
这是来自 Django-Debug-Toolbar 的屏幕截图,显示了第一个 SELECT 查询(这是传递给 BaseModelFormSet 的查询集),然后是 4 个单独的 SELECT 查询(每个 form.instance 一个)
注意:我也在此处将其作为 Django 票发布: https ://code.djangoproject.com/ticket/32244#comment:1
即使这可以通过某种形式的缓存来解决,它仍然看起来仍然是低效的应用层逻辑。
解决方案
我想出了一个非常老套的解决方法,它保留了我们需要的表单集功能。基本上BookForm
你可以覆盖ModelForm._clean_fields()
def _clean_fields(self):
# remove 'id' field so it's not cleaned
# (this is the ModelChoiceField that's generating an extra SELECT query per Book object in the formset during clean)
id_field = self.fields.pop('id')
# run normal cleaning, with the form now unaware of its own 'id' field
super(BookForm, self)._clean_fields()
# add 'id' field back and insert it into clean_data
id_value = id_field.widget.value_from_datadict(self.data, self.files, self.add_prefix('id'))
self.cleaned_data['id'] = id_value
# add the 'id' field back into the form
self.fields['id'] = id_field
推荐阅读
- ddev - 从其他主机访问 ddev web 容器
- sql - 如何为zend 3编写sql?
- c# - 在 WPF 日历中将日期显示为假期
- php - 如何使用php从xml格式的xml文档中获取特定标签
- google-apps-script - appScript 在数据存在时从参考单元格中连接和增加查询。我错过了什么?
- docker - Docker swarm:如何将应用程序和数据库放在一起?
- c# - Puppeteer Sharp:避免下载 Chromium(本地捆绑 Chromium)
- angular - 使用调用 pixabay api 的 http 请求在 Angular 6 中显示图像
- wxpython - 如何在 Mac 上运行 Ride?
- rest - Rest 方法,与我们返回的内容链接