如何在不使用ModelForm的情况下验证/ clean()一个unique = True字段?

tho*_*mad 12 django django-forms

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

我知道django的ModelForm自动执行validate_unique()在BaseModelForm的clean()方法中调用的函数- 因此,当使用ModelForm时,这将被正确处理(就像在Admin中一样).

但是,我是从头开始创建自己的表单,并想知道如何自己处理这个问题?我认为我最大的绊脚石是知道在清理数据时哪个对象附加到表单上...

一些代码:

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

tgh*_*ghw 16

独特的验证很难完全正确,所以我建议使用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',)
Run Code Online (Sandbox Code Playgroud)

从多个模型制作表单并不容易,但在这种情况下,您只需将notifications字段添加到ModelForm中并将其拉出.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.
Run Code Online (Sandbox Code Playgroud)

我就是这样做的,但是如果你打算自己验证独特,那么你可以做以下事情:

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

我在这里看到两个问题.首先,您可能会遇到并发问题,其中两个人提交相同的名称,两者都通过唯一检查,但之后会出现数据库错误.另一个问题是您无法编辑用户配置文件,因为您没有要从搜索中排除的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
Run Code Online (Sandbox Code Playgroud)

但那时你只是复制ModelForms中的逻辑.