skip to content

In Django, how do you build the URL of a route named 'job-detail' for job 42 in a view and in a template?

level: juniorimportance: must knowfreq 65%

answer

  1. look the route up by name
  2. positional or keyword, not both
  3. values pass through the converter
  4. the template tag uses the same lookup
  5. a path, not a full URL

basics

~10 s

In 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 s

Give 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 lines
python
from 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

for a junior

Recall how to name a route with name=, build its URL with reverse() in Python and {% url %} in templates, and pass args or kwargs.

for a middle

Explain how reverse() picks a candidate pattern, runs converters' to_url(), adds the script prefix and raises NoReverseMatch when nothing fits.

for a senior

Build URLs only from names across views, templates, emails and models, and rely on tests that render pages so NoReverseMatch surfaces before release.

for a principal

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.