In Django's CreateView and UpdateView, what is the difference between setting fields and setting form_class, and may you set both?
answer
- who builds the form
- modelform_factory on the fly
- both set is a configuration error
- neither set is also an error
basics
~20 sSetting 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 linesfrom 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 fieldsgo deeper
Remember the rule: one of fields or form_class on CreateView and UpdateView, never both, never neither. FormView only takes form_class.
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.
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.
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