CodeOath
← All posts
Python70 min total · 18 parts

Django Fundamentals: MVT Architecture, the ORM, and Middleware

Contents — Part 12 of 18: Forms and Validation
Part 12 of 18 · ~1 min

Forms and Validation

class OrderForm(forms.ModelForm):
    class Meta:
        model = Order
        fields = ["customer_name", "total", "status"]

    def clean_total(self):
        total = self.cleaned_data["total"]
        if total <= 0:
            raise forms.ValidationError("Total must be positive.")
        return total
def order_create(request):
    if request.method == "POST":
        form = OrderForm(request.POST)
        if form.is_valid():          # runs field validation AND clean_<field> methods
            order = form.save()      # ModelForm.save() creates/updates the actual model row
            return redirect("order-detail", pk=order.pk)
    else:
        form = OrderForm()           # an unbound, empty form for the initial GET
    return render(request, "orders/form.html", {"form": form})

A ModelForm derives its fields directly from the model (Meta.model, Meta.fields), avoiding writing out every field twice (once on the model, once on the form). form.is_valid() populates form.cleaned_data with the type-converted, validated values — reading request.POST["total"] directly instead gives you a raw string, with none of the type coercion or validation cleaned_data provides. A clean_<fieldname> method is the standard place for validation specific to one field; clean() (no field name) is for validation that needs to compare multiple fields against each other.

CSRF protection

<form method="post">
  {% csrf_token %}
  {{ form.as_p }}
  <button type="submit">Save</button>
</form>

Every POST form needs {% csrf_token %} — Django's CsrfViewMiddleware rejects a POST request with a 403 if a valid CSRF token isn't present, as protection against cross-site request forgery (a malicious site tricking a logged-in user's browser into submitting a request to your app). Forgetting it is one of the most common "why does my form submission 403" issues for anyone new to Django.