ModelForms automatically generate form fields from your models, reducing duplication and keeping your forms in sync with your data structure.
Creating a ModelForm
A ModelForm starts with the model it mirrors. Define the model first, then create a form class that references it through an inner Meta class.
python# polls/models.py from django.db import models class Question(models.Model): question_text = models.CharField(max_length=200) pub_date = models.DateTimeField('date published') is_active = models.BooleanField(default=True)
python# polls/forms.py from django import forms from .models import Question class QuestionForm(forms.ModelForm): class Meta: model = Question fields = ['question_text', 'pub_date', 'is_active']
Meta Class Options
The Meta class on a ModelForm gives you fine-grained control over which fields to include, how they appear, and what error messages to display.
pythonclass QuestionForm(forms.ModelForm): class Meta: model = Question # Include specific fields fields = ['question_text', 'pub_date'] # Or exclude specific fields # exclude = ['is_active'] # Or include all fields (not recommended) # fields = '__all__' # Custom labels labels = { 'question_text': 'Your Question', 'pub_date': 'Publication Date', } # Custom help text help_texts = { 'question_text': 'Enter your question here.', } # Custom widgets widgets = { 'question_text': forms.TextInput(attrs={ 'class': 'form-control', 'placeholder': 'Enter your question' }), 'pub_date': forms.DateTimeInput(attrs={ 'type': 'datetime-local' }), } # Custom error messages error_messages = { 'question_text': { 'required': 'Please enter a question.', 'max_length': 'Question is too long.', }, }
Saving ModelForms
ModelForms can save directly to the database:
python# polls/views.py def create_question(request): if request.method == 'POST': form = QuestionForm(request.POST) if form.is_valid(): question = form.save() # Creates and saves the object return redirect('polls:detail', pk=question.pk) else: form = QuestionForm() return render(request, 'polls/create.html', {'form': form})
Delayed Save
Modify the object before saving:
pythonif form.is_valid(): question = form.save(commit=False) # Don't save yet question.author = request.user # Add extra data question.save() # Now save
Editing Existing Objects
Pass an instance to edit:
pythondef edit_question(request, pk): question = get_object_or_404(Question, pk=pk) if request.method == 'POST': form = QuestionForm(request.POST, instance=question) if form.is_valid(): form.save() return redirect('polls:detail', pk=pk) else: form = QuestionForm(instance=question) # Pre-filled form return render(request, 'polls/edit.html', {'form': form})
Adding Custom Validation
ModelForms support the same validation hooks as regular forms. Use clean_fieldname() for single-field validation and clean() for cross-field rules.
pythonclass QuestionForm(forms.ModelForm): class Meta: model = Question fields = ['question_text', 'pub_date'] def clean_question_text(self): """Validate the question_text field.""" text = self.cleaned_data['question_text'] if not text.endswith('?'): raise forms.ValidationError('Questions must end with a question mark.') return text def clean(self): """Cross-field validation.""" cleaned_data = super().clean() pub_date = cleaned_data.get('pub_date') if pub_date and pub_date > timezone.now(): raise forms.ValidationError('Publication date cannot be in the future.') return cleaned_data
Common Pitfalls
- Using
fields = '__all__'in ModelForm: This exposes every model field, including sensitive ones. Always list fields explicitly for security. - Forgetting
commit=Falsewhen setting extra fields: If your view needs to add data (likeauthor = request.user) before saving, useform.save(commit=False)first. - Not calling
form.save_m2m(): When usingcommit=Falseon a ModelForm with ManyToManyField, you must callform.save_m2m()after saving the instance.
Best Practices
- List fields explicitly in the
Meta.fieldsattribute rather than using'__all__'orexclude. - Use the
widgetsdictionary in Meta to customize HTML attributes like CSS classes and input types. - Add custom validation with
clean_fieldname()methods for field-level validation orclean()for cross-field validation.
Summary
- ModelForms auto-generate form fields from Django model definitions, reducing duplication
- Configure fields, labels, widgets, and error messages in the inner
Metaclass - Use
form.save()to create/update database records directly from form data - Use
form.save(commit=False)to modify the instance before saving - Add custom validation with
clean_fieldname()andclean()methods
Code Examples
python
from django import forms
from .models import Question
class QuestionForm(forms.ModelForm):
class Meta:
model = Question
fields = ['question_text', 'pub_date']
widgets = {
'question_text': forms.TextInput(attrs={'class': 'form-control'}),
'pub_date': forms.DateTimeInput(attrs={'type': 'datetime-local'}),
}