You must not have read the section "Validation defined on the model is only enforced on the form". The validations I referenced, e.g. `choices`, are only run in when [`full_clean()`](https://docs.djangoproject.com/en/dev/ref/models/instances/?...) is called, and it's not called on `save()`--it is usually called through form validation.
Regarding computed columns, it looks like the specific issue I ran into repeatedly was [fixed in 1.8](https://docs.djangoproject.com/en/2.0/releases/1.8/#query-ex...). I should have been explicit that I was referring to not being able to define such computed columns on the model the way one would a normal column, not to being unable to use `F`-expressions.
> You must not have read the section "Validation defined on the model is only enforced on the form". The validations I referenced, e.g. `choices`, are only run in when [`full_clean()`](https://docs.djangoproject.com/en/dev/ref/models/instances/?...) is called, and it's not called on `save()`--it is usually called through form validation.
In addition to being wrong about whether I read your post, this is a great illustration of what I found tedious while reading it: since that feature was released in Django 1.2, the documentation has been clear about the relationship between save() and full_clean():
The direct statement of fact you used as a heading simply hadn't been true for years before you wrote your post and it's not helped by little mocking asides such as “Because nobody ever modifies a model instance expect through its form, right?” which are basically restating the reasons why that feature exists in the first place.
> I should have been explicit that I was referring to not being able to define such computed columns on the model the way one would a normal column, not to being unable to use `F`-expressions.
Strong agreement here. Your post could have been so much more useful if each of the mocking asides had instead been taken as a cue to ask whether it's more likely that so many people have worked on Django for years without noticing such a major flaw or that your understanding of how it was intended to be used is incorrect.
Imagine if your post had been something like “Things I learned about Django the hard way” and had covered things like picking safer defaults or updating the docs to more explicitly suggest how validation is intended to work (especially in this age where people use fewer forms and more APIs than a decade ago), or that Django treats the database as the ultimate source of truth so you should approach various things about validation and transactions with that in mind (e.g. strengthening the the wording to make it clear that e.g. get_or_create relies on database integrity checks so you should either set those or religiously use some other strategy). That post could be a DjangoCon talk and it'd get a lot more positive reaction than another angry rant on an internet full of them, especially since people in the community would be likely to share it rather than seeing a big claim which is not correct and closing the tab.
Back when I was working on Django codebases, I saw almost no use of `full_clean()`, largely because some validation was performed on `save()` (by virtue of being database constraints, e.g. non-nullability) while other validation was not, and there was no indication in the APIs which was which.
In a world in which Django applications were always written by people who read and understood the docs in full, I would totally agree with you about these criticisms. But I've never had such an opportunity outside of my own projects. I've only dealt with codebases that were written with falty assumptions, enabled by Django's choice to hide some but not all tricky work from its users. By then the database constraints would be incredibly painful to add since there was lots of data already violating it, and switching global defaults like ATOMIC_SAVE were nearly as bad since code had widely relied on other behavior.
Regarding computed columns, it looks like the specific issue I ran into repeatedly was [fixed in 1.8](https://docs.djangoproject.com/en/2.0/releases/1.8/#query-ex...). I should have been explicit that I was referring to not being able to define such computed columns on the model the way one would a normal column, not to being unable to use `F`-expressions.