在 FK 上使用“limit_choices_to”会导致 ModelForm 提交时出现“MultipleObjectsReturned”错误

vmo*_*eco 5 django duplicates django-queryset

长话短说:

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

limit_choices_to当以类似的方式使用模型字段时,此行为会在模型字段中的 FK 属性上传播,MultipleObjectsReturned从而在使用与此模型关联的模型表单并选择重复值时导致错误。

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


重现问题:

python manage.py shell(并解决它):

设两个模型AB

class A(models.Model):
    pass

class B(models.Model):
    a = models.ForeignKey(A)
    d = models.BooleanField(default=False)
Run Code Online (Sandbox Code Playgroud)

以及以下条目:

>>> a = A.objects.create()
>>> b1 = B.objects.create(a=a, d=True)
>>> b2 = B.objects.create(a=a, d=True)
Run Code Online (Sandbox Code Playgroud)

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

>>> 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!
Run Code Online (Sandbox Code Playgroud)

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

>>> A.objects.filter(b__d=True)
<QuerySet [<A: A object>, <A: A object>]>
Run Code Online (Sandbox Code Playgroud)

这个错误可以通过简单的方法轻松解决distinct()

>>> A.objects.filter(b__d=True).distinct().get(id=1)
<A: A object>
Run Code Online (Sandbox Code Playgroud)

使用第三个模型及其关联的 modelform :

让我们添加第三个模型:

class C(models.Model):
    a = models.ForeignKey(A, limit_choices_to={'b__d': True})
Run Code Online (Sandbox Code Playgroud)

我可以使用 modelform 创建/编辑实例:

class CForm(forms.ModelForm):
    class Meta:
        model = C
        fields = ['a',]
Run Code Online (Sandbox Code Playgroud)

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

>>> A.objects.filter(b__d=True)
<QuerySet [<A: A object>, <A: A object>]>
Run Code Online (Sandbox Code Playgroud)

其中仅包含同一对象两次:

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

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

目前的解决方案:

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

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()
Run Code Online (Sandbox Code Playgroud)

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

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

rap*_*pto 0

我创建了一个可调用函数来获取选择列表,从而防止返回多行的外键查询:

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'))
Run Code Online (Sandbox Code Playgroud)

我遇到这个问题时的情况是这样的:

        limit_choices_to=dict(
            parameter_type__datatype__data_class__name='parameter'),)
Run Code Online (Sandbox Code Playgroud)