Khi cần form tạo và sửa một bản ghi, bạn không cần viết lại cặp view GET/POST quen thuộc. Django đã có sẵn hai class view chuyên việc này là CreateView và UpdateView: chỉ cần khai báo model, danh sách field và template, Django lo phần còn lại — render form, validate, lưu và redirect. Hai view này phù hợp nhất khi form của bạn map trực tiếp lên một model qua ModelForm.

Vấn đề

Trong một app blog, view tạo bài viết bằng hàm thường dài khoảng 25 dòng: kiểm tra request.method, khởi tạo form với request.POST hoặc form rỗng, gọi is_valid(), lưu rồi redirect. View sửa bài viết gần như giống hệt, chỉ khác dòng lấy object bằng get_object_or_404 và truyền instance=post. Hai view giống nhau tới 80% nhưng vẫn phải viết và bảo trì riêng — càng nhiều model, càng nhiều code lặp.

# views.py — cách viết bằng hàm: dài và lặp lại
from django.shortcuts import get_object_or_404, redirect, render
from .forms import PostForm
from .models import Post
def post_create(request):
if request.method == "POST":
form = PostForm(request.POST)
if form.is_valid():
post = form.save(commit=False)
post.author = request.user
post.save()
return redirect("post-detail", pk=post.pk)
else:
form = PostForm()
return render(request, "blog/post_form.html", {"form": form})
def post_update(request, pk):
post = get_object_or_404(Post, pk=pk)
if request.method == "POST":
form = PostForm(request.POST, instance=post)
if form.is_valid():
form.save()
return redirect("post-detail", pk=post.pk)
else:
form = PostForm(instance=post)
return render(request, "blog/post_form.html", {"form": form})

Giải pháp

Hai class view dưới đây làm đúng những việc trên, mỗi view chỉ còn 4–5 dòng khai báo. CreateView tự tạo ModelForm từ model và fields, render template theo quy ước <app>/<model>_form.html, lưu object khi form hợp lệ rồi redirect về success_url. UpdateView làm tương tự, chỉ khác là nó tự lấy object theo pk trên URL và nạp form với instance sẵn có. Vì success_url được tính lúc import module — khi đó URLconf chưa load xong — phải dùng reverse_lazy() thay vì reverse().

# views.py — cùng tính năng, chỉ còn khai báo
from django.urls import reverse_lazy
from django.views.generic import CreateView, UpdateView
from .models import Post
class PostCreateView(CreateView):
model = Post
fields = ["title", "content", "category"]
template_name = "blog/post_form.html"
success_url = reverse_lazy("post-list")
class PostUpdateView(UpdateView):
model = Post
fields = ["title", "content", "category"]
template_name = "blog/post_form.html"
success_url = reverse_lazy("post-list")
# urls.py
from django.urls import path
from .views import PostCreateView, PostUpdateView
urlpatterns = [
path("posts/new/", PostCreateView.as_view(), name="post-create"),
path("posts/<int:pk>/edit/", PostUpdateView.as_view(), name="post-update"),
]

Hai tình huống hay gặp: cần gán author trước khi lưu — override form_valid() và set form.instance.author, rồi gọi super().form_valid(form). Cần chặn người khác sửa bài không phải của mình — override get_queryset() để UpdateView chỉ tìm trong bài của user đang đăng nhập; bài người khác trả 404 thay vì mở form.

# views.py — phiên bản hoàn thiện: gán author + chặn sửa bài người khác
from django.urls import reverse_lazy
from django.views.generic import CreateView, UpdateView
from .models import Post
class PostCreateView(CreateView):
model = Post
fields = ["title", "content", "category"]
template_name = "blog/post_form.html"
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
def get_success_url(self):
return reverse_lazy("post-detail", kwargs={"pk": self.object.pk})
class PostUpdateView(UpdateView):
model = Post
fields = ["title", "content", "category"]
template_name = "blog/post_form.html"
success_url = reverse_lazy("post-list")
def get_queryset(self):
# Chỉ tìm trong bài của chính mình: bài người khác trả 404
return Post.objects.filter(author=self.request.user)

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

Soát các view tạo/sửa trong dự án: cặp hàm GET/POST nào lặp lại cho từng model thì thay bằng CreateView/UpdateView theo mẫu trên. Bắt đầu từ model đơn giản nhất — ví dụ danh mục — chạy test tạo và sửa một lượt, rồi mới áp dụng cho model có logic riêng như gán author hay kiểm tra quyền sở hữu.

Bài viết liên quan

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

CreateView và UpdateView dùng chung một template được không?

Được — cả hai đều render template theo quy ước <app>/<model>_form.html, nên một template post_form.html dùng chung cho cả tạo và sửa. Để phân biệt, kiểm tra form.instance.pk trong template: tạo mới là None, sửa thì có giá trị.

Khi nào dùng fields, khi nào dùng form_class?

Dùng fields khi form đơn giản, chỉ cần liệt kê nhanh các cột được phép sửa. Dùng form_class với một ModelForm riêng khi cần widget tùy chỉnh, clean_<field> hay nhãn tiếng Việt — lúc đó không khai báo fields nữa.

Vì sao success_url phải dùng reverse_lazy() mà không dùng reverse()?

Vì success_url là thuộc tính class, được tính ngay lúc import module — khi đó URLconf chưa load xong nên reverse() sẽ lỗi. reverse_lazy() hoãn việc tra URL tới lúc cần redirect. Muốn redirect động theo object vừa tạo, override get_success_url() như ví dụ trên.

UpdateView có tự chặn người khác sửa bài của mình không?

Không. Mặc định UpdateView lấy object theo pk mà không kiểm tra quyền sở hữu, nên ai đăng nhập cũng sửa được mọi bản ghi. Muốn chặn, override get_queryset() lọc theo request.user như ví dụ trên, hoặc kết hợp LoginRequiredMixin và UserPassesTestMixin.

💬 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