[Fixed]-How to validate/clean() a unique=True field without using a ModelForm?


Unique validation is hard to get completely right, so I would recommend using a ModelForm anyways:

class EditUserProfileForm(forms.ModelForm):
    # "notifications" are created from a different model, not the UserProfile
    notifications    = forms.MultipleChoiceField(
                        label="Email Notifications",

    class Meta:
        model = UserProfile
        fields = ('display_name',)

Making a form from multiple models is not easy, but in this case you can just add the notifications field onto the ModelForm and pull it out of .cleaned_data as usual:

# 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.

That’s how I would do it, but if you’re set on validating unique yourself, you can always do something like:

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

There are two problems I see here. First, you can run into concurrency issues, where two people submit the same name, both pass unique checks, but then one gets a DB error. The other problem is that you can’t edit a user profile because you don’t have an ID to exclude from the search. You’d have to store it in your __init__ and then use it in the cleaning:

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

But at that point you’re just duplicating the logic in ModelForms.



Working Example !

I am using email as unique field

In models.py use below code

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

In forms.py use below code

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')

Leave a comment