【问题标题】:ModelChoiceField in forms.Form won't validate if queryset is overriddenforms.Form 中的 ModelChoiceField 将不会验证查询集是否被覆盖
【发布时间】:2014-07-10 13:21:12
【问题描述】:

我有一个 django ModelChoiceField,如果我覆盖查询集,它将无法验证。

class PersonalNote(forms.Form):
    tile    = ModelChoiceField(queryset=Tile.objects.none())
    note    = forms.CharField()

form = PersonalNote()
form.fields['tile'].queryset = Tile.objects.filter(section__xxx=yyy)

form.is_valid() 错误是:“选择一个有效选项。该选项不是可用选项之一”。 如果 Tile.objects.none() 被替换为 Tile.objects.all() 它会验证,但会从数据库加载太多数据。我也试过:

class PersonalNote(forms.Form):
    tile    = ModelChoiceField(queryset=Tile.objects.none())
    note    = forms.CharField()

    def __init__(self, *args, **kwargs):
        yyy = kwargs.pop('yyy', None)
        super(PersonalNote, self).__init__(*args, **kwargs)
        if yyy:
            self.fields['tile'].queryset = Tile.objects.filter(section__xxx=yyy)

这里可能有什么问题?请注意,实际应用程序也会覆盖标签,但这似乎不是一个因素:

class ModelChoiceField2(forms.ModelChoiceField):
    def label_from_instance(self, obj):
        assert isinstance(obj,Tile)
        return obj.child_title()

【问题讨论】:

    标签: django validation django-forms


    【解决方案1】:

    2 小时后,我找到了解决方案。因为您在类定义中指定了 none 查询集,所以当您实例化要验证的 PersonalNote(request.POST) 时,它引用了一个空查询集

    class PersonalNote(forms.Form):
        tile    = ModelChoiceField(queryset=Tile.objects.none())
        note    = forms.CharField() 
    

    要解决此问题,当您基于 POST 请求创建表单时,请务必在检查 is_valid() 之前再次覆盖您的查询集

    def some_view_def(request):
        form = PersonalNote(request.POST)
        **form.fields['tile'].queryset = Tile.objects.filter(section__xxx=yyy)**
    
        if form.is_valid():
             #Do whatever it is
    

    【讨论】:

      【解决方案2】:

      当您将一个空查询集传递给ModelChoiceField 时,您是在说该字段没有任何内容是有效的。也许您可以过滤查询集,这样就没有太多选项了。

      【讨论】:

      • 对。如何过滤查询集?上述两种方法都不起作用。
      • 在模型表单中,与传递空查询集 (Tile.objects.none()) 的方式相同。您可以使用filter() 缩小有效选择范围。
      • 为了过滤,我需要访问 yyy 参数,这就是关键所在。许多示例显示在构造函数中使用 none(),然后稍后添加查询集。它可以工作,除了验证。
      • 啊,我明白了。一种可能的解决方案是覆盖适当的字段清理方法,即:clean_tile()
      【解决方案3】:

      我也有这个问题。这个想法是根据条件动态更改ModelChoiceField 的查询集(在我的情况下,它是另一个ModelChoiceField 制作的过滤器)。

      所以,以下一个模型为例:

      class FilterModel(models.Model):
          name = models.CharField()
      
      class FooModel(models.Model):
          filter_field = models.ForeignKey(FilterModel)
          name = models.CharField()
      
      class MyModel(models.Model):
          foo_field = models.ForeignKey(FooModel)
      

      如您所见,MyModel 有一个带有 FooModel 的外键,但没有带有 FilterModel 的外键。所以,为了过滤FooModel 选项,我在我的表单上添加了一个新的ModelChoiceField

      class MyForm(forms.ModelForm):
         class Meta:
              model = MyModel
      
         def __init__(self, *args, **kwargs):
             # your code here
             self.fields['my_filter_field'] = forms.ModelChoiceField(FilterModel, initial=my_filter_field_selected)
             self.fields['my_filter_field'].queryset = FilterModel.objects.all()
      

      然后,在您的前端,您可以使用 Ajax 根据 my_filter_field 的选定值加载 foo_field 的选项。在这一点上,一切都应该工作。但是,当表单被加载时,它将带来来自FooModel 的所有可能选项。为避免这种情况,您需要动态更改foo_field 的查询集。

      在我的表单视图中,我向MyForm 传递了一个新参数:

      id_filter_field = request.POST.get('my_filter_field', None)
      form = MyForm(data=request.POST, id_filter_field=id_filter_field)
      

      现在,您可以在 MyForm 上使用该参数来更改查询集:

      class MyForm(forms.ModelForm):
          # your code here
          def __init__(self, *args, **kwargs):
              self.id_filter_field = kwargs.pop('id_filter_field', None)
              # your code here
              if self.id_filter_field:
                  self.fields['foo_field'].queryset = FooModel.objects.filter(filter_field_id=self.id_filter_field)
              else:
                  self.fields['foo_field'].queryset = FooModel.objects.none()
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-11-01
        • 2021-11-16
        • 2011-08-17
        • 1970-01-01
        • 2014-11-15
        • 2018-02-01
        • 2022-12-18
        • 1970-01-01
        相关资源
        最近更新 更多