Django's forms handle HTML form rendering, validation, and data processing. You can create two types.
Forms - manually define fields (for custom inputs).
ModelForms - automatically generate a form from a model.
Django forms integrate with views and templates to simplify capturing user input, validating it, and saving it
to the database.
Utilising Forms
Creating the Form Class
For a form based on a model, in the forms.py file of your app, import forms from django along with your
model from .models
Create a form class inheriting from forms.ModelForm
Add a child class of Meta that defines the model that you are using and fields that will be available in
the form. ModelForm creates fields automatically based on your model
The Meta class in where custom widgets, labels, help_texts or excluded fields are defined
from django import forms
# Import your model to make a form from
from .models import ModelName
class FormName(forms.ModelForm):
class Meta:
model = ModelName
fields = ['model_field_1', ' model_field_2']
To create a form not based on a model you can use:
from django import forms
class ContactForm(forms.Form):
name = forms.CharField(max_length=100, label="Your Name")
email = forms.EmailField(label="Your Email")
message = forms.CharField(widget=forms.Textarea, label="Your Message")
Adding Extra Fields
To add an extra field to your form that is not in your model or you are excluding from your fields variable, you
can define it above the Meta class. By default these extra fields will appear in your form after those defined
in the fields variable.
class EditForm(ModelForm):
'''Form to edit'''
# Extra field not in the model
unavailability_input = forms.CharField(
widget=forms.Textarea(
attrs={
'placeholder': 'Enter dates as YYYY-MM-DD, separated by commas'
}
),
required=False,
)
Add the Form to your View
Now that the form is defined, it can be imported in your views.py file.
# Imports from the forms.py file in the same app
from .forms import FormName
Then a view can be created or updated to use the form. The key points are:
The view will assume that the request method is GET by default. To handle form submission we will need to
split the function into and if check if the request method is POST and 'else'. Everything already in the
function should go into the 'else' part such as defined variables.
Define the form variable and add it to both the POST section with the information from the submitted form
and in the else section in order to display the form on the page. Add the form to the context so that is
rendered.
Validate the form fields against their types, requirements, and validation rules using
form.is_valid()
and if it is, then save it before redirecting the user.
from .forms import FormName
def add_item(request):
# Add the if to check if the request method is POST
if request.method == 'POST':
# Define the form using the information submitted
form = FormName(request.POST)
# Check that the form is valid, then save it and redirect
if form.is_valid():
form.save()
return redirect('view_function_2') # Redirect after saving to another view
# For the case of GET (e.g. page loading)
else:
# Define the form in order to acces it with templating language
form = FormName()
# Add the form to the context, form is defined in POST and GET so in either option there will be a form variable to add
context = {'form': form}
return render(request, 'todo/add_item.html', context)
Add the Form to your Template
Add the form to your template with a method of POST and the action to be the url associated with your above
defined view function.
It is imported to include
as the first thing inside your form for security reasons. Django will not process the form if this is
missing.
Render the form using the variable defined within your context. You can change the layout of your form using:
{{ form.as_p }}
{{ form.as_table }}
{{ form.as_ul }}
Add in a submit button to your form.
<form method="POST" action="{% url 'add' %}">
{% csrf_token %}
{{ form.as_p }} <!-- Renders the form fields with <p> element wrappers -->
<button type="submit">Submit</button>
</form>
Widgets
Widgets in Django forms control how a field is rendered in HTML by describing the element (<input>,
<textarea> etc.), and attributes such as class, id, or placeholder. For ModelForms, the widgets are
defined as a dictionary inside the Meta class using the field name as the key and the element and attribute
options as the value.
# Add widgets in the Meta Class
class Meta:
model = Item
fields = ['name']
widgets = {
'name': forms.TextInput(attrs={'class': 'form-control'}),
}
In a form not based on a ModelForm you would add the widgets in the view.
from django import forms
class MyForm(forms.Form):
name = forms.CharField(
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Enter your name',
'id': 'name-input'
})
)
Here is a quick reference of common built-in widgets.
forms.NullBooleanSelect → Dropdown with "Unknown / Yes / No"
File Uploads
forms.FileInput → <input type="file">
forms.ClearableFileInput → File input with a "clear" checkbox (default for FileField/ImageField).
Common Attributes you can pass:
class: For CSS classes ('form-control')
id: For targeting with JavaScript or CSS
placeholder: Placeholder text
rows/cols: For Textarea
style: Inline CSS
type: Change the input type (e.g., text, email)
multiple: Allow multiple selections for Select
disabled: Disable the field
readonly: Make it read only
Validation
You can define custom validation by adding methods to your form. For best practice, these are defined within
the form rather than the view, as it will help validate the input before submission.
For field specific validation, it is best to define a function that follows the syntax of
clean_fieldname(self).
This is where custom checks for specific fields are added. These will be run first when the form is submitted.
If you want to check for length of entry, if it has a capital letter, if it is an email etc prior to submission,
those checks are covered by the widgets.
def clean_name(self):
data = self.cleaned_data['name']
if 'bad' in data:
raise forms.ValidationError("Invalid name.")
return data
In the case of ModelForms you can also add validation into a clean(self) function at the bottom of the form.
# models.py
from django.core.exceptions import ValidationError
class Room(models.Model):
price = models.DecimalField(max_digits=6, decimal_places=2)
def clean(self):
if self.price <= 0:
raise ValidationError("Price must be positive.")
# Imports
from django.forms import ModelForm
from .models import Room
from django import forms
from datetime import datetime
class EditRoomForm(ModelForm):
'''Form to edit a room.'''
# Handle the unavailable dates differently
unavailability_input = forms.CharField(
widget=forms.Textarea(
# Add placeholder text to instruct user
attrs={
'placeholder': 'Enter dates as YYYY-MM-DD, separated by commas'
}
),
# Make the field not required
required=False,
)
class Meta:
'''Meta class for the edit room form'''
model = Room
# Fields listed
fields = [
'name', 'sanitised_name', 'amenities', 'description',
'image', 'price'
]
def clean_unavailability_input(self):
'''Convert the input dates to usable format'''
input_data = self.cleaned_data['unavailability_input']
if not input_data:
return []
dates = [date.strip() for date in input_data.split(',')]
# Validate date format
for date in dates:
try:
# Convert to correct date format
datetime.strptime(date, '%Y-%m-%d')
except ValueError:
raise forms.ValidationError(
f"Invalid date format: {date}. Use YYYY-MM-DD."
)
return dates
def save(self, commit=True):
'''Overwrite the save to include the cleaned unavailable dates'''
room = super().save(commit=False)
# Save the cleaned list of dates into the 'unavailability' model field
room.unavailability = self.cleaned_data['unavailability_input']
if commit:
room.save()
return room
Troubleshooting
If a form is not working, check that the first thing inside the form element in your HTML page is {% csrf_token %}.