Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

In a custom Form, how does one validate a Model's field's uniqueness (i.e., has unique=True set)?

I know that django's ModelForm automatically performs a validate_unique() function that is called within the BaseModelForm's clean() method -- so, when using ModelForm, this will be handled correctly (as it is in the Admin).

However, I am creating my own form from scratch and wonder how I can go about handling this myself? I think my biggest stumbling block is knowing which object is attached to the form when the data is being cleaned ...

Some code:

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
like image 283
thornomad Avatar asked Oct 12 '09 23:10

thornomad


People also ask

What can be used to validate all model fields if any Field is to be exempted from validation provide it in the exclude parameter?

clean_fields() method documentation: This method will validate all fields on your model. The optional exclude argument lets you provide a list of field names to exclude from validation. It will raise a ValidationError if any fields fail validation.

How do you exclude a specific Field from a ModelForm?

Set the exclude attribute of the ModelForm 's inner Meta class to a list of fields to be excluded from the form.

How can you validate Django model fields?

to_python() method of the models. Field subclass (obviously for that to work you must write custom fields). Possible use cases: when it is absolutely neccessary to ensure, that an empty string doesn't get written into the database (blank=False keyword argument doesn't work here, it is for form validation only)

Which method has a form instance which runs validation routines for all its fields?

The run_validators() method on a Field runs all of the field's validators and aggregates all the errors into a single ValidationError .


1 Answers

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",
                        required=False,
                        widget=forms.CheckboxSelectMultiple,)

    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.

like image 157
tghw Avatar answered Oct 11 '22 14:10

tghw