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.
<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.