我可以依靠Django表单中的字段验证顺序吗?

时间:2010-07-21 13:01:52

标签: django validation

我有一个带有用户名和电子邮件字段的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。但是文档还不清楚字段清理的顺序。我在表单中的电子邮件之前声明用户名,这是否意味着我可以安全地假设用户名在电子邮件之前被清除了?

我可以阅读代码,但我对Django API的承诺更感兴趣,并且知道即使在未来版本的Django中我也很安全。

4 个答案:

答案 0 :(得分:8)

更新

.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中)。

答案 1 :(得分:7)

没有承诺以任何特定顺序处理字段。官方建议,任何依赖于多个字段的验证都应使用clean()方法表单,而不是字段特定的clean_foo()方法。

答案 2 :(得分:7)

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

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

  

这些方法按顺序运行   如上所述,一次一个场。那   是,对于表格中的每个字段(在   命令它们在表格中声明   定义),Field.clean()方法   然后运行(或其覆盖)   清洁_()。最后一次   这两种方法都是为每一种运行的   字段,Form.clean()方法或其   覆盖,执行。

答案 3 :(得分:3)

  

Form子类的clean()方法。这种方法可以执行任何   需要从表单访问多个字段的验证   一旦。如果字段A,您可以在此处检查该项   如果提供,则字段B必须包含有效的电子邮件地址等。   此方法返回的数据是最终的cleaning_data属性   对于表单,所以不要忘记返回已清理数据的完整列表   如果重写此方法(默认情况下,Form.clean()只返回   self.cleaned_data)。

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

复制粘贴

这意味着如果你想检查电子邮件的值和parent_email不相同的东西,你应该在该函数内部进行检查。即:

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