skip to content

In Django's CreateView and UpdateView, what is the difference between setting fields and setting form_class, and may you set both?

level: juniorimportance: should knowfreq 52%

answer

  1. who builds the form
  2. modelform_factory on the fly
  3. both set is a configuration error
  4. neither set is also an error

basics

~20 s

Setting fields makes the view generate a ModelForm for its model with modelform_factory; form_class hands it a form you wrote yourself. Setting both raises ImproperlyConfigured, and so does setting neither on a CreateView or UpdateView.

solid answer

~30 s

`CreateView` and `UpdateView` get their form from `ModelFormMixin.get_form_class()`. If `form_class` is set, that class is used as is. Otherwise the view works out the model (from `model`, then `self.object`, then `get_queryset().model`) and calls `modelform_factory(model, fields=self.fields)`. Declaring both raises `ImproperlyConfigured` ("Specifying both 'fields' and 'form_class' is not permitted"), and declaring neither raises it too, because Django refuses to guess which fields are safe to edit. Use `fields` for a quick form over a few columns; switch to `form_class` when you need custom widgets, extra non-model fields, cross-field `clean()` or a constructor argument. `FormView` has no `fields` at all: it only takes `form_class`.

code

python · 14 lines
python
from django.views.generic.edit import CreateView, UpdateView

from .forms import ProposalForm
from .models import Proposal


class ProposalCreateView(CreateView):
    model = Proposal
    fields = ['title', 'abstract', 'track']  # Django builds the ModelForm


class ProposalUpdateView(UpdateView):
    model = Proposal
    form_class = ProposalForm  # your own ModelForm; do not also set fields

go deeper

for a junior

Remember the rule: one of fields or form_class on CreateView and UpdateView, never both, never neither. FormView only takes form_class.

for a middle

Explain get_form_class(): form_class wins if set, otherwise the model is resolved and modelform_factory builds a form from fields, with ImproperlyConfigured guarding both misconfigurations.

for a senior

Argue for the explicit allow-list: a generated form with every field turns a new column into a mass-assignment hole. Say when you move from fields to a dedicated form class.

for a principal

Frame a team convention: fields for throwaway internal pages, a form_class in forms.py for anything user-facing, so validation and widgets are reviewable and reusable outside the view.

## Where an editing view gets its form Django's editing generics are built from mixins. `FormView` combines `FormMixin` (form handling) with `ProcessFormView` (GET renders, POST validates). `CreateView` and `UpdateView` swap `FormMixin` for its subclass **`ModelFormMixin`**, which adds `SingleObjectMixin` so the view knows about a **model** and a current **object**. The form class is chosen by one hook, `get_form_class()`, and `get_form()` then instantiates it with `get_form_kwargs()`. ## The two ways to declare the form | Attribute | Who writes the form | Typical use | |---|---|---| | `fields = ['title', 'abstract', 'track']` | Django, via `modelform_factory()` at request time | A plain edit page over a few model columns | | `form_class = ProposalForm` | You, as a `ModelForm` (or plain `Form` on `FormView`) | Custom widgets, extra fields, cross-field rules, constructor arguments | In `ModelFormMixin.get_form_class()` the logic is, in order: 1. If **both** `fields` and `form_class` are set, raise `ImproperlyConfigured` with "Specifying both 'fields' and 'form_class' is not permitted." 2. If `form_class` is set, return it unchanged. 3. Otherwise resolve the model: the `model` attribute, else the class of `self.object`, else `self.get_queryset().model`. 4. If `fields` is still `None`, raise `ImproperlyConfigured` ("Using ModelFormMixin ... without the 'fields' attribute is prohibited"). 5. Return `modelform_factory(model, fields=self.fields)`. ## Why Django refuses to guess Step 4 is deliberate. Early Django versions allowed a generated form to include every model field, which meant adding a column such as `is_accepted` to a talk proposal silently made it editable by the speaker. The explicit `fields` list is an allow-list: a new column is invisible to the form until someone adds it. That is also why setting both attributes is an error rather than a merge: Django will not decide whether `fields` should filter your custom form or be ignored. ## FormView is different `FormView` uses plain `FormMixin`, which has **no `fields` attribute** and no model. Its `get_form_class()` simply returns `form_class`. Forget it and the view does not raise `ImproperlyConfigured`; it tries to call `None(...)` and fails with a `TypeError`. So: - `FormView` needs `form_class` (a `Form` or `ModelForm`) and a `success_url`. - `CreateView` / `UpdateView` need exactly one of `fields` or `form_class`. - `DeleteView` also has a `form_class`, defaulting to an empty `django.forms.Form` (a separate question). ## Choosing between them Start with `fields` when the page edits a handful of columns with default widgets. Move to `form_class` as soon as you need any of the following: - a widget or label that differs from the model default; - a field that is not on the model, such as a "confirm co-speaker consent" checkbox; - a `clean()` that compares two fields; - an extra constructor argument, such as the current user, passed through `get_form_kwargs()`. Keeping the form in `forms.py` also lets you reuse it in the admin or a function view and test it without a request. What the generated `ModelForm` actually contains, and how its validation runs, belongs to the forms topics rather than to the view.

  • If a view sets form_class but no model, how does CreateView still know which model it saves?
    It does not need to: the `ModelForm` in `form_class` carries its own `Meta.model`, and `form_valid()` just calls `form.save()`. The view only resolves a model itself when it has to generate the form from `fields`, or when `UpdateView` needs `get_object()`, which uses `model` or `queryset`.
  • Can fields = '__all__' be used on a CreateView?
    Yes, `modelform_factory` accepts `'__all__'`, so the view will build a form over every editable field. It defeats the allow-list, though: any column added later, such as a review status, becomes user-editable without anyone touching the view.

saying these in an interview costs you the question

  • Setting both fields and form_class lets fields filter the custom form
  • Omitting fields makes CreateView include every model field by default
  • FormView accepts a fields list just like CreateView
  • form_class on a CreateView must be a plain Form, not a ModelForm