skip to content

URL Dispatcher

Django's URLconf maps paths to views through path() patterns, include() and named routes resolved with reverse(). Interviewers check you can route, namespace and reverse without hardcoded URLs.

part ofDjangooverview, primer and where to startread it →
on this pageshow

explore

questions

19

In Django, what does include() do when a project's urls.py mounts an app's urls.py under a prefix such as 'comments/'?

level: juniorimportance: must knowfreq 60%

answer

  1. one URLconf delegating to another
  2. the matched part is cut off
  3. remainder resolved by the app
  4. captures flow downward
  5. slash where prefix meets route

basics

~20 s

include() delegates to another URLconf: Django strips the part of the path matched by the prefix, here comments/, and resolves the remainder against the app's urlpatterns. Values captured in the prefix and any extra kwargs reach every included view.

solid answer

~40 s

`path('comments/', include('comments.urls'))` makes the root URLconf match only the prefix; Django cuts that part off and resolves the rest, such as `42/reply/`, against `comments/urls.py`, which declares its routes relative to the mount point. That keeps an app's URLs inside the app so it can be mounted anywhere. `include()` accepts a dotted module path, a module, a list of patterns, or a `(patterns, app_name)` tuple; if the included module sets `app_name`, the mount also gets a namespace. Converters in the prefix, like `threads/<int:thread_id>/comments/`, pass `thread_id` to every included view, and a kwargs dict on the `path()` is passed to all of them. The prefix and the inner routes are joined as plain text, so end the prefix with `/`. The admin is the exception: `path('admin/', admin.site.urls)`, without `include()`.

code

python · 15 lines
python
from django.urls import include, path

from comments import views as comment_views

urlpatterns = [
    path(
        'threads/<int:thread_id>/comments/',
        include([
            path('', comment_views.comment_list),
            path('<int:pk>/', comment_views.comment_detail),
        ]),
    ),
]

# /threads/7/comments/42/ -> comment_detail(request, thread_id=7, pk=42)

go deeper

for a junior

Recall that include() mounts an app's urls.py under a prefix, that the app's routes are written relative to that prefix, and how the admin is mounted.

for a middle

Explain the chop-and-delegate flow, the accepted argument forms, and how prefix captures and extra kwargs reach every included view.

for a senior

Spot the mounting bugs: missing slashes between prefix and route, broad includes shadowing later entries, and prefix captures that break views not written for them.

for a principal

Set conventions for URL ownership in a large project, such as one prefix per app and no cross-app routes, so apps stay mountable and reviews stay local.

