skip to content

ModelForm Bindings

ModelForm builds fields from a model via Meta.fields, saves with save(commit=False) plus save_m2m(), and runs model validation in _post_clean. Interviewers probe why '__all__' invites mass assignment.

part ofDjangooverview, primer and where to startread it →
on this pageshow

explore

questions

6

In Django, what does a ModelForm build from its model, and why must its Meta declare fields or exclude?

level: juniorimportance: must knowfreq 70%

answer

  1. one form field per model field
  2. blank decides required
  3. relations become queryset choices
  4. an explicit list is mandatory
  5. Meta options touch generated fields only

basics

~20 s

A Django ModelForm generates a form field for each selected editable model field, mapping type, max_length, blank, choices, labels and help text, and its save() writes the instance. Meta must name fields or exclude, or class creation raises ImproperlyConfigured.

solid answer

~40 s

A `ModelForm` reads its `Meta.model` and creates form fields for the model fields you select: a `CharField` keeps its `max_length`, a `TextField` becomes a `CharField` with a `Textarea`, a `ForeignKey` becomes a `ModelChoiceField` and a `ManyToManyField` a `ModelMultipleChoiceField`. `blank=True` makes the form field optional, `verbose_name` becomes the label, `help_text` carries over, and fields with `editable=False` or auto primary keys are left out. `save()` then creates or updates the model instance. Since Django requires an explicit choice, a `Meta` with neither `fields` nor `exclude` raises `ImproperlyConfigured` when the class is defined, so nobody exposes every column by accident. `Meta.widgets`, `labels`, `help_texts` and `error_messages` customise the generated fields; a field you declare yourself on the form replaces the generated one and ignores those options.

code

python · 22 lines
python
from django.conf import settings
from django.db import models

class AdoptionApplication(models.Model):
    class Status(models.TextChoices):
        PENDING = 'pending', 'Pending'
        APPROVED = 'approved', 'Approved'
        REJECTED = 'rejected', 'Rejected'

    applicant = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    pet = models.ForeignKey('Pet', on_delete=models.CASCADE)
    home_type = models.CharField(max_length=20, choices=[('house', 'House'), ('flat', 'Flat')])
    has_other_pets = models.BooleanField(default=False)
    message = models.TextField(blank=True)
    preferred_traits = models.ManyToManyField('Trait', blank=True)
    status = models.CharField(max_length=10, choices=Status.choices, default=Status.PENDING)
    staff_notes = models.TextField(blank=True)

    class Meta:
        constraints = [
            models.UniqueConstraint(fields=['applicant', 'pet'], name='one_application_per_pet'),
        ]

go deeper

for a junior

Recall that a ModelForm generates fields from Meta.model, that blank=True makes a field optional, and that Meta needs fields or exclude.

for a middle

Explain the field mapping for relations and TextField, how Meta.widgets and labels customise generated fields, and why declared fields ignore them.

for a senior

Show you default to explicit fields lists, know editable=False as a hard stop, and review ModelForms for fields users should not control.

for a principal

Set a team rule for when 'all' is ever acceptable and how forms stay in step as models gain columns.

