In Django, how do you build the URL of a route named 'job-detail' for job 42 in a view and in a template?
answer
- look the route up by name
- positional or keyword, not both
- values pass through the converter
- the template tag uses the same lookup
- a path, not a full URL
basics
~10 sIn Python call django.urls.reverse('job-detail', kwargs={'pk': 42}) or args=[42]; in a template write {% url 'job-detail' pk=42 %}. Both find the route by its name= and return its path, such as /jobs/42/.
solid answer
~30 sGive the route a name, `path('jobs/<int:pk>/', views.job_detail, name='job-detail')`, and never hardcode the path again. In Python, `reverse('job-detail', kwargs={'pk': 42})` or `reverse('job-detail', args=[42])` returns `'/jobs/42/'`; passing both `args` and `kwargs` raises `ValueError`. Each value goes through the route's converter `to_url()` and must fit its regex, otherwise `reverse()` raises `NoReverseMatch`. In templates, `{% url 'job-detail' pk=job.pk %}` or `{% url 'job-detail' job.pk %}` does the same lookup. The result is a path, already URL-quoted and prefixed with the script prefix when the site is mounted under one, not a full URL with host. `redirect('job-detail', pk=42)` wraps the same lookup in a redirect response.
code
python · 9 linesfrom django.db import models
from django.urls import reverse
class Job(models.Model):
title = models.CharField(max_length=200)
def get_absolute_url(self):
return reverse('job-detail', kwargs={'pk': self.pk})go deeper
Recall how to name a route with name=, build its URL with reverse() in Python and {% url %} in templates, and pass args or kwargs.
Explain how reverse() picks a candidate pattern, runs converters' to_url(), adds the script prefix and raises NoReverseMatch when nothing fits.
Build URLs only from names across views, templates, emails and models, and rely on tests that render pages so NoReverseMatch surfaces before release.
Treat route names as an internal API: choose naming conventions and namespaces that let teams rename paths without touching links.
## Naming a route In Django, **reversing** means building a URL from a route's name instead of writing the path by hand. It starts in the URLconf: ```python # jobs/urls.py from django.urls import path from . import views urlpatterns = [ path('jobs/', views.job_list, name='job-list'), path('jobs/<int:pk>/', views.job_detail, name='job-detail'), path('jobs/<int:pk>/apply/', views.job_apply, name='job-apply'), ] ``` The `name=` argument is the stable handle. If the path later becomes `positions/<int:pk>/`, every link built from `'job-detail'` follows automatically. ## reverse() in Python code ```python from django.urls import reverse reverse('job-list') # '/jobs/' reverse('job-detail', args=[42]) # '/jobs/42/' reverse('job-detail', kwargs={'pk': 42}) # '/jobs/42/' ``` What `reverse()` does, step by step: 1. Looks up every pattern registered under the name in the root URLconf, including those inside `include()`s. 2. Keeps the candidates whose parameters fit what was passed: the number of positional `args`, or the exact set of `kwargs` names. 3. Converts each value with the route's converter `to_url()` method, so `42` becomes `'42'`. 4. Substitutes the text into the route and checks it against the route's regex, so a value the converter would never have matched is rejected. 5. Adds the **script prefix**, which is `/` unless the site is served under a sub-path, and URL-quotes the result. If no candidate survives, `reverse()` raises `django.urls.NoReverseMatch`. ## {% url %} in templates Templates use the built-in `url` tag, which calls the same machinery: ```django <a href="{% url 'job-detail' pk=job.pk %}">{{ job.title }}</a> <a href="{% url 'job-apply' job.pk %}">Apply</a> ``` The route name is a quoted string literal; an unquoted word is read as a template variable holding the name. Arguments may be positional or keyword, but not mixed. A failed lookup raises `NoReverseMatch` while the template renders. ## Picking the right tool | Where the URL is needed | Tool | |---|---| | a link in a template | `{% url 'job-detail' pk=job.pk %}` | | a redirect after a POST in a view | `redirect('job-detail', pk=job.pk)` | | a URL string in view code | `reverse('job-detail', kwargs={'pk': job.pk})` | | a model's canonical URL | `get_absolute_url()` returning `reverse(...)` | | a class attribute or decorator argument | `reverse_lazy(...)` | | a full URL with scheme and host, as in an email | `request.build_absolute_uri(reverse(...))` | ## Rules and gotchas - **Never mix `args` and `kwargs`.** `reverse()` raises `ValueError` if both are given. - **Pass what the converter expects.** `<int:pk>` accepts `42` or `'42'` but not `None` or `'abc'`; those end in `NoReverseMatch`. - **It returns a path.** No scheme or host is added; the script prefix is, so a site served under `/careers` gets `/careers/jobs/42/`. - **Do not quote the result again.** The output is already URL-quoted; applying `urllib.parse.quote()` on top double-encodes it. - **Prefer names over callables.** `reverse(views.job_detail)` works for un-namespaced routes, but Django's docs discourage it and it cannot reach namespaced views. - **Keep names distinctive.** Un-namespaced names are global across the project; generic names such as `detail` invite collisions, which app namespaces solve. - **Some regex routes cannot be reversed.** A `re_path()` pattern that uses `|` alternation can match requests, but `reverse()` cannot build URLs from it. ## Reversing outside views and templates The same lookup is used well beyond page links: - **Tests** request pages with `self.client.get(reverse('job-detail', kwargs={'pk': job.pk}))`, so a renamed path does not break the suite. - **Models** expose `get_absolute_url()`, which the admin's "view on site" link and `redirect(job)` both use. - **Emails and background work** have no request: there is no host to add, and the script prefix falls back to `/` unless `FORCE_SCRIPT_NAME` is set, so build absolute links from a configured base URL. - **Namespaced apps** are reversed with a prefix, as in `reverse('jobs:job-detail', ...)`, when their URLconf sets `app_name`. | Direction | Function | Input | Output | |---|---|---|---| | name to path | `reverse()` | route name and arguments | `'/jobs/42/'` | | path to view | `resolve()` | `'/jobs/42/'` | the view, its name and `{'pk': 42}` | ## Why it matters in practice Hardcoded paths drift silently: a renamed route leaves stale links in templates, emails and redirects that only a user discovers. Reversed URLs fail loudly with `NoReverseMatch` the first time the page renders or the view runs, which a test suite exercising each page catches before deployment.
- What does Django's reverse() add when the site runs under a sub-path such as /careers?It prepends the script prefix, which Django sets for each request from the server's `SCRIPT_NAME` (or from `FORCE_SCRIPT_NAME` if configured). `reverse('job-detail', kwargs={'pk': 42})` then returns `/careers/jobs/42/`. Outside a request, such as in a management command, the prefix is `/` unless `FORCE_SCRIPT_NAME` is set, so links built there may lack it.
- How does Django's reverse() choose between two routes that share one name?It considers every pattern with that name and returns the first whose parameters fit the arguments: the number of positional args, or the exact keyword names, and values that pass the converters. So `'job-list'` with no arguments and `'job-list'` with `page` can coexist. If several fit, the pattern registered last in `urlpatterns` wins.
saying these in an interview costs you the question
- reverse() returns a full URL including the scheme and host.
- You can pass args and kwargs to reverse() together for flexibility.
- reverse() finds a route by matching the path string you pass it.
- {% url job-detail pk=job.pk %} with an unquoted name is the normal syntax.
- reverse() output should be passed through urllib.parse.quote() before use.