代码之家  ›  专栏  ›  技术社区  ›  thornomad

如何在不使用模型窗体的情况下验证/clean()一个unique=true字段?

  •  12
  • thornomad  · 技术社区  · 16 年前

    在自定义表单中,如何验证模型字段的唯一性(即 unique=True 设置)?

    我知道Django的ModelForm自动执行 validate_unique() 在BaseModelForm中调用的函数 clean() 方法——所以,当使用ModelForm时,将正确处理它(就像在管理中一样)。

    然而,我正在从头开始创建我自己的表单,我想知道如何自己来处理这个问题?我认为我最大的障碍是当数据被清除时知道哪个对象被附加到表单上…

    一些代码:

    class UserProfile(CreatedModifiedModel):
        user            = models.ForeignKey(User, unique=True)
        display_name    = models.CharField('Display Name',max_length=30,
                            blank=True,unique=True)
    
    class EditUserProfileForm(forms.Form):
        display_name    = forms.CharField(required=False,max_length=30)
    
        # "notifications" are created from a different model, not the UserProfile
        notifications    = forms.MultipleChoiceField(
                            label="Email Notifications",
                            required=False,
                            widget=forms.CheckboxSelectMultiple,)
    
        def clean_display_name(self):
            # how do I run my own validate_unique() on this form?
            # how do I know which UserProfile object I am working with?
    
        # more code follows, including the __init__ which sets up the notifications
    
    2 回复  |  直到 7 年前
        1
  •  16
  •   Ondrej Slinták    13 年前

    唯一验证很难完全正确,因此我建议您无论如何使用ModelForm:

    class EditUserProfileForm(forms.ModelForm):
        # "notifications" are created from a different model, not the UserProfile
        notifications    = forms.MultipleChoiceField(
                            label="Email Notifications",
                            required=False,
                            widget=forms.CheckboxSelectMultiple,)
    
        class Meta:
            model = UserProfile
            fields = ('display_name',)
    

    从多个模型创建表单并不容易,但是在这种情况下,您可以添加 notifications 字段到模型窗体并将其拉出 .cleaned_data 像往常一样:

    # view
    if request.method == 'POST':
        form = EditUserProfileForm(request.POST, instance=user_profile)
        if form.is_valid():
            user_profile = form.save()
            notifications = form.cleaned_data['notifications']
            # Do something with notifications.
    

    我就是这么做的,但是如果你想验证自己的独特性,你总是可以做如下的事情:

    def clean_display_name(self):
        display_name = self.cleaned_data['display_name']
        if UserProfile.objects.filter(display_name=display_name).count() > 0:
            raise ValidationError('This display name is already in use.')
        return display_name
    

    这里有两个问题。首先,您可能会遇到并发性问题,其中两个人提交相同的名称,都通过唯一性检查,但是一个人会得到一个DB错误。另一个问题是,您不能编辑用户配置文件,因为您没有要从搜索中排除的ID。你得把它储存在你的 __init__ 然后用它清洗:

    def __init__(self, *args, **kwargs):
        ...
        if 'instance' in kwargs:
            self.id = kwargs['instance'].id
        ...
    
    def clean_display_name(self):
        display_name = self.cleaned_data['display_name']
        qs = UserProfile.objects.filter(display_name=display_name)
        if self.id:
            qs = qs.exclude(pk=self.id)
        if qs.count() > 0:
            raise ValidationError('This display name is already in use.')
        return display_name
    

    但在那一点上,你只是在复制模型表单中的逻辑。

        2
  •  0
  •   Nids Barthwal    7 年前

    工作示例!

    我使用电子邮件作为唯一字段

    在models.py中使用以下代码

    User._meta.get_field('email')._unique = True
    

    在Forms.py中使用以下代码

    class ProfileForm(forms.ModelForm):
        email = forms.EmailField(max_length=255, required=True)
    
        def clean_email(self):
            user_id = self.cleaned_data['user'].id
            email = self.cleaned_data['email']
            obj = User.objects.filter(email=email).exclude(id = user_id)
            if obj:
                raise forms.ValidationError('User with this email is already exists.Try a new email')