Khi đã có sẵn model, đừng viết form bằng tay rồi map từng field sang model nữa. Django ModelForm tự sinh form từ model, tự validate theo đúng ràng buộc của field, và lưu thẳng thành object chỉ bằng form.save(). Nó phù hợp nhất cho các form CRUD: tạo bài viết, sửa hồ sơ, nhập dữ liệu — nơi field của form trùng gần hết với field của model.

Vấn đề

Giả sử bạn có model Article và muốn làm trang tạo bài viết. Cách viết tay thường trông thế này:

def article_create(request):
if request.method == "POST":
title = request.POST.get("title", "")
content = request.POST.get("content", "")
# Không có validate: title rỗng vẫn lưu được,
# max_length=200 cũng không ai chặn giúp bạn
Article.objects.create(title=title, content=content)
return redirect("article-list")
return render(request, "blog/article_form.html")

Vấn đề là mọi ràng buộc của model — bắt buộc, độ dài tối đa, kiểu dữ liệu — đều bị bỏ qua. Bạn phải tự viết lại validate, tự render lại form kèm lỗi khi sai, và mỗi lần thêm field vào model lại phải sửa thêm một chỗ nữa.

Giải pháp

ModelForm làm cả ba việc đó trong một class. Giả sử model của bạn như sau:

# blog/models.py
from django.db import models
class Article(models.Model):
title = models.CharField(max_length=200)
summary = models.CharField(max_length=300, blank=True)
content = models.TextField()

Khai báo model, liệt kê field, xong:

# blog/forms.py
from django import forms
from .models import Article
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = ["title", "summary", "content"]
widgets = {
"summary": forms.Textarea(attrs={"rows": 3}),
}
def clean_title(self):
title = self.cleaned_data["title"]
if len(title) < 10:
raise forms.ValidationError("Tiêu đề phải dài ít nhất 10 ký tự.")
return title

View chỉ còn vài dòng, và vẫn giữ được quyền kiểm soát trước khi lưu:

# blog/views.py
from django.shortcuts import render, redirect
from .forms import ArticleForm
def article_create(request):
if request.method == "POST":
form = ArticleForm(request.POST)
if form.is_valid():
article = form.save(commit=False)
article.summary = article.summary or "Chưa có tóm tắt"
article.save()
return redirect("article-list")
else:
form = ArticleForm()
return render(request, "blog/article_form.html", {"form": form})

Trong template, chỉ cần {{ form.as_p }} kèm {% csrf_token %} là form hiển thị đầy đủ kèm lỗi từng field khi nhập sai.

Vì sao cách này tốt hơn:

  • Validate miễn phí: form.is_valid() kiểm tra đúng ràng buộc của model, từ field bắt buộc đến max_length, thay vì bạn viết lại bằng tay.
  • Ít code trùng lặp: thêm field vào model thì chỉ cần thêm tên vào danh sách fields.
  • Lỗi hiển thị sẵn: form không hợp lệ thì template render lại kèm lỗi từng field, không cần tự truyền errors.
  • Tùy biến khi cần: clean_tên-field để validate riêng một field, widgets để đổi giao diện nhập liệu, commit=False để gán thêm dữ liệu trước khi lưu thật.

Áp dụng vào dự án của bạn

Dùng ModelForm cho mọi form CRUD bám sát model: form tạo và sửa bài viết, hồ sơ người dùng, đơn hàng nội bộ. Khi form lệch xa model — ví dụ form đăng ký gom nhiều model, hoặc form chỉ để tìm kiếm — lúc đó mới quay về forms.Form thuần. Một quy tắc nhỏ nhưng quan trọng: liệt kê tường minh fields, đừng dùng fields = “__all__”, tránh vô tình lộ field nhạy cảm như is_staff khi model thay đổi sau này.

Bài viết liên quan

Câu hỏi thường gặp

ModelForm khác gì forms.Form?

Form thuần không gắn với model nào: bạn tự định nghĩa field và tự xử lý việc lưu. ModelForm sinh field từ model và có sẵn phương thức save().

Khi nào dùng fields = “__all__”?

Gần như không bao giờ. Liệt kê tường minh từng field để khi model thêm field nhạy cảm, form không tự động lộ nó ra ngoài.

Làm sao validate liên quan nhiều field?

Viết phương thức clean() toàn form, đọc các giá trị từ self.cleaned_data, và raise ValidationError khi tổ hợp không hợp lệ.

Form có file upload thì sao?

Truyền thêm request.FILES vào form khi khởi tạo, và thêm enctype=”multipart/form-data” cho thẻ form trong template.

ModelForm có dùng cho form sửa không?

Có. Truyền instance=article vào form, form sẽ điền sẵn dữ liệu cũ và khi lưu sẽ cập nhật đúng object đó thay vì tạo mới.

💬 Bạn đã thử cách này trong dự án của mình chưa? Nếu gặp lỗi hoặc có cách làm gọn hơn, để lại bình luận bên dưới — mình đọc hết và sẽ bổ sung những ý hay vào bài.

Nguồn tham khảo