Search code examples
djangodjango-modelsdjango-testing

Django: Stop model instance creation on ValidationError


I have defined a model and customized the clean() Method for better validation. If I use the model in frontend, it works and I can not save a model that does not fulfil my validation criteria. But when I save via the shell or write my tests a wrong model will still be saved.

models.py

class FooModel(models.Model):
    weight_min = models.DecimalField(default=40.0, max_digits=4, decimal_places=1)
    weight_max = models.DecimalField(default=40.0, max_digits=4, decimal_places=1)

    def clean(self):
        if self.weight_min > self.weight_max:
             raise ValidationError("'Weight min' must be smaller than 'Weight max'.")

tests.py

def test_create_model_with_wrong_weight(self):
        foo = FooModel(weight_min=40.0, weight_max=30.0)

        self.assertRaises(ValidationError, match.save()) # Works, but still saves the model 
        self.assertIs(0, Match.objects.all()) # Fails, QuerySet has objects.

I read the docs and tried to call full_clean() in save() but then I dont know how to write the test.

What do I have to do to:

  • Raise the ValidationError
  • Prevent Saving the wrong Model Instance

Solution

  • the save method is not supposed to do validation, it doesn't do it by design, therefore you are able to save even the non valid model instance.

    Your workflow can look like the following:

    try:
        match.full_clean()
        match.save()
    except ValidationError as e:
        # Do something based on the errors contained in e.message_dict.
        # Display them to a user, or handle them programmatically.
    

    https://docs.djangoproject.com/en/2.0/ref/models/instances/#validating-objects

    Adding validation (i.e. .full_clean() call) to the model's .save() method is often not a good idea, because if you will later work with this model through a form, the .full_clean() will be called twice (by the form, by the .save() method), but if you don't mind this you can do it like the following:

    class Match:
        ...
        def save(self, *args, **kwargs):
            try:
                self.full_clean()
                super().save(*args, **kwargs)  # actually save the valid match
            except ValidationError as e:
                ...  # work with e.message_dict etc.