Hardcoding URLs like “/blog/…” across views and templates is the habit that makes a Django project hardest to maintain. Change one path in urls.py and dozens of redirects and links break at once. The fix is to name every URL pattern, then use reverse() in views, reverse_lazy() in class-based views, and {% url %} in templates — change the URL in one place and the whole project updates itself.

The Problem

Say you have a view that creates a post and, once saved, should send the user to the detail page. The way many beginners write it is to build the string by hand:

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

The problem appears when you change the path from blog/ to bai-viet/ for SEO, or add a language prefix like /vi/ to every URL. Then all hardcoded redirects and links point to the wrong place, users hit 404 errors, and you have to hunt down and fix each one by hand. This bug usually only shows up when a real user clicks a link.

The Solution

The first step is to name the URL pattern in urls.py. After that, everywhere that needs a link to that page just calls the name instead of typing the path:

# 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() takes the pattern name and its parameters and returns the full path. You can pass parameters by name with kwargs or by position with args, as long as they match the pattern in urls.py. If you mistype the name, Django raises a NoReverseMatch error right away instead of silently returning a broken link — a bug that surfaces early is always better than a 404 when a user clicks.

reverse_lazy() is for places where Django evaluates the value as soon as the module is imported, before the URLconf has loaded — typically the success_url attribute of a class-based view. Using reverse() there fails because the URL table is not ready yet, while reverse_lazy() delays the lookup until it is actually needed:

# 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")

One last note: when a project has many apps, set app_name in each app’s urls.py, then call names like “blog:post-detail”. This avoids two apps sharing the same pattern name and shows at a glance which app a link belongs to.

Apply It to Your Project

Scan your codebase for hardcoded “/…/” strings in redirect(), HttpResponseRedirect, and templates, and replace each one with a pattern name. Start with the views that create, edit, and delete data, since that is where redirects are most common and a 404 is most annoying. Once you finish, rerun your tests and click through the main flows — create, edit, delete a post — to make sure every redirect still works.

Related Posts

Frequently Asked Questions

When should I use reverse(), and when reverse_lazy()?

Use reverse() inside function views and anywhere that runs after Django has finished loading the URLconf. Use reverse_lazy() for class-level attributes like success_url, because when Python imports the module the URLconf is not ready yet.

Can reverse() build a query string?

Not directly. reverse() only builds the path, so to add ?page=2, append the string after the result, for example reverse(“post-list”) + “?page=2”.

If I rename a URL pattern, do I have to update every reverse() call?

Yes — the pattern name is the “contract” between urls.py and the code that calls it, so renaming it means updating every call that uses the new name. But changing the actual path needs no updates at all, as long as the name stays the same. That is why you should choose pattern names carefully from the start.

The {% url %} template tag raises NoReverseMatch — what should I check first?

Check three things: whether the pattern name exists in urls.py, whether the app containing it has been loaded, and whether the parameters passed are complete and correctly named. The most common mistake is a missing required parameter such as slug.

References