James CookeJames Cooke

Django’s model save vs full_clean

Screwing up data

At a previous talk I gave on “Things I wish I’d known about Django” there was this slide:

What?

I’ve made some experimental code in a small Django clean vs save project. It has a few models and a single test file which is readable and passes.

The main take-aways are:

  • Creating an instance of a Model and calling save on that instance does not call full_clean. Therefore it’s possible for invalid data to enter your database if you don’t manually call the full_clean function before saving.
  • Object managers’ default create function also doesn’t call full_clean.

Personally I find this jarring.

Given that the developer is the customer of Django I think it conflicts with the principle of least astonishment.

Why is it like this?

The Django documentation of Validating Objects is quoted in Django ticket #13100 as saying:

Note that full_clean() will NOT be called automatically when you call your model’s save() method. You’ll need to call it manually if you want to run model validation outside of a ModelForm. (This is for backwards compatibility.)

Ahh “backwards compatibility”?!

It appears that phrase only lived for four months back in 2010.

I haven’t been able to find any more specific reasons that it was added or removed.

What next?

More warnings I guess:

  • Consider if you ever want to be able to call save without full_clean. If the answer is ‘no’, then explore how you’ll wrap your models in business logic or extend them in some way that implements this (with tests of course). A quick search of the internet will show you some Django plugins that adjust this behaviour.
  • Remember that you can ruin your database when migrating if you don’t call full_clean after the migration has changed a model but before saving. All migrations should be tested to ensure that they can roll-back if full_clean raises a ValidationError during migration.
  • Check out posts like Why I sort of dislike Django. It mentions things like backwards compatibility and the save function.

Finally

Thanks to PXG for sharing the “Why I sort of dislike Django” post and discussing Django project structures with me.

Thanks for reading!