## Why per-app URLconfs exist A Django **URLconf** is a module with a `urlpatterns` list. In a small project one list can hold every route, but as soon as the project has several **apps**, each app keeps its own `urls.py` and the project's root URLconf mounts them. The app then describes its routes **relative to wherever it is mounted**, which is what lets a reusable app, such as a comments app, be dropped into different projects and prefixes without edits. ```python # mysite/urls.py - the ROOT_URLCONF from django.contrib import admin from django.urls import include, path urlpatterns = [ path('admin/', admin.site.urls), path('comments/', include('comments.urls')), ] ``` ```python # comments/urls.py from django.urls import path from . import views app_name = 'comments' urlpatterns = [ path('', views.comment_list, name='list'), path('<int:pk>/', views.comment_detail, name='detail'), path('<int:pk>/reply/', views.comment_reply, name='reply'), ] ``` ## How a request flows through include() For a request to `/comments/42/reply/`: 1. The root resolver walks `mysite/urls.py` in order; `admin/` does not match. 2. `comments/` matches as a **prefix**: an `include()` entry is not an endpoint, so it only needs to match the start of the path. 3. Django chops off `comments/` and hands the remaining `42/reply/` to `comments.urls`. 4. The included list is walked in order; `<int:pk>/reply/` matches, and Django calls `comment_reply(request, pk=42)`. 5. If nothing inside matches, the root list carries on with the entries after the `include()`, and the request ends in a 404 only when every entry has declined. ## What include() accepts | Argument | Example | Namespace effect | |---|---|---| | dotted module path | `include('comments.urls')` | uses the module's `app_name`, if set | | module object | `include(comments_urls)` | same as above | | list of patterns | `include([path('edit/', ...), ...])` | none: names stay global | | `(patterns, app_name)` tuple | `include((patterns, 'comments'))` | sets the application namespace | An optional `namespace=` argument names this particular mount, and it is only allowed when an `app_name` exists. Including a plain list is also a handy way to factor out a repeated prefix inside one URLconf. `django.contrib.admin` is the well-known exception. `admin.site.urls` already returns a three-part tuple of patterns, application namespace and instance namespace, so it goes straight into `path('admin/', admin.site.urls)`. Wrapping it in `include()` raises `ImproperlyConfigured`, because `include()` does not accept 3-tuples. ## Captured values and extra options flow down - A converter in the prefix is captured once and passed to **every** included view: with `path('threads/<int:thread_id>/comments/', include('comments.urls'))`, each comments view must accept `thread_id`. - A dictionary given as the third argument, `path('comments/', include('comments.urls'), {'moderated': True})`, is passed to every view in the included URLconf, whether or not it expects it. - Both are useful for mounting one app with context, but they widen every included view's signature, so they suit apps designed for it. ## Common mistakes - **Missing slash between prefix and route.** Prefix and inner route are joined as plain text. With `path('comments', include('comments.urls'))`, comment 42 lives at `/comments42/`, not `/comments/42/`. - **A leading slash in inner routes.** Every path already starts with `/`; Django's `urls.W002` check warns about a route that begins with one. - **`$` at the end of a regex include prefix.** With `re_path()`, a prefix ending in `$` can never have anything after it; check `urls.W001` warns about it. - **`include(admin.site.urls)`.** Raises `ImproperlyConfigured`; pass the tuple to `path()` directly. - **A broad include mounted at `''` near the top.** It is tried in place, so its routes can answer paths meant for entries listed below it. - **`i18n_patterns()` inside an included URLconf.** Django refuses it; language-prefixed patterns belong in the root URLconf. ## Per-app URLconfs in practice A few habits keep included URLconfs easy to mount and to review: - **Keep every route in the app relative.** No route inside `comments/urls.py` should mention `comments/`; the project decides the prefix. - **Give each app its own `app_name`.** Names such as `list` and `detail` are then safe to reuse across apps, because they are addressed as `comments:list` and `events:list`. - **Mount each app once, at a stable prefix,** unless it is deliberately deployed twice under separate instance namespaces. - **Keep project-level routes in the root URLconf.** The home page, health checks and the admin belong there, not inside an app that might be mounted elsewhere. - **Put narrow includes before broad ones.** An include at `''` should come last, after every prefix that could otherwise be captured by its routes. These are conventions rather than rules Django enforces, but they are what makes the chop-and-delegate model pay off. ## What include() does not do `include()` imports the app's URLconf module, but it does not copy its routes into the root list; `path()` turns its result into one resolver entry that delegates at request time. It also does not invent a namespace: route names stay global unless the included URLconf provides an `app_name`.

  • Why does Django reject include(admin.site.urls) when path('admin/', admin.site.urls) works?
    `admin.site.urls` returns a 3-tuple: the patterns, the application namespace `admin` and the site's instance name. `include()` returns exactly that shape itself and only accepts a module, a list or a `(patterns, app_name)` 2-tuple, so it raises `ImproperlyConfigured` for a 3-tuple. `path()` accepts the 3-tuple directly as an include.
  • In Django, what happens when a request matches an include() prefix but none of the included routes?
    The included resolver reports no match and the outer URLconf continues with the entries listed after the `include()`. The request gets a 404 only when every entry in the root list has declined, so a later entry can still serve a path under the same prefix.

saying these in an interview costs you the question

  • include() passes the full request path, prefix included, to the app's URLconf.
  • The app's routes must repeat the comments/ prefix to match.
  • Values captured in the include prefix are dropped before the included views run.
  • The admin must be mounted with include(admin.site.urls).
  • include() copies the app's routes into the root list when the project starts.
open as a page

In Django's path() routes, what do the built-in str, int, slug, uuid and path converters match, and what does the view receive?

level: juniorimportance: must knowfreq 62%

basics

~20 s

str (the default) matches one non-empty segment without a slash; int matches digits and passes an int; slug matches ASCII letters, digits, hyphens and underscores; uuid matches a lowercase dashed UUID and passes uuid.UUID; path matches anything non-empty, slashes included.

open as a page

In a Django URLconf listing path('events/<slug:slug>/') before path('events/new/'), which view serves /events/new/, and why?

level: juniorimportance: must knowfreq 55%

basics

~20 s

The slug route serves it: Django tries urlpatterns in list order and calls the first pattern that matches, and 'new' is a valid slug. Listing the fixed events/new/ route above the converter route fixes it.

open as a page

In Django, what happens when a view raises Http404, and how does the response differ between DEBUG=True and DEBUG=False?

level: juniorimportance: must knowfreq 62%

basics

~20 s

Raising Http404 abandons the view and Django builds a 404. With DEBUG=True it shows a technical page listing the URL patterns tried; with DEBUG=False it calls handler404, which renders a 404.html template or a plain Not Found page.

open as a page

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%

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/.

open as a page

In Django, what is the difference between the app_name set in an app's urls.py and the namespace argument passed to include()?

level: middleimportance: must knowfreq 52%

basics

~20 s

app_name is the application namespace, shared by every mount of that URLconf; include(..., namespace=...) is the instance namespace, identifying one mount. The instance defaults to app_name, and routes are looked up as 'namespace:name', such as 'comments:detail'.

open as a page

In Django, why does success_url = reverse('job-list') on a class-based view break at import time, and what does reverse_lazy() change?

level: middleimportance: must knowfreq 55%

basics

~20 s

