Most forms edit model instances; ModelForm removes the duplication between model and form.
A `ModelForm` builds its fields from a model:
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ["title", "slug", "body", "tags"]Field types, max_length, choices, blank and help text come from the model. Validation runs both the form's checks and the model's (full_clean), including unique constraints.
Saving:
form.save()inserts a new row or updates the instance you passed in- edit an existing object with
PostForm(request.POST, instance=post) form.save(commit=False)returns an unsaved instance so you can set fields that aren't in the form, likeobj.author = request.user, then callobj.save(). Withcommit=Falseand many-to-many fields, callform.save_m2m()afterwards.
List fields explicitly. fields = "__all__" or exclude = [...] means any new model field automatically becomes editable by users, a mass-assignment vulnerability waiting to happen (is_staff, price, owner...).
Customize with widgets = {"body": forms.Textarea(attrs={"rows": 10})}, labels, help_texts, or by overriding a field declaratively on the form class. Add clean_<field> methods as with normal forms.
Generic views CreateView and UpdateView use ModelForms automatically, which is why they need only model and fields to work.
Example
# blog/forms.py
from django import forms
from .models import Post
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ["title", "slug", "body", "tags"] # explicit, never "__all__"
widgets = {"body": forms.Textarea(attrs={"rows": 12})}
# blog/views.py
from django.contrib.auth.decorators import login_required
from django.shortcuts import get_object_or_404, redirect, render
@login_required
def post_edit(request, pk=None):
post = get_object_or_404(Post, pk=pk) if pk else None
form = PostForm(request.POST or None, instance=post)
if request.method == "POST" and form.is_valid():
obj = form.save(commit=False)
obj.author = request.user.author # field not in the form
obj.save()
form.save_m2m() # needed after commit=False
return redirect("blog:detail", pk=obj.pk)
return render(request, "blog/post_form.html", {"form": form})Remember
- ModelForm derives fields and validation from the model
- instance= edits an existing row; save() creates or updates
- save(commit=False) lets you set extra fields; then save_m2m()
- Always list fields explicitly
Watch out for
- Using fields = "__all__", exposing sensitive fields added later.