63

I have this model I'm showing in the admin page:

class Dog(models.Model):
    bark_volume = models.DecimalField(...
    unladen_speed = models.DecimalField(...

    def clean(self):
        if self.bark_volume < 5:
            raise ValidationError("must be louder!")

As you can see I put a validation on the model. But what I want to happen is for the admin page to show the error next to the bark_volume field instead of a general error like it is now. Is there a way to specify which field the validation is failing on?

Much thanks in advance.

Mike DeSimone
  • 41,631
  • 10
  • 72
  • 96
Greg
  • 45,306
  • 89
  • 231
  • 297

6 Answers6

126

OK, I figured it out from this answer.

You have to do something like this:

class Dog(models.Model):
    bark_volume = models.DecimalField(...
    unladen_speed = models.DecimalField(...

    def clean_fields(self):
        if self.bark_volume < 5:
            raise ValidationError({'bark_volume': ["Must be louder!",]})
Community
  • 1
  • 1
Greg
  • 45,306
  • 89
  • 231
  • 297
  • 1
    If translation is not needed, using string for message is enough, list is not required: `raise ValidationError({'bark_volume': 'Must be louder!'})`. Using Django 1.9.5. – arogachev Apr 29 '16 at 06:52
13
class Dog(models.Model):
    bark_volume = models.DecimalField(...
    unladen_speed = models.DecimalField(...

    def clean(self):
        if self.bark_volume < 5:
            if not self._errors.has_key('bark_volume'):
                from django.forms.util import ErrorList
                self._errors['bark_volume'] = ErrorList()
            self._errors['bark_volume'].append('must be louder!')

That works on forms, at least. Never tried it on the model itself, but the methodology should be the same. However, from the Django docs:

When you use a ModelForm, the call to is_valid() will perform these validation steps for all the fields that are included on the form. (See the ModelForm documentation for more information.) You should only need to call a model’s full_clean() method if you plan to handle validation errors yourself, or if you have excluded fields from the ModelForm that require validation.

And...

Note that full_clean() will not be called automatically when you call your model’s save() method, nor as a result of ModelForm validation. You’ll need to call it manually when you want to run model validation outside of a ModelForm.

So, basically, unless you have a really good reason to do field cleaning on the model, you should do it on the form instead. The code for that would look like:

class DogForm(forms.ModelForm):

    def clean(self):
        bark_volume = self.cleaned_data.get('bark_volume')
        if bark_volume < 5:
            if not self._errors.has_key('bark_volume'):
                from django.forms.util import ErrorList
                self._errors['bark_volume'] = ErrorList()
            self._errors['bark_volume'].append('must be louder!')

        return self.cleaned_data

And that will work, for sure.

Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
  • Do you also need to raise a ValidationError? or does Django check for self._errors after the clean method is called? – Ted Jun 13 '11 at 18:35
  • 2
    Actually raising ValidationError merely adds items to self._errors. It is the master list of all errors on the form. – Chris Pratt Jun 13 '11 at 19:05
  • Using it in the model gives me this error: 'Dog' object has no attribute '_errors' – Greg Jun 14 '11 at 16:40
  • Okay, then it would appear that you can't use the same methodology. I would suggest doing the validation on your form if it involves multiple fields. That method will work with forms. – Chris Pratt Jun 15 '11 at 14:17
  • "nor as a result of ModelForm validation." This is not true, or at least no longer true. `self.instance.instance.full_clean()` is called in the `ModelForm._post_clean` method, which is called in `ModelForm.is_valid`. This quote has also been removed from the docs. – Lucas Wiman Mar 13 '15 at 23:13
10

To note for anyone that may come across this with a newer version of Django - the clean_fields method from the accepted answer now requires an "exclude" param. Also - I believe the accepted answer is also missing a call to it's super function. The final code that I used was:

def clean_fields(self, exclude=None):
    super(Model, self).clean_fields(exclude)

    if self.field_name and not self.field_name_required:
        raise ValidationError({'field_name_required':["You selected a field, so field_name_required is required"]})
streetlogics
  • 4,640
  • 33
  • 33
  • I think that `exclude` parameter should be respected by the validation code, so a call like `clean_fileds(model, exclude=["field_name"])` doesn't raise exception even when `field_name` is required. – firegurafiku Jul 21 '16 at 00:32
7

abbreviated, from the django docs:

def clean(self):
    data = self.cleaned_data
    subject = data.get("subject")

    if subject and "help" not in subject:
        msg = "Must put 'help' in subject."
        self.add_error('subject', msg)

    return data
Junshoong
  • 169
  • 1
  • 2
  • 12
Chase Finch
  • 5,161
  • 1
  • 21
  • 20
6

The simplest way to validate this particular case would be:

from django.core.validators import MinValueValidator
from django.utils.translation import ugettext_lazy as _

class Dog(models.Model):
    bark_volume = models.DecimalField(
        ..., validators=[MinValueValidator(5, message=_("Must be louder!"))]

Django's documentation about validators: https://docs.djangoproject.com/en/dev/ref/validators/

Monika Sulik
  • 16,498
  • 15
  • 50
  • 52
6

Use a clean_ method that is specific to the field:

class DogForm(forms.ModelForm):
    class Meta:
        model = Dog

    def clean_bark_volume(self):
        if self.cleaned_data['bark_volume'] < 5:
            raise ValidationError("must be louder!")

See the clean<fieldname> part of the Form Validation page. Also, make sure to use cleaned_data instead of the form field itself; the latter may have old data. Finally, do this on the form and not the model.

Mike DeSimone
  • 41,631
  • 10
  • 72
  • 96
  • First, he was using the `clean` method on the *model* not a form. So, there is no `cleaned_data` in that scenario. Second, `clean_FOO` is only applicable if you're only validating against that one specific field. If there's multiple fields involved you *have* to use `clean` – Chris Pratt Jun 13 '11 at 19:07
  • 2
    There can be more than one `clean_` method; each will be checked in turn and their errors will be attached to the relevant fields. – Mike DeSimone Jun 13 '11 at 20:56
  • 1
    What's the relevance of that? You can't validate multiple fields in one clean_ method. There's no guarantee to the availability of the cleaned_data for one field inside the clean method of another. If your validation involves multiple fields you *must* use `clean` instead. – Chris Pratt Jun 14 '11 at 14:25