Introduction to Django Forms
This article introduces Django forms and explains how to collect, validate, clean, and process user input. It covers Form, ModelForm, fields, widgets, validation, cleaned_data, GET and POST handling, file uploads, CSRF protection, model creation and updates, class-based view integration, testing, and common mistakes.
Introduction to Django Forms
Forms are one of the main ways users send data to a Django application.
A form may be used to:
- create an account
- log in
- submit a contact message
- create a blog post
- update a profile
- upload a file
- search for records
- select application settings
HTML forms can collect data in the browser, but Django provides a form system that handles much more than rendering <input> elements.
Django forms can:
- define fields
- validate submitted values
- convert raw strings into Python values
- display validation errors
- generate HTML controls
- clean and normalize data
- work with model instances
- protect against invalid input
A basic Django form sits between the browser and the application logic.
⧉
1 2 3 4 5 6 7 8 9 10 11 | |
What Is a Django Form?
A Django form is a Python class that describes a set of input fields and the rules associated with them.
A simple form might look like this:
⧉
1 2 3 4 5 6 7 8 9 | |
This form defines three fields:
⧉
1 2 3 | |
Django knows how to:
- render suitable HTML inputs
- check that required values exist
- validate the email address
- enforce the maximum length
- convert submitted values into cleaned Python data
- display errors when validation fails
The Two Main Types of Django Forms
Django commonly uses two form classes:
⧉
1 2 | |
Use forms.Form when the form is not directly tied to a database model.
Use forms.ModelForm when the form creates or updates a model instance.
Examples:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 | |
A Basic Form
Create a form in an app’s forms.py file:
⧉
1 2 3 4 5 | |
Example:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
Each form field is an instance of a Django form-field class.
Common Form Fields
| Field | Purpose |
|---|---|
CharField |
Text |
EmailField |
Email address |
IntegerField |
Whole number |
DecimalField |
Decimal number |
BooleanField |
True/false value |
DateField |
Date |
DateTimeField |
Date and time |
ChoiceField |
One value from choices |
MultipleChoiceField |
Several values from choices |
FileField |
Uploaded file |
ImageField |
Uploaded image |
URLField |
URL |
UUIDField |
UUID value |
Example:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 | |
Form Fields and Model Fields Are Different
Django form fields and Django model fields have similar names, but they serve different purposes.
Model field:
⧉
1 2 3 4 5 6 | |
Form field:
⧉
1 2 3 4 5 6 | |
A model field describes database data.
A form field describes user input and validation.
⧉
1 2 3 4 5 | |
A ModelForm connects the two systems automatically.
Rendering a Form in a Template
A Django form object can generate HTML.
View:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
Template:
⧉
1 2 3 4 5 6 7 8 9 10 11 | |
form.as_p renders each field inside a paragraph.
Generated HTML may resemble:
⧉
1 2 3 4 5 6 7 8 9 10 | |
Form Rendering Shortcuts
Django provides several rendering shortcuts.
⧉
1 | |
Renders fields inside <p> elements.
⧉
1 | |
Renders fields as list items.
⧉
1 | |
Renders fields as table rows.
For greater control, render fields manually:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
Manual rendering is useful when the form needs custom layout or styling.
GET and POST
Forms commonly use either GET or POST.
Use GET when the form retrieves or filters information.
Examples:
- search
- filters
- sorting
Use POST when the form changes server-side data.
Examples:
- creating an object
- editing an object
- registering a user
- submitting a message
- uploading a file
Search form:
⧉
1 | |
Create form:
⧉
1 | |
Processing a POST Request
A typical form view handles both the initial page request and the submitted form.
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | |
The two main states are:
⧉
1 2 3 4 5 | |
Bound and Unbound Forms
An unbound form has no submitted data:
⧉
1 | |
A bound form contains submitted data:
⧉
1 | |
An unbound form is normally used for initial display.
A bound form can be validated.
Check:
⧉
1 | |
Example:
⧉
1 2 3 | |
returns:
⧉
1 | |
While:
⧉
1 2 3 4 5 6 7 | |
has:
⧉
1 | |
is_valid()
Call:
⧉
1 | |
to run validation.
Example:
⧉
1 2 | |
If every field is valid:
⧉
1 | |
returns:
⧉
1 | |
If one or more fields are invalid:
⧉
1 | |
Django then stores the validation errors on the form.
cleaned_data
After a successful call to is_valid(), use:
⧉
1 | |
Example:
⧉
1 2 | |
cleaned_data contains validated Python values.
For example, raw submitted data may contain:
⧉
1 | |
but an IntegerField converts it into:
⧉
1 | |
Likewise, a date string may become a Python date object.
This is one of the most important purposes of a Django form:
⧉
1 2 3 4 5 | |
Do not use request.POST as if it has already been validated.
Prefer:
⧉
1 | |
after validation.
Validation Errors
When validation fails, the form contains errors.
⧉
1 2 | |
For example:
⧉
1 2 3 4 5 6 7 | |
may produce errors for:
⧉
1 2 3 | |
Django automatically redisplays bound form values and validation messages when the same form is rendered again.
Displaying Errors
With:
⧉
1 | |
errors are normally rendered automatically.
When rendering manually:
⧉
1 2 3 | |
Display all non-field errors with:
⧉
1 | |
You can also render all errors:
⧉
1 | |
Required Fields
Form fields are required by default.
⧉
1 | |
means a value is required.
Make a field optional with:
⧉
1 2 3 | |
Example:
⧉
1 2 3 4 5 6 7 8 9 | |
Initial Values
Use initial to give a field a starting value.
⧉
1 2 3 4 | |
You can also provide initial values when creating the form:
⧉
1 2 3 4 5 6 | |
This is useful when:
- editing existing data
- pre-filling known values
- providing sensible defaults
Initial values are not the same as submitted data.
Labels
Django generates labels automatically from field names.
⧉
1 | |
normally becomes:
⧉
1 | |
Set a custom label:
⧉
1 2 3 | |
Help Text
Use help_text to provide instructions.
⧉
1 2 3 4 5 6 | |
In templates, help text may appear alongside the field.
Manual rendering:
⧉
1 2 3 4 5 6 7 | |
Widgets
A form field determines validation.
A widget determines how the field is rendered in HTML.
Example:
⧉
1 2 3 | |
The field is still a CharField.
But it renders as:
⧉
1 | |
instead of:
⧉
1 | |
A useful distinction is:
⧉
1 2 3 4 5 | |
Common Widgets
| Widget | HTML control |
|---|---|
TextInput |
Text input |
Textarea |
Text area |
PasswordInput |
Password input |
EmailInput |
Email input |
NumberInput |
Number input |
CheckboxInput |
Checkbox |
Select |
Drop-down |
RadioSelect |
Radio buttons |
CheckboxSelectMultiple |
Multiple checkboxes |
DateInput |
Date-like input |
FileInput |
File upload |
Example:
⧉
1 2 3 | |
Customizing Widget Attributes
HTML attributes can be added through attrs.
⧉
1 2 3 4 5 6 7 8 | |
Generated HTML may resemble:
⧉
1 2 3 4 5 | |
Another example:
⧉
1 2 3 4 5 6 7 8 | |
ChoiceField
Use ChoiceField when a user must select one value from a predefined list.
⧉
1 2 3 4 5 6 7 8 9 10 11 | |
Django normally renders this as a <select>.
Stored value:
⧉
1 | |
Displayed label:
⧉
1 | |
Radio Buttons
Use RadioSelect:
⧉
1 2 3 4 5 6 7 8 | |
The validation remains that of a ChoiceField.
Only the widget changes.
Multiple Choices
Use MultipleChoiceField:
⧉
1 2 3 4 5 6 7 | |
A valid result may be:
⧉
1 2 3 4 | |
Use checkboxes:
⧉
1 2 3 4 | |
Boolean Fields
A Boolean field commonly renders as a checkbox.
⧉
1 | |
It is required by default.
An optional Boolean field can be:
⧉
1 2 3 | |
Custom Field Validation
Django provides several ways to add custom validation.
For one field, define:
⧉
1 | |
Example:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
The method must return the cleaned value.
Field Validation Flow
For a field named:
⧉
1 | |
Django performs validation and then calls:
⧉
1 | |
if the earlier field validation succeeds.
A simplified flow is:
⧉
1 2 3 4 5 6 7 8 9 | |
Cross-Field Validation
Sometimes validation depends on more than one field.
Use the form’s:
⧉
1 | |
method.
Example:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | |
This creates a form-level error rather than an error attached to one specific field.
Adding an Error to a Specific Field
Inside clean() you can attach an error manually:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
This often gives a better user experience than a generic non-field error.
Validators
Reusable validators can be attached to form fields.
⧉
1 2 3 4 5 6 7 8 9 10 | |
Custom validator:
⧉
1 2 3 4 5 6 7 8 9 10 | |
Use it:
⧉
1 2 3 | |
Validators are useful when the same rule needs to be reused.
A Complete Basic Form View
Form:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
View:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | |
Template:
⧉
1 2 3 4 5 6 7 8 9 10 11 | |
Why Redirect After a Successful POST?
After successfully processing a form, redirect to another URL:
⧉
1 | |
This follows the common:
⧉
1 2 3 4 5 6 7 | |
pattern.
It is often called Post/Redirect/Get.
Without a redirect, refreshing the page may cause the browser to ask whether the user wants to resubmit the form.
For actions such as:
- creating records
- sending emails
- placing orders
accidental resubmission can cause duplicate actions.
ModelForm
A ModelForm creates form fields from a Django model.
Consider:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
A corresponding model form can be:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
Django generates suitable form fields based on the model fields.
The Meta Class
A ModelForm uses an inner Meta class.
⧉
1 2 3 4 5 6 | |
model tells Django which model the form belongs to.
fields tells Django which fields the user may edit.
Explicitly List Editable Fields
Prefer:
⧉
1 2 3 4 | |
over:
⧉
1 | |
when users should not be able to edit every model field.
Explicit field lists make it easier to see which data can be submitted.
This is especially important for fields such as:
- owner
- author
- permissions
- status
- internal flags
- prices
- moderation fields
Do not expose sensitive model fields merely because they exist on the model.
Creating an Object with ModelForm
View:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | |
form.save() creates and saves the model instance.
Updating an Existing Object
Pass the existing instance into the form.
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | |
The important argument is:
⧉
1 | |
Without it, the form would create a new article instead of updating the existing one.
save(commit=False)
Sometimes the view must set model fields that are not exposed in the form.
Suppose:
⧉
1 2 3 4 5 6 7 8 9 10 11 | |
The form should not let users select arbitrary authors:
⧉
1 2 3 4 5 6 7 | |
In the view:
⧉
1 2 3 4 5 6 7 | |
commit=False creates the model instance without saving it to the database immediately.
This gives the application a chance to set additional values.
Many-to-Many Fields and save_m2m()
When a ModelForm contains many-to-many fields and you use:
⧉
1 | |
the many-to-many relationships cannot be saved until the main object has a primary key.
Example:
⧉
1 2 3 4 5 6 7 8 | |
Use:
⧉
1 | |
after saving the model instance.
Customizing a ModelForm
You can customize generated fields directly.
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | |
Other Meta options include:
⧉
1 2 3 | |
⧉
1 2 3 | |
⧉
1 2 3 4 5 | |
Adding Non-Model Fields to a ModelForm
A ModelForm can contain additional fields that do not exist on the model.
⧉
1 2 3 4 5 6 7 8 9 10 11 12 | |
The extra field appears in:
⧉
1 | |
but it is not automatically saved to the model.
This is useful for:
- confirmations
- temporary options
- workflow decisions
- additional validation
File Upload Forms
Forms that accept files require:
⧉
1 | |
Example:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 | |
View:
⧉
1 2 3 4 | |
Both pieces are required:
⧉
1 2 3 4 5 | |
CSRF Protection
POST forms should normally include:
⧉
1 | |
Example:
⧉
1 2 3 4 5 6 7 8 | |
Django uses CSRF protection to help prevent another website from submitting unwanted requests using a user’s browser session.
Do not remove CSRF protection merely to make a form submission work.
Form Prefixes
Sometimes one page contains multiple forms with fields that have the same names.
Use prefixes:
⧉
1 2 3 4 5 6 7 | |
Generated names may become:
⧉
1 2 | |
On POST:
⧉
1 2 3 4 | |
The same prefix must be used when binding submitted data.
Hidden Fields
Use a hidden widget when a field should be submitted but not displayed as a normal control.
⧉
1 2 3 | |
Remember that hidden form values are still controlled by the browser.
A user can modify them.
Do not trust hidden input for:
- permissions
- ownership
- prices
- security decisions
Sensitive values should be determined server-side.
Disabled Fields
A field can be disabled:
⧉
1 2 3 | |
Django renders the field as disabled and ignores submitted modifications to it.
This is more secure than relying only on an HTML disabled attribute added manually because Django also enforces the behavior during form processing.
Form Inheritance
Forms are Python classes, so they can use inheritance.
⧉
1 2 3 4 5 6 7 8 9 10 11 | |
Inheritance can reduce repetition, but avoid creating deep form hierarchies that make field behavior difficult to understand.
Formsets
Sometimes a page needs to process several similar forms at once.
Examples:
- several invoice items
- several phone numbers
- several related records
Django provides formsets for this purpose.
Basic example:
⧉
1 2 3 4 5 6 7 | |
A formset manages a collection of related forms.
For beginner applications, start with ordinary forms first. Learn formsets when a page genuinely needs repeated forms.
Model Formsets
For multiple instances of the same model, Django provides model formsets.
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
This can support bulk editing.
Inline Formsets
Inline formsets are useful for parent-child relationships.
For example:
⧉
1 2 3 4 | |
An inline formset can allow editing the order and several related items on one page.
This is a more advanced form topic and should be introduced only when the application needs it.
Form Classes and Class-Based Views
Django’s generic class-based views can work with forms.
Example:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
For model forms, views such as:
⧉
1 2 | |
can generate or use model forms automatically.
Using a Custom Form with CreateView
⧉
1 2 3 4 5 6 7 8 9 10 | |
Do not normally define both:
⧉
1 | |
and:
⧉
1 | |
on the same generic editing view.
Choose one approach.
Customizing Form Behavior in Class-Based Views
Set server-controlled values in:
⧉
1 | |
Example:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
This keeps author out of the browser-submitted fields.
Passing Extra Data to a Form
Sometimes a form needs access to information such as the current user.
Custom form:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
Function-based view:
⧉
1 2 3 4 | |
In a class-based view, override get_form_kwargs():
⧉
1 2 3 4 5 | |
This is useful when:
- choices depend on the user
- validation depends on permissions
- querysets need to be user-specific
Dynamic Querysets
Suppose a form contains a model choice:
⧉
1 2 3 4 5 6 7 | |
You can limit its queryset:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | |
This can prevent users from selecting objects they do not own.
Still enforce permissions server-side when saving or processing the result.
Empty Values
Different form fields normalize empty input in different ways.
For a normal optional text field:
⧉
1 2 3 | |
an empty value is commonly:
⧉
1 | |
For other fields, the cleaned value may be:
⧉
1 | |
Do not assume all empty inputs have the same cleaned representation.
Check the behavior of the field type being used.
Error Messages
Customize field errors:
⧉
1 2 3 4 5 6 7 8 9 10 | |
This can make forms easier for users to understand.
Avoid exposing internal exceptions or implementation details as validation messages.
Checking Whether a Form Changed
When editing existing data, use:
⧉
1 | |
Example:
⧉
1 2 3 | |
See changed fields:
⧉
1 | |
Example result:
⧉
1 2 3 4 | |
Cleaning and Normalizing Input
Validation can also normalize user input.
Example:
⧉
1 2 3 4 | |
Or:
⧉
1 2 3 4 | |
Be careful not to perform unexpected transformations that change meaningful user data.
Avoid Putting Too Much Logic in Forms
Forms are a good place for:
- input validation
- field-specific normalization
- cross-field validation
Forms are usually not the best place for:
- complex payment workflows
- large business processes
- sending several external requests
- multi-step database operations
- unrelated application logic
For example, this may become difficult to maintain:
⧉
1 2 3 4 5 6 7 | |
Prefer a service function for the main business operation.
⧉
1 2 3 4 5 | |
Validation and business workflow are related, but they are not the same responsibility.
Forms Do Not Replace Database Constraints
A form may validate uniqueness:
⧉
1 2 | |
but another part of the application may create records without using that form.
Important data rules should also be enforced at the appropriate lower level, such as:
- model constraints
- unique fields
- database constraints
Forms improve input validation and user feedback.
They should not be the only protection for important data integrity rules.
Forms Do Not Replace Permissions
A form can hide a field:
⧉
1 2 3 4 | |
but that does not prove the user is allowed to edit the object.
The view must still enforce authorization.
Example:
⧉
1 2 3 4 5 | |
Then:
⧉
1 2 3 4 | |
The form validates data.
The view controls access.
Testing Forms
Django forms are easy to test directly.
Valid form:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
Invalid form:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
Testing Custom Validation
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
Test important boundary values as well as the normal case.
Common Beginner Mistakes
Forgetting to Call is_valid()
Do not use:
⧉
1 | |
before validation.
Correct:
⧉
1 2 | |
Reading Raw POST Data Instead of Cleaned Data
Avoid:
⧉
1 | |
after creating a form.
Prefer:
⧉
1 | |
after validation.
Creating a New Form After Validation Fails
Incorrect:
⧉
1 2 3 4 5 | |
This discards submitted data and errors.
Keep the bound form:
⧉
1 2 | |
and render it again if invalid.
Forgetting CSRF Protection
For normal POST forms:
⧉
1 | |
should be inside the form.
Forgetting multipart/form-data
Required for file uploads:
⧉
1 2 3 4 | |
Forgetting request.FILES
For file forms:
⧉
1 2 3 4 | |
Using fields = "__all__" Carelessly
This may accidentally expose fields users should not control.
Prefer an explicit field list.
Forgetting instance During Updates
Incorrect:
⧉
1 | |
when editing an existing article.
Correct:
⧉
1 2 3 4 | |
Forgetting save_m2m()
After:
⧉
1 2 | |
call:
⧉
1 | |
if the form includes many-to-many fields.
Putting Authorization in Hidden Fields
Do not trust:
⧉
1 2 3 4 5 | |
to control ownership.
Set ownership server-side.
Treating Browser Validation as Security
HTML attributes such as:
⧉
1 2 3 | |
improve the interface, but a client can bypass them.
Django must still validate the submitted data.
A Complete ModelForm Example
Model:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | |
Form:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | |
Create view:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | |
Template:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | |
Update view:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | |
This setup demonstrates:
- model-backed forms
- field validation
- widgets
- creation
- updating
- ownership protection
- CSRF protection
- Post/Redirect/Get
Recommended Form Workflow
For a normal POST form:
- Define the form class.
- Display an unbound form for
GET. - Bind
request.POSTforPOST. - Include
request.FILESwhen uploading files. - Call
is_valid(). - Read validated values from
cleaned_data. - Perform the required action.
- Redirect after success.
- Render the same bound form when validation fails.
Conceptually:
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
Mini Reference
⧉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | |
Basic form:
⧉
1 2 3 4 5 | |
Basic processing:
⧉
1 2 3 4 5 6 7 | |
Basic template:
⧉
1 2 3 4 5 6 7 8 | |
Basic model form:
⧉
1 2 3 4 5 6 7 | |
Django forms provide a structured way to receive, validate, clean, and process user input.
The main ideas are:
forms.Formis used for general inputforms.ModelFormconnects forms to models- form fields define validation rules
- widgets control HTML rendering
- bound forms contain submitted data
is_valid()runs validationcleaned_datacontains validated Python values- field-specific validation uses
clean_<field>() - cross-field validation uses
clean() - model forms can create and update model instances
commit=Falseallows server-side fields to be set before saving- file forms require
multipart/form-dataandrequest.FILES - POST forms should use CSRF protection
- authorization and data integrity should not rely only on forms
For a beginner Django application, start with a small Form or ModelForm, process it with the standard GET/POST pattern, and keep the validation rules close to the input they describe. Add custom widgets, formsets, dynamic fields, and more advanced form behavior only when the application requires them.
Join the Newsletter
Practical insights on Django, backend systems, deployment, architecture, and real-world development — delivered without noise.
Get updates when new guides, learning paths, cheat sheets, and field notes are published.
No spam. Unsubscribe anytime.
There is no third-party involved so don't worry - we won't share your details with anyone.