Skip to content

Django - Form Processing

Handling HTML forms is a fundamental part of web development. Django provides a powerful forms library (django.forms) that handles rendering forms as HTML, validating submitted data, and converting that data into Python types.

Creating a form in Django is similar to creating a model. You define a class that inherits from forms.Form (or forms.ModelForm if the form maps directly to a model). Class attributes represent the form fields.

Let’s create a simple login form. Create a forms.py file in your myapp folder:

myapp/forms.py
from django import forms
class LoginForm(forms.Form):
username = forms.CharField(max_length=100, required=True)
password = forms.CharField(widget=forms.PasswordInput(), required=True)

Here:

  • LoginForm inherits from forms.Form.
  • username is a CharField, representing a text input.
  • password is also a CharField, but we use the PasswordInput widget to render it as <input type="password"> in HTML, obscuring the input.
  • max_length provides basic validation. required=True means the field cannot be empty.
  • Django offers many field types (EmailField, IntegerField, BooleanField, etc.) and widgets (Textarea, CheckboxInput, Select, etc.). See Django Form Fields and Widgets documentation.

Views handle the logic of displaying and processing forms. A common pattern is to handle both GET (displaying the empty form) and POST (processing submitted data) requests in the same view.

Let’s create a login_view in myapp/views.py:

myapp/views.py
from django.shortcuts import render, redirect
from .forms import LoginForm
# Assuming you have some authentication logic
# from django.contrib.auth import authenticate, login as auth_login
def login_view(request):
if request.method == 'POST':
form = LoginForm(request.POST)
if form.is_valid():
username = form.cleaned_data['username']
password = form.cleaned_data['password']
#
# *** Add authentication logic here! ***
# Example (requires django.contrib.auth setup):
# user = authenticate(request, username=username, password=password)
# if user is not None:
# auth_login(request, user)
# return redirect('home') # Redirect to a success page
# else:
# # Handle invalid login (e.g., add form error)
# form.add_error(None, 'Invalid username or password.')
#
# For this simple example, we'll just render a success page
return render(request, 'myapp/loggedin.html', {'username': username})
# If form is invalid, render the page with the form and errors
else: # GET request
form = LoginForm()
return render(request, 'myapp/login.html', {'form': form})

Explanation:

  • If request.method is ‘POST’, we instantiate the form with the submitted data (request.POST).
  • form.is_valid() triggers validation. It checks field requirements (like required=True) and runs any custom validation methods.
  • If valid, data is available in form.cleaned_data, a dictionary containing validated Python objects.
  • Important: The example above lacks real authentication. In a real application, you’d use django.contrib.auth.authenticate and login here.
  • If the form is invalid, or if it’s a GET request, we instantiate an empty form (LoginForm()) or re-render the bound form (which now contains error messages).
  • We pass the form object to the template context.

We need two templates: one for displaying the form (login.html) and one for the success message (loggedin.html).

myapp/templates/myapp/login.html:

myapp/templates/myapp/login.html
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
<style>
.errorlist { color: red; list-style-type: none; padding-left: 0; }
label { display: block; margin-top: 10px; }
</style>
</head>
<body>
<h2>Login</h2>
<form method="post">
{% csrf_token %}
{{ form.non_field_errors }}
<div>
{{ form.username.label_tag }}
{{ form.username }}
{{ form.username.errors }}
</div>
<div>
{{ form.password.label_tag }}
{{ form.password }}
{{ form.password.errors }}
</div>
<br>
<button type="submit">Login</button>
</form>
</body>
</html>

Key Template Elements:

  • <form method="post">: Specifies the HTTP method.
  • {% csrf_token %}: Essential security tag to prevent Cross-Site Request Forgery attacks. Django middleware checks for this token on POST requests.
  • {{ form.as_p }} or {{ form.as_ul }} or {{ form.as_table }}: Django shortcuts to render the form fields wrapped in <p>, <li>, or <tr><td> tags, respectively.
  • Manual Field Rendering (as shown above): Gives more control. {{ form.fieldname.label_tag }}, {{ form.fieldname }}, {{ form.fieldname.errors }} render the label, input widget, and validation errors for a specific field.
  • {{ form.non_field_errors }} displays errors not tied to a specific field (e.g., from form.add_error(None, ...)).

myapp/templates/myapp/loggedin.html:

myapp/templates/myapp/loggedin.html
<!DOCTYPE html>
<html>
<head>
<title>Logged In</title>
</head>
<body>
<p>Welcome, **{{ username }}**! (Login successful - demo only)</p>
</body>
</html>

Map a URL to the view in myapp/urls.py:

myapp/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('login/', views.login_view, name='login'),
# Add other app URLs here
]

Ensure this myapp/urls.py is included in your main project urls.py.

Run the development server (python manage.py runserver) and navigate to /myapp/login/ (or whatever URL you configured).

You should see the login form. Try submitting:

  • With empty fields: Django’s required=True validation should trigger, displaying errors next to the fields.
  • With valid data: You should be redirected (or see the content of) loggedin.html.
  • With incorrect login details (if you implemented authentication): You should see the form again with a non-field error message.

You can add custom validation logic to your form class by defining methods starting with clean_ followed by the field name, or a general clean() method.

Example: Ensure username isn’t ‘admin’.

myapp/forms.py
from django import forms
from django.core.exceptions import ValidationError
class LoginForm(forms.Form):
username = forms.CharField(max_length=100, required=True)
password = forms.CharField(widget=forms.PasswordInput(), required=True)
def clean_username(self):
data = self.cleaned_data['username']
if data.lower() == 'admin':
raise ValidationError("Admin username is not allowed.")
return data # Always return the cleaned data
# Example clean() method for cross-field validation
# def clean(self):
# cleaned_data = super().clean()
# # Add validation that depends on multiple fields
# return cleaned_data

If a validation method raises ValidationError, the form’s is_valid() will return False, and the error message will be associated with the field (or displayed via non_field_errors if raised in clean() without specifying a field).

Further Reading: Django Forms Documentation