代码之家  ›  专栏  ›  技术社区  ›  Ned Batchelder

我能指望Django表单中字段验证的顺序吗?

  •  8
  • Ned Batchelder  · 技术社区  · 14 年前

    我有一个Django表单,上面有用户名和电子邮件字段。我想检查用户尚未使用电子邮件:

    def clean_email(self):
        email = self.cleaned_data["email"]
        if User.objects.filter(email=email).count() != 0:
            raise forms.ValidationError(_("Email not available."))
        return email
    

    def clean_email(self):
        email = self.cleaned_data["email"]
        username = self.cleaned_data["username"]
        if User.objects.filter(email=email,  username__ne=username).count() != 0:
            raise forms.ValidationError(_("Email not available."))
        return email
    

    Django文档说,在进入下一个字段之前,一个字段的所有验证都已完成。如果电子邮件在用户名之前被清除,那么 cleaned_data["username"] clean_email

    我可以阅读代码,但我更感兴趣的是djangoapi的前景如何,并且知道即使在Django的未来版本中,我也是安全的。

    4 回复  |  直到 14 年前
        1
  •  8
  •   Wolph    14 年前

    Django文档声称它是按字段定义的顺序排列的。

    但我发现它并不总是能兑现诺言。 资料来源: http://docs.djangoproject.com/en/dev/ref/forms/validation/

    如上所述,一次一个字段。那个 在表格中声明的顺序 定义),Field.clean()方法 清洁()。最后,一次 这两种方法在每种情况下都会运行 字段、Form.clean()方法或其 执行覆盖。

        2
  •  9
  •   Jordan Reiter    7 年前

    .keyOrder 不再有效。我认为应该这样做:

    from collections import OrderedDict
    
    
    class MyForm(forms.ModelForm):
        …
    
        def __init__(self, *args, **kwargs):
            super(MyForm, self).__init__(*args, **kwargs)
            field_order = ['has_custom_name', 'name']
            reordered_fields = OrderedDict()
            for fld in field_order:
                reordered_fields[fld] = self.fields[fld]
            for fld, value in self.fields.items():
                if fld not in reordered_fields:
                    reordered_fields[fld] = value
            self.fields = reordered_fields
    

    有些东西可以改变表单顺序,而不管您如何在表单定义中声明它们。其中之一就是如果你用的是 ModelForm ,在这种情况下,除非在 fields 在下面 class Meta 他们将处于一种不可预测的状态。

    有一个可靠的解决方案

    可以通过设置 self.fields.keyOrder

    下面是一些您可以使用的示例代码:

    class MyForm(forms.ModelForm):
        has_custom_name = forms.BooleanField(label="Should it have a custom name?")
        name = forms.CharField(required=False, label="Custom name")
    
        class Meta:
            model = Widget
            fields = ['name', 'description', 'stretchiness', 'egginess']
    
        def __init__(self, *args, **kwargs):
            super(MyForm, self).__init__(*args, **kwargs)
            ordered_fields = ['has_custom_name', 'name']
            self.fields.keyOrder = ordered_fields + [k for k in self.fields.keys() if k not in ordered_fields]
    
        def clean_name(self):
            data = self.cleaned_data
            if data.get('has_custom_name') and not data.get('name'):
                raise forms.ValidationError("You must enter a custom name.")
            return data.get('name')
    

    keyOrder 设置, has_custom_name 将被验证(因此出现在 self.cleaned_data )之前 name 已验证。

        3
  •  8
  •   Daniel Roseman    14 年前

    不能保证这些字段是按特定顺序处理的。官方建议,任何依赖于多个字段的验证都应该在表单的 clean() 方法,而不是特定于字段的 clean_foo() 方法。

        4
  •  3
  •   Harph    12 年前

    表单子类clean()方法。此方法可以执行任何操作 需要从位于的窗体访问多个字段的验证 一次。在这里你可以输入一些东西来检查 字段B必须包含有效的电子邮件地址等。 此方法返回的数据是最终的数据属性 如果重写此方法(默认情况下,Form.clean()只返回 自我清理(数据)。

    https://docs.djangoproject.com/en/dev/ref/forms/validation/#using-validators

    这意味着,如果你想检查的东西,如电子邮件的价值和家长的电子邮件是不一样的,你应该在该功能内这样做。即:

    from django import forms
    
    from myapp.models import User
    
    class UserForm(forms.ModelForm):
        parent_email = forms.EmailField(required = True)
    
        class Meta:
            model = User
            fields = ('email',)
    
        def clean_email(self):
            # Do whatever validation you want to apply to this field.
            email = self.cleaned_data['email']
            #... validate and raise a forms.ValidationError Exception if there is any error
            return email
    
        def clean_parent_email(self):
            # Do the all the validations and operations that you want to apply to the
            # the parent email. i.e: Check that the parent email has not been used 
            # by another user before.
            parent_email = self.cleaned_data['parent_email']
            if User.objects.filter(parent_email).count() > 0:
                raise forms.ValidationError('Another user is already using this parent email')
            return parent_email
    
        def clean(self):
            # Here I recommend to user self.cleaned_data.get(...) to get the values 
            # instead of self.cleaned_data[...] because if the clean_email, or 
            # clean_parent_email raise and Exception this value is not going to be 
            # inside the self.cleaned_data dictionary.
    
            email = self.cleaned_data.get('email', '')
            parent_email = self.cleaned_data.get('parent_email', '')
            if email and parent_email and email == parent_email:
                raise forms.ValidationError('Email and parent email can not be the same')
            return self.cleaned_data