## What a ModelForm is A **`ModelForm`** is an ordinary Django `Form` whose fields are generated from a model, plus a `save()` method that writes the result back. For a pet-adoption site, one class can turn an `AdoptionApplication` model into the application form applicants fill in. ```python from django import forms from .models import AdoptionApplication class AdoptionApplicationForm(forms.ModelForm): class Meta: model = AdoptionApplication fields = ['pet', 'home_type', 'has_other_pets', 'message', 'preferred_traits'] labels = {'message': 'Why is this pet a good fit?'} widgets = {'message': forms.Textarea(attrs={'rows': 4})} ``` ## How model fields become form fields | Model field | Generated form field | |---|---| | `CharField(max_length=20)` | `CharField(max_length=20)` | | `TextField` | `CharField` with a `Textarea` widget | | `BooleanField` | `BooleanField` | | `ForeignKey` | `ModelChoiceField` over the related model's rows | | `ManyToManyField` | `ModelMultipleChoiceField` | | field with `choices` | a select built from those choices | | auto primary key, `editable=False` | not included | Every generated field also inherits attributes: - **`required`** is False when the model field has `blank=True`, otherwise True. - **`label`** comes from `verbose_name`, first letter capitalised. - **`help_text`** is copied from the model field. - Length limits carry over, so the form rejects what the column could not hold. ## Why fields or exclude is mandatory If `Meta` sets `model` but neither `fields` nor `exclude`, Django raises `ImproperlyConfigured` as soon as the class is defined, with a message saying that creating a ModelForm without either attribute is prohibited. The rule exists because silently including every column is how mass-assignment bugs happen. You choose one of three spellings: 1. `fields = [...]`: an explicit allowlist, the recommended default. 2. `fields = '__all__'`: every editable field, a shortcut for trusted contexts. 3. `exclude = [...]`: everything except a denylist. A typo such as `fields = ('pet')`, a string rather than a tuple, raises `TypeError` with a hint to add the comma. ## Customising generated fields through Meta - **`widgets`**, **`labels`**, **`help_texts`**, **`error_messages`** and **`field_classes`** map field names to overrides. - These apply only to **generated** fields. If you declare `message = forms.CharField(...)` on the form yourself, Django uses your field as-is and ignores the `Meta` entries for it, and your declared field no longer inherits the model's `max_length` or `blank` automatically. - Field order follows `fields` when it is a list; with `'__all__'` or `exclude`, it follows the model, with many-to-many fields last. ## Building a ModelForm at runtime `django.forms.modelform_factory(model, fields=[...])` creates a `ModelForm` class on the fly, which is handy in generic code. The same safety rule applies: calling it without `fields` or `exclude` raises `ImproperlyConfigured`. Other `Meta` options worth recognising: - **`localized_fields`**: fields whose input and display follow the active locale. - **`field_classes`**: swap the generated form field class, for example a custom field for a slug. - **`formfield_callback`**: a function that decides the form field for each model field, for project-wide conventions. ## What save() gives you `form.save()` builds or updates the instance from `cleaned_data`, saves it, and saves many-to-many selections. With `instance=` it updates an existing row; without it, it creates one. Calling `save()` on an invalid form raises `ValueError`. ## What interviewers listen for - The mapping rules, especially `blank` to `required` and relations to model choice fields. - The mandatory `fields`/`exclude` rule and the security reason behind it. - That `Meta` customisations never reach declared fields.

  • In a Django ModelForm, why does a Meta.widgets entry have no effect on a field you declared on the form?
    `ModelForm` generates only the fields you have not declared. A declared field is used exactly as written, so `Meta.widgets`, `labels`, `help_texts` and `error_messages` never touch it; set those options on the declared field itself.
  • Which model fields can never appear on a Django ModelForm, whatever Meta says?
    Fields with `editable=False` and auto-created primary keys. The generator skips non-editable fields, and `construct_instance()` also skips them when copying data back, so a value for them in the POST is ignored.

saying these in an interview costs you the question

  • A ModelForm without fields or exclude simply includes every model field.
  • blank=True on the model has no effect on the generated form field.
  • A ForeignKey becomes a plain integer input for the related id.
  • Meta.widgets also applies to fields declared directly on the form.
  • ModelForm validation ignores the model field's max_length.
open as a page

In a Django ModelForm, what does save(commit=False) return, and why must you call save_m2m() afterwards?

level: middleimportance: must knowfreq 66%

basics

~10 s

Django's ModelForm.save(commit=False) returns the model instance populated from cleaned_data but not saved. After setting extra fields and calling instance.save(), you call form.save_m2m(), because many-to-many rows need the saved instance's primary key.

open as a page

When editing an adoption application with a Django ModelForm, why must both the GET and the POST branches pass instance=?

level: middleimportance: should knowfreq 50%

basics

~20 s

In a Django ModelForm, instance= supplies the initial values on GET and tells save() which row to update on POST. A POST form built without instance= starts from a new object, so save() inserts a duplicate row instead of updating.

open as a page

In a Django ModelForm, what validation runs after the form's clean(), and why must an overridden clean() call super().clean()?

level: middleimportance: should knowfreq 44%

basics

~20 s

After a Django ModelForm's clean(), _post_clean() copies cleaned data onto the instance, runs the model's field validation and clean(), then validate_unique() and validate_constraints(). Those last two only run if ModelForm.clean() ran, so overrides must call super().clean().

open as a page

Why is fields = '__all__' on a Django ModelForm for a public pet-adoption application a mass-assignment risk?

level: seniorimportance: should knowfreq 55%

basics

~20 s

With fields = 'all' a Django ModelForm accepts and saves every editable field, including staff-only ones like status, and any column added later. Hiding inputs in the template does not help; a crafted POST sets them.

open as a page

Why can a Django ModelForm pass is_valid() yet saving raise IntegrityError when the view sets applicant after save(commit=False)?

level: seniorimportance: should knowfreq 40%

basics

~20 s

A Django ModelForm excludes fields that are not on the form from model validation, and skips any unique check or UniqueConstraint that involves one. A constraint on (applicant, pet) is never checked, so the duplicate reaches the database.

open as a page