Hardcode URL dạng “/blog/…” rải khắp view và template là thói quen khiến dự án Django khó bảo trì nhất. Chỉ cần đổi một đường dẫn trong urls.py, hàng chục chỗ redirect và link sẽ vỡ cùng lúc. Giải pháp là đặt tên cho mỗi URL pattern rồi dùng reverse() trong view, reverse_lazy() trong class-based view và {% url %} trong template — đổi URL ở một chỗ duy nhất, toàn bộ dự án tự cập nhật theo.

Vấn đề

Giả sử bạn có view tạo bài viết và sau khi lưu xong muốn chuyển người dùng sang trang chi tiết. Cách nhiều người mới viết là ghép chuỗi thủ công:

# views.py — cách hardcode, đừng làm theo
def create_post(request):
post = Post.objects.create(...)
return redirect(f"/blog/{post.slug}/")
<a href="/blog/{{ post.slug }}/">Xem bài viết</a>

Vấn đề xuất hiện khi bạn đổi đường dẫn từ blog/ sang bai-viet/ để chuẩn SEO, hoặc thêm tiền tố ngôn ngữ /vi/ vào toàn bộ URL. Lúc đó mọi redirect và link hardcode đều trỏ sai, người dùng gặp lỗi 404, còn bạn phải đi tìm và sửa từng chỗ một. Lỗi này thường chỉ lộ ra khi người dùng thật bấm vào link.

Giải pháp

Bước đầu tiên là đặt tên cho URL pattern trong urls.py. Sau đó, mọi nơi cần link tới trang đó chỉ gọi tên thay vì gõ đường dẫn:

# urls.py
from django.urls import path
from . import views
urlpatterns = [
path("blog/<slug:slug>/", views.post_detail, name="post-detail"),
path("blog/", views.post_list, name="post-list"),
]
# views.py
from django.shortcuts import redirect
from django.urls import reverse
# reverse(): tra ve duong dan day du tu ten pattern
url = reverse("post-detail", kwargs={"slug": post.slug})
# ket qua: "/blog/hello-world/"
# redirect() nhan thang ten pattern, khoi goi reverse()
return redirect("post-detail", slug=post.slug)
# Trong template, dung tag {% url %} trong thuoc tinh href
{% url 'post-detail' slug=post.slug %}

reverse() nhận tên pattern và tham số rồi trả về đường dẫn đầy đủ. Truyền tham số theo tên bằng kwargs hoặc theo vị trí bằng args đều được, miễn là khớp với pattern trong urls.py. Nếu gõ sai tên, Django báo lỗi NoReverseMatch ngay lúc chạy thay vì âm thầm trả về link hỏng — lỗi lộ sớm luôn tốt hơn lỗi 404 lúc người dùng bấm vào.

reverse_lazy() dùng cho những chỗ Django tính giá trị ngay lúc import module, khi URLconf chưa kịp nạp — điển hình là thuộc tính success_url của class-based view. Dùng reverse() ở đó sẽ gây lỗi vì bảng URL chưa sẵn sàng, còn reverse_lazy() hoãn việc tra cứu tới lúc thực sự cần:

# views.py — class-based view
from django.urls import reverse_lazy
from django.views.generic import CreateView
class PostCreateView(CreateView):
model = Post
fields = ["title", "content"]
# Dung reverse() o day se loi vi URLconf chua nap xong
success_url = reverse_lazy("post-list")

Lưu ý cuối: khi dự án có nhiều app, đặt app_name trong urls.py của từng app rồi gọi tên dạng “blog:post-detail”. Cách này tránh hai app trùng tên pattern và cho biết ngay link thuộc về app nào.

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

Quét codebase để tìm chuỗi “/…/” hardcode trong redirect(), HttpResponseRedirect và template, rồi thay từng chỗ bằng tên pattern. Bắt đầu từ các view tạo/sửa/xóa dữ liệu vì đó là nơi redirect nhiều nhất và lỗi 404 gây khó chịu nhất. Sau khi đổi xong, chạy lại test và bấm thử các luồng chính — đăng bài, sửa bài, xóa bài — để chắc mọi redirect vẫn chạy đúng.

Bài viết liên quan

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

Khi nào dùng reverse(), khi nào dùng reverse_lazy()?

Dùng reverse() trong hàm view và mọi chỗ chạy sau khi Django đã nạp xong URLconf. Dùng reverse_lazy() cho thuộc tính ở cấp class như success_url, vì lúc Python import module thì URLconf chưa sẵn sàng.

reverse() có dùng được với query string không?

Không trực tiếp. reverse() chỉ tạo phần path, muốn thêm ?page=2 thì nối chuỗi sau kết quả, ví dụ reverse(“post-list”) + “?page=2”.

Đổi tên URL pattern có phải sửa lại mọi chỗ gọi reverse() không?

Có — tên pattern là “hợp đồng” giữa urls.py và code gọi nó, đổi tên thì phải sửa mọi chỗ gọi theo tên mới. Nhưng đổi đường dẫn thật thì không cần sửa gì, miễn tên pattern giữ nguyên. Vì vậy hãy đặt tên pattern cẩn thận ngay từ đầu.

Template tag {% url %} báo lỗi NoReverseMatch thì kiểm tra gì trước?

Kiểm tra ba thứ: tên pattern có trong urls.py không, đã nạp đúng app chứa nó chưa, và tham số truyền vào có đủ, đúng tên không. Lỗi thường gặp nhất là thiếu một tham số bắt buộc như slug.

💬 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