In Django, what does include() do when a project's urls.py mounts an app's urls.py under a prefix such as 'comments/'?
answer
- one URLconf delegating to another
- the matched part is cut off
- remainder resolved by the app
- captures flow downward
- slash where prefix meets route
basics
~20 sinclude() 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 linesfrom 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
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.
Explain the chop-and-delegate flow, the accepted argument forms, and how prefix captures and extra kwargs reach every included view.
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.
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.