Topic 11 / 12

Handling User Input with Django Forms

~18 min read  //  Django Part 1 Series  //  Coding India

1. Why Not Just Read request.POST Directly?

When a user submits an HTML form, the data arrives in request.POST — a dictionary of raw strings. You could read it directly, but doing so is dangerous: there is no type conversion, no validation, and no protection against malformed input. Django Forms solve all three problems in one structured layer.

2. Creating a Django Form

Create a forms.py file inside your blog app:

# blog/forms.py
from django import forms

class CommentForm(forms.Form):
    # Each field defines the data type, constraints, and widget to render.
    name = forms.CharField(
        max_length=100,
        widget=forms.TextInput(attrs={'placeholder': 'Your name'})
    )
    email = forms.EmailField(
        widget=forms.EmailInput(attrs={'placeholder': '[email protected]'})
    )
    body = forms.CharField(
        widget=forms.Textarea(attrs={'rows': 4, 'placeholder': 'Your comment...'})
    )

3. Processing a Form in a View

A view that handles a form must deal with two cases: the initial page load (GET request, show a blank form) and the form submission (POST request, validate and process the data). This pattern is called the GET/POST split:

# blog/views.py
from django.shortcuts import render, redirect
from .forms import CommentForm

def add_comment(request, post_slug):
    if request.method == 'POST':
        # Bind the form to the submitted data
        form = CommentForm(request.POST)

        if form.is_valid():
            # form.cleaned_data is a dictionary of validated, type-cast values
            name  = form.cleaned_data['name']
            email = form.cleaned_data['email']
            body  = form.cleaned_data['body']
            # ... save the comment to the database here ...
            return redirect('post_detail', slug=post_slug)
        # If form is invalid, fall through and re-render with errors shown
    else:
        # GET request: show an empty form
        form = CommentForm()

    return render(request, 'blog/add_comment.html', {'form': form})

4. Rendering the Form in a Template

<!-- templates/blog/add_comment.html -->
{% extends "blog/base.html" %}
{% block content %}
<h1>Leave a Comment</h1>
<form method="post">
  {% csrf_token %}
  {{ form.as_p }}
  <button type="submit">Submit</button>
</form>
{% endblock %}

{% csrf_token %} injects a hidden security token into every POST form. Django validates this token on submission to prevent Cross-Site Request Forgery attacks — an attacker on another website cannot forge form submissions to your site because they cannot know this token. Never omit it.

{{ form.as_p }} renders every field wrapped in a <p> tag, including error messages when validation fails. You can also render fields individually for full layout control:

<div class="field">
  <label for="{{ form.name.id_for_label }}">Your Name</label>
  {{ form.name }}
  {% if form.name.errors %}
    <ul class="errors">
      {% for error in form.name.errors %}<li>{{ error }}</li>{% endfor %}
    </ul>
  {% endif %}
</div>

5. ModelForm — The Shortcut for Model-Based Forms

If your form maps directly to a model (which it usually does), use a ModelForm instead. It auto-generates fields from the model definition, so you do not duplicate code:

# blog/forms.py
from django import forms
from .models import Post

class PostForm(forms.ModelForm):
    class Meta:
        model = Post
        # List the fields to include in the form
        fields = ['title', 'slug', 'body', 'published']
        widgets = {
            'body': forms.Textarea(attrs={'rows': 10}),
        }
# blog/views.py — create a new post
from django.contrib.auth.decorators import login_required
from .forms import PostForm

@login_required
def create_post(request):
    if request.method == 'POST':
        form = PostForm(request.POST)
        if form.is_valid():
            post = form.save(commit=False)  # Don't save to DB yet
            post.author = request.user      # Attach the logged-in user
            post.save()                     # Now save
            return redirect('post_detail', slug=post.slug)
    else:
        form = PostForm()
    return render(request, 'blog/create_post.html', {'form': form})

commit=False is a powerful ModelForm feature: it creates the model instance in memory without writing to the database, letting you modify fields (like setting author) before the final save().