In reply to my desire for read-only form fields, a commentator on Reddit wrote (in part):

You don't really save much code by having read only inputs. In the example used (inputs only editable if blank) you would still need server side checking that these values are not modified. [...]

This is incorrect (at least for a competent implementation of read only fields). As a modern framework, Django is smart about this so it never takes the network's word for what's supposed to be in a form.

I'm sure that there are some frameworks that read the submitted form fields from the HTTP POST response and put them all into your model instance (or the object holding form data). Django does not work like that. The only things that will appear in validated form data (and will be saved in model instances) are form fields (and field values) that you told Django to expect. It doesn't matter if your model has a field and the HTTP POST tries to set the field; Django will not pass it through if your form does not include the field (and it's a writeable field, once you can have read-only fields).

You do not have to write any extra code or make any extra checks in your forms processing to do this. It is all handled for you by the Django forms code, the way you want it to be, and everything just works right. All you have to do is not touch anything that Django says doesn't validate (and not read raw POST field data; always use the cleaned data).

Since this is such an obvious and important security-related thing, I expect that all modern web frameworks behave this way. We are no longer in the days when your PHP code slops the form POST data straight into the database; everyone knows better by now.

By the way, in a typical Django application that is generating dynamic forms (with fields sometimes included or excluded), the form that matters for what gets accepted in a HTTP POST is the Django form that is generated when the POST is processed, not the form that was generated when the HTML page and form were generated and shown to the user (which may have happened some time ago if the user sat there for a while). It's possible for these two versions of the form to differ from each other if things have changed in the mean time.

(It's also possible for the underlying record data to have changed, of course, but this is a general issue with all web applications.)