Khi view Django cần lấy một bản ghi theo id hoặc slug, cách dùng Model.objects.get() sẽ ném DoesNotExist và trả lỗi 500 nếu bản ghi không tồn tại. Hàm get_object_or_404() trong django.shortcuts làm đúng việc đó trong một dòng: tìm không thấy thì tự ném Http404 để Django trả trang 404. Dùng nó cho mọi view chi tiết lấy object theo id, slug hay khóa duy nhất.

Vấn đề

Hãy tưởng tượng bạn viết view chi tiết bài viết, lấy bài theo slug từ URL. Người dùng gõ nhầm slug, hoặc bài đã bị xóa, thì Post.objects.get(slug=slug) không tìm thấy bản ghi nào và ném Post.DoesNotExist. Nếu bạn quên bọc try/except, Django trả lỗi 500 — vừa xấu, vừa lộ ra rằng server gặp sự cố thay vì đơn giản là “không có trang này”.

from django.http import Http404
from django.shortcuts import render
def post_detail(request, slug):
try:
post = Post.objects.get(slug=slug)
except Post.DoesNotExist:
raise Http404("Không tìm thấy bài viết.")
return render(request, "blog/detail.html", {"post": post})

Đoạn này chạy đúng, nhưng mỗi view chi tiết lại lặp lại khối try/except giống nhau — và chỉ cần một chỗ quên bọc là có ngay lỗi 500 trong production.

Giải pháp

Thay toàn bộ khối try/except bằng một dòng duy nhất:

from django.shortcuts import get_object_or_404, render
def post_detail(request, slug):
post = get_object_or_404(Post, slug=slug)
return render(request, "blog/detail.html", {"post": post})

Bên trong, hàm gọi get() trên model rồi chuyển DoesNotExist thành Http404, nên hành vi giống hệt bản viết tay mà code gọn hơn nhiều. Bạn còn có thể truyền queryset đã lọc sẵn, ví dụ chỉ lấy bài đã xuất bản:

from django.shortcuts import get_list_or_404, get_object_or_404
def post_detail(request, slug):
post = get_object_or_404(Post.objects.filter(status="published"), slug=slug)
return render(request, "blog/detail.html", {"post": post})
def posts_by_tag(request, tag_slug):
posts = get_list_or_404(Post, tags__slug=tag_slug)
return render(request, "blog/tag.html", {"posts": posts})

get_list_or_404() là bản “danh sách” tương ứng: nó lọc rồi trả về list, và ném Http404 khi kết quả rỗng. Cả hai hàm đều nhận model, manager hoặc queryset, và chấp nhận cả đối tượng Q lẫn các lookup quen thuộc của get() và filter(). Bạn bỏ được code lặp, mọi view có cùng một hành vi 404 nhất quán, và người đọc code nhìn một dòng là hiểu ngay: “không có thì 404”.

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

Rà soát các view chi tiết trong dự án và thay các khối try/except DoesNotExist bằng get_object_or_404, bắt đầu từ những view nhận tham số từ URL như chi tiết sản phẩm, hồ sơ người dùng hay bài viết. Với trang danh sách theo danh mục hoặc tag, dùng get_list_or_404 khi danh mục rỗng cũng nên coi là 404 thay vì trang trống.

Bài viết liên quan

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

get_object_or_404 khác gì objects.get()?

objects.get() ném DoesNotExist khi không tìm thấy bản ghi, và Django không tự chuyển nó thành 404 nên dễ thành lỗi 500. get_object_or_404() bọc sẵn việc chuyển đổi đó, nên view của bạn luôn trả 404 đúng chuẩn khi bản ghi vắng mặt.

Truyền queryset đã lọc sẵn cho get_object_or_404 được không?

Được. Truyền queryset đã lọc sẵn rất hữu ích khi bạn muốn giới hạn phạm vi tìm kiếm, ví dụ chỉ tìm trong các bài đã xuất bản.

Khi nào nên dùng get_list_or_404 thay vì filter()?

Dùng get_list_or_404 khi danh sách rỗng cũng là lỗi, ví dụ trang tag không còn bài nào hoặc danh mục không tồn tại. Nếu trang trống là trạng thái hợp lệ, như kết quả tìm kiếm không có gì, hãy giữ filter() và hiển thị thông báo “không có kết quả” thay vì 404.

Nếu có nhiều object khớp với điều kiện thì sao?

get_object_or_404 vẫn ném MultipleObjectsReturned như get() bình thường, chứ không chuyển thành 404. Vì vậy hãy luôn truy vấn theo trường duy nhất như id, slug hoặc cặp khóa đã đặt unique, tránh dùng điều kiện mơ hồ cho hàm này.

Dùng get_object_or_404 trong API Django REST Framework được không?

Dùng được trong view hàm hoặc view thường, vì DRF sẽ chuyển Http404 thành response 404. Nếu bạn dùng generic view của DRF thì không cần gọi tay: phương thức get_object() có sẵn đã tự trả 404.

💬 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