Skip to content

Form Component

Django’s form component simplifies HTML form handling by providing automatic field rendering, built-in validation, and error message generation. The component also handles file uploads through the request.FILES interface.

Form File Upload

To upload a file via an HTML form, two requirements must be met:

  1. The form method must be post
  2. The form enctype must be multipart/form-data
def ab_file(request):
    if request.method == 'POST':
        # request.POST only carries ordinary key/value pairs — files are not included
        print(request.FILES)  # retrieve file data
        # Example output:
        # <MultiValueDict: {'file': [<InMemoryUploadedFile: example.jpg (image/jpeg)>]}>
        file_obj = request.FILES.get('file')  # get the file object
        print(file_obj.name)
        with open(file_obj.name, 'wb') as f:
            for chunk in file_obj.chunks():
                # chunks() reads the file in pieces (recommended for large files)
                f.write(chunk)

    return render(request, 'form.html')

Frontend template:

<form action="" method="post" enctype="multipart/form-data">
    {% csrf_token %}
    <p>username: <input type="text" name="username"></p>
    <p>file: <input type="file" name="file"></p>
    <input type="submit">
</form>

Django Form Class

Instead of processing raw request data manually, define a Form class that declares fields and their validation rules:

from django import forms
from django.core.exceptions import ValidationError

class LoginForm(forms.Form):
    username = forms.CharField(
        min_length=3,
        max_length=20,
        error_messages={
            'required': 'Username is required.',
            'min_length': 'Username must be at least 3 characters.',
            'max_length': 'Username must be at most 20 characters.',
        }
    )
    password = forms.CharField(
        min_length=6,
        widget=forms.PasswordInput,
        error_messages={
            'required': 'Password is required.',
            'min_length': 'Password must be at least 6 characters.',
        }
    )
    email = forms.EmailField(
        error_messages={
            'required': 'Email is required.',
            'invalid': 'Enter a valid email address.',
        }
    )

Using a Form in a View

def login(request):
    if request.method == 'POST':
        form = LoginForm(data=request.POST)
        if form.is_valid():
            # All fields passed validation
            cleaned = form.cleaned_data  # dict of validated values
            print(cleaned)
            # {'username': 'jason', 'password': '123456', 'email': 'jason@example.com'}
            return redirect('/home/')
        # Validation failed — re-render the form with error messages
    else:
        form = LoginForm()   # empty form for GET requests

    return render(request, 'login.html', {'form': form})

Rendering the Form in a Template

<form action="" method="post" novalidate>
    {% csrf_token %}
    {% for field in form %}
        <div>
            <label>{{ field.label }}</label>
            {{ field }}
            {% if field.errors %}
                <span class="error">{{ field.errors.0 }}</span>
            {% endif %}
        </div>
    {% endfor %}
    <button type="submit">Submit</button>
</form>
Add novalidate to the <form> tag to disable the browser’s built-in HTML5 validation and let Django handle all validation server-side, keeping the error messages consistent.

Custom Validation

Two ways to add field-level custom validation:

Using validators

import re
from django.core.exceptions import ValidationError

def check_username(value):
    if re.search(r'\d', value):
        raise ValidationError('Username must not contain digits.')

class RegisterForm(forms.Form):
    username = forms.CharField(validators=[check_username])

Using clean_<fieldname> methods

class RegisterForm(forms.Form):
    username = forms.CharField()
    password = forms.CharField(widget=forms.PasswordInput)
    confirm_password = forms.CharField(widget=forms.PasswordInput)

    def clean_username(self):
        value = self.cleaned_data.get('username')
        if value.lower() == 'admin':
            raise ValidationError('The username "admin" is not allowed.')
        return value

    def clean(self):
        # Cross-field validation
        password = self.cleaned_data.get('password')
        confirm = self.cleaned_data.get('confirm_password')
        if password and confirm and password != confirm:
            raise ValidationError('The two passwords do not match.')
        return self.cleaned_data
Last updated on