在 FK 上使用"limit_choices_to"会导致在提交模型表单时出现"多个对象返



TL;博士:

根据相关对象的值筛选查询集可能会导致结果中出现重复值。

以类似方式使用模型字段中的limit_choices_toFK 属性时,此行为会传播到模型字段中,在使用与此模型关联的模型窗体并选择重复值时会导致MultipleObjectsReturned错误。

是否可以在模型的外键limit_choices_to上应用distinct()或等效项,以避免模型表单字段的选项重复?


重现问题:

有了python manage.py shell(并解决它(:

让两个模型AB

class A(models.Model):
pass
class B(models.Model):
a = models.ForeignKey(A)
d = models.BooleanField(default=False)

和以下条目:

>>> a = A.objects.create()
>>> b1 = B.objects.create(a=a, d=True)
>>> b2 = B.objects.create(a=a, d=True)

以下使用get()查询集会导致错误:

>>> A.objects.filter(b__d=True).get(id=1)
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/home/vmonteco/.venvs/django/lib/python3.6/site-packages/django/db/models/query.py", line 384, in get
(self.model._meta.object_name, num)
app.models.MultipleObjectsReturned: get() returned more than one A -- it returned 2!

这听起来很正常,因为afilter()的结果中出现了两次:

>>> A.objects.filter(b__d=True)
<QuerySet [<A: A object>, <A: A object>]>

此错误可以通过简单的distinct()轻松解决:

>>> A.objects.filter(b__d=True).distinct().get(id=1)
<A: A object>

与第三个模型及其关联的模型形式:

让我们添加第三个模型:

class C(models.Model):
a = models.ForeignKey(A, limit_choices_to={'b__d': True})

我可以使用模型表单创建/编辑实例:

class CForm(forms.ModelForm):
class Meta:
model = C
fields = ['a',]

填充a字段选择的查询集应如下所示:

>>> A.objects.filter(b__d=True)
<QuerySet [<A: A object>, <A: A object>]>

其中只包含相同的对象两次:

>>> A.objects.filter(b__d=True).values('id')
<QuerySet [{'id': 1}, {'id': 1}]>

然后,在表单提交时,django 对字段的查询集应用get(id=selected_value)。如果所选值是重复值,则会出现我在上一部分中暴露的问题。

当前解决方案:

到目前为止,我找到的唯一解决方案是在我的模型表单中覆盖字段的查询集,以确保没有重复:

class CForm(forms.ModelForm):
class Meta:
model = C
fields = ['a',]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['a'].queryset = self.fields['a'].queryset.distinct()

但是,由于此查询集是在模型字段的定义之后直接定义的,因此此解决方案感觉不令人满意,看起来更像是一种解决方法。limit_choices_to似乎没有记录此案。

在使用字段查询集时,是否有更合适的方法来避免limit_choices_to中的重复?

我创建了一个可调用对象来获取选项列表,防止返回多行的外键查询:

class ParameterTypeGetter():
def __init__(self, class_name):
self.class_name = class_name
def __call__(self):
types = ParameterType.objects.filter(datatype__data_class__name=self.class_name)
return Q(parameter_type__in=types)
class ElementData(models.Model, VarDataCommentsMixIn):
parameter = models.ForeignKey(
Parameter, null=True, blank=True,
on_delete=models.PROTECT,
limit_choices_to=ParameterTypeGetter('parameter'))
multiparameter = models.ManyToManyField(
Parameter, related_name='multiparameter', blank=True,
limit_choices_to=ParameterTypeGetter('multiparameter'))

当我遇到这个问题时的方式:

limit_choices_to=dict(
parameter_type__datatype__data_class__name='parameter'),)

最新更新