Class attributes run when views.py is imported, usually while the URLconf that imports it is still loading, so reverse() finds no patterns and raises ImproperlyConfigured. reverse_lazy() returns a lazy string that reverses only when converted to text, after loading.

open as a page

How do you mount one reusable Django comments app at both /news/comments/ and /support/comments/ without its URL names clashing?

level: middleimportance: should knowfreq 38%

basics

~10 s

Keep app_name = 'comments' in the app and include it twice with different instance namespaces, such as namespace='news-comments' and namespace='support-comments'. The app keeps reversing 'comments:...' and Django picks the instance from the current request.

open as a page

In Django, with APPEND_SLASH at its default, what happens to a request for /events/launch-party when only events/<slug:slug>/ is routed?

level: middleimportance: should knowfreq 47%

basics

~10 s

APPEND_SLASH defaults to True, so CommonMiddleware replaces the 404 with a 301 redirect to /events/launch-party/, because the slashless path matches nothing and the slashed one resolves. Without CommonMiddleware the setting does nothing.

open as a page

In Django, when would you reach for re_path() instead of path(), and how does switching change the arguments your view receives?

level: middleimportance: should knowfreq 44%

basics

~20 s

Use re_path() when a segment needs a constraint that path() converters cannot express and a custom converter is not worth writing. Its captures reach the view as strings: named groups as keyword arguments, unnamed groups positionally.

open as a page

In a Django root URLconf, what signatures must custom handler404 and handler500 views have, and why does the 500 page get so little context?

level: middleimportance: should knowfreq 44%

basics

~20 s

handler400, handler403 and handler404 take (request, exception); handler500 takes only (request). Django renders the default 500.html with an empty context because whatever broke the view may also break a template that uses the request or database.

open as a page

In Django, what does django.urls.resolve() return for a path, and what can its ResolverMatch tell you?

level: middleimportance: should knowfreq 36%

basics

~20 s

resolve() matches a path against the URLconf and returns a ResolverMatch naming the view, its args and kwargs, url_name, view_name, namespaces and the route template; an unmatched path raises Resolver404. Django stores the same object on request.resolver_match.

open as a page

In Django, what can redirect() take after a job application is submitted, and how does it decide whether a string is a view name?

level: middleimportance: should knowfreq 42%

basics

~20 s

redirect() takes a model instance (via get_absolute_url()), a view name with arguments (reversed), or a URL. Strings are reversed first; on failure, one containing '/' or '.' is used as a URL, otherwise NoReverseMatch propagates.

open as a page

On a Django ticketing site, why is a custom path converter whose to_python() loads the Event row from the database a risky design?

level: seniorimportance: should knowfreq 20%

basics

~20 s

to_python() runs during URL resolution, outside the view: a missing row raises DoesNotExist, which is not ValueError and becomes a 500; it cannot see the request to filter by user; and under ASGI a sync query there raises SynchronousOnlyOperation.

open as a page

After a Django job-board view saves an application, redirecting to the application page raises NoReverseMatch — how do you read the error and find the cause?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Read the NoReverseMatch message: 'not a valid view function or pattern name' means a wrong name or namespace; 'with keyword arguments ... not found. N pattern(s) tried' means the values do not fit, such as pk=None from an unsaved object.

open as a page

How do you write and register a custom path converter in Django, and what does raising ValueError in its to_python() or to_url() do?

level: middleimportance: nice to knowfreq 28%

basics

~20 s

Write a class with a regex attribute, to_python() and to_url(), then call django.urls.register_converter(cls, 'name') before any path() uses name:.... ValueError in to_python() makes that pattern not match; in to_url() it makes reverse() skip the pattern.

open as a page

In Django 5.2 and later, how do reverse()'s query and fragment arguments help build a redirect to a filtered job list?

level: middleimportance: nice to knowfreq 30%

basics

~10 s

Since Django 5.2, reverse('job-list', query={'status': 'open', 'page': 2}, fragment='job-42') returns '/jobs/?status=open&page=2#job-42'. The query is URL-encoded, may be a QueryDict such as request.GET, and the fragment is appended as given.

open as a page

A Django comments app is mounted at /news/comments/ and /support/comments/, and posting a news comment redirects into the support section — why, and how do you fix it?

level: seniorimportance: nice to knowfreq 22%

basics

~10 s

The view's redirect('comments:list') reverses with no current app, so Django falls back to the default instance or, lacking one, the last included mount: support. Reverse with current_app=request.resolver_match.namespace and redirect to that URL.

open as a page

In a multi-tenant Django project that sets request.urlconf per tenant hostname, what changes for resolution, reverse() and the error handlers, and what breaks outside a request?

level: seniorimportance: nice to knowfreq 22%

basics

~10 s

Django resolves against request.urlconf instead of ROOT_URLCONF and from then on reverse(), {% url %} and the handler404/500 lookup follow it; code outside a request falls back to ROOT_URLCONF unless urlconf= is passed.

open as a page