Skip to content

Django URL Routing

The routing layer (URLconf) is Django’s configuration for mapping URLs to view functions. This article covers how to define routes, regex matching rules, named and unnamed groups, reverse URL resolution, route distribution (include), and the path / re_path syntax introduced in Django 2.x.

Routing Layer

What is Routing?

A route is the mapping relationship between a request URL and a view function.

Route Configuration

from django.conf.urls import url
from django.contrib import admin
from app01 import views

urlpatterns = [
    url(regex, view_function, kwargs, name),
]
    # regex       — a regular expression string
    # view        — a callable (usually a view function) or a dotted path string to one
    # kwargs      — optional default arguments passed to the view (as a dict)
    # name        — an optional alias for the route

# Regex rules:
#   1. Patterns are matched top-to-bottom; the first match wins and no further
#      patterns are tried.
#   2. To capture a value from the URL, wrap the relevant part in parentheses (grouping).
#   3. Do not add a leading slash; every URL already has one. Use ^articles, not ^/articles.
#   4. The 'r' prefix on each pattern string is optional but recommended.
#
#   In settings.py, APPEND_SLASH controls whether Django appends a trailing slash:
#       APPEND_SLASH = True   (default — enabled)

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    # Homepage
    url(r'^$', views.home),
    # Route matching
    url(r'^test/$', views.test),
    url(r'^testadd/$', views.testadd),
    # Catch-all / error page (for reference)
    url(r'', views.error),
]

Grouping

# What is grouping?
#   Simply put, grouping means wrapping part of a regex in parentheses.
#   There are two kinds: unnamed groups and named groups.

# Unnamed groups:
#   An unnamed group passes the matched content to the view as a positional argument.
    urlpatterns = [
        url(r'^admin/', admin.site.urls),

        # The regex below matches paths like: article/<number>/
        # The matched group value is passed as a positional argument to the view.
        # Number of positional args equals the number of groups.
        url(r'^aritcle/(\d+)/$', views.article),
    ]
    # views.py must accept an extra parameter to receive the captured group value
    def article(request, article_id):
        return HttpResponse('Article with id %s ...' % article_id)

# Named groups:
#   A named group passes the matched content to the view as a keyword argument.
    urlpatterns = [
        url(r'^admin/', admin.site.urls),

        # The regex below matches paths like: article/<number>/
        # The matched group is passed as a keyword argument: article_id=<matched number>
        url(r'^aritcle/(?P<article_id>\d+)/$', views.article),
    ]
    # The parameter name in views.py must match the name defined in urls.py
    def article(request, article_id):
        return HttpResponse('Article with id %s ...' % article_id)

# Difference between named and unnamed groups:
#   Both capture URL parameters and pass them to the view function.
#   Unnamed groups use positional arguments; named groups use keyword arguments.
#   Named and unnamed groups cannot be mixed, but each type can appear multiple times.

Reverse URL Resolution

# What is reverse URL resolution?
#   Using certain methods to obtain a result that can be used directly to access
#   the corresponding URL and trigger the view function.

# Configuration:
#   First, give the route an alias (name)
    url(r'^func_kkk/', views.func, name='ooo')

#   Backend reverse resolution
    from django.shortcuts import render, HttpResponse, redirect, reverse
    reverse('ooo')

#   Frontend reverse resolution (in templates)
    <a href="{% url 'ooo' %}">link text</a>

#   Note: aliases must be unique

# Reverse resolution with named and unnamed groups:

#   Unnamed group reverse resolution:
    url(r'^index/(\d+)/', views.index, name='xxx')
    # Frontend:
    {% url 'xxx' 123 %}
    # Backend:
    reverse('xxx', args=(1,))

#   Named group reverse resolution:
    url(r'^func/(?P<year>\d+)/', views.func, name='ooo')
    # Frontend:
    <a href="{% url 'ooo' year=123 %}">method 1</a>
    <a href="{% url 'ooo' 123 %}">method 2</a>
    # Backend:
    reverse('ooo', kwargs={'year': 123})   # method 1
    reverse('ooo', args=(111,))            # method 2

# When using reverse resolution in the backend, import the module first:
from django.shortcuts import reverse

Route Distribution

# Each Django app can have its own templates folder, urls.py, and static folder.

# In a company, a project may have many modules, each maintained by different people.
# When a Django project has many URLs, the main urls.py becomes very long and hard
# to maintain. Route distribution lets us split URL definitions into per-app files.

# With route distribution, the main urls.py no longer maps URLs directly to view
# functions. Instead it identifies which app a URL belongs to and delegates it there.
# Once a prefix matches, processing stops and the matched app handles the rest.

# Main urls.py (two equivalent approaches):
    from app01 import urls as app01_urls
    from app02 import urls as app02_urls
    urlpatterns = [
        url(r'^admin/', admin.site.urls),

        # Approach 1: import and reference
        url(r'^app01/', include(app01_urls)),   # All URLs starting with app01 go to app01
        url(r'^app02/', include(app02_urls)),   # All URLs starting with app02 go to app02

        # Approach 2: use string path (recommended)
        url(r'^app01/', include('app01.urls')),
        url(r'^app02/', include('app02.urls')),
        # Important: do NOT add a $ at the end of the prefix pattern in the main urls.py
    ]

# Sub-route: app01/urls.py
    from django.conf.urls import url
    from app01 import views
    urlpatterns = [
        url(r'^reg/', views.reg)
    ]

# Sub-route: app02/urls.py
    from django.conf.urls import url
    from app02 import views
    urlpatterns = [
        url(r'^reg/', views.reg)
    ]

Namespaces

# When multiple apps define routes with the same alias, reverse resolution cannot
# automatically determine the correct prefix. Namespaces solve this.

# Main urls.py
    url(r'^app01/', include('app01.urls', namespace='app01')),
    url(r'^app02/', include('app02.urls', namespace='app02'))

# app01/urls.py
    urlpatterns = [
        url(r'^reg/', views.reg, name='reg')
    ]
# app02/urls.py
    urlpatterns = [
        url(r'^reg/', views.reg, name='reg')
    ]

# Backend resolution
    reverse('app01:reg')
    reverse('app02:reg')

# Frontend resolution (templates)
    {% url 'app01:reg' %}
    {% url 'app02:reg' %}

# In practice, as long as alias names don't conflict you don't need namespaces.
# A common convention is to prefix aliases with the app name:
    urlpatterns = [
        url(r'^reg/', views.reg, name='app01_reg')
    ]
    urlpatterns = [
        url(r'^reg/', views.reg, name='app02_reg')
    ]

Pseudo-Static URLs

# Make a dynamic page appear to be a static page.
# The goal is to boost the site's SEO ranking.
# However, no amount of optimization beats paid advertising.
    urlpatterns = [
        url(r'^reg.html', views.reg, name='app02_reg')
    ]

Virtual Environments

In normal development, each project gets its own dedicated Python interpreter
environment that contains only the packages the project actually uses.

On Linux: install only what you need, when you need it.

Virtual environments:
    Creating a virtual environment is like downloading a fresh, clean Python
    interpreter. Don't create too many — each one consumes disk space.

Tips:
    Every project depends on many packages, often at specific versions.
    How should you install them? One by one?

    In development, each project ships with a requirements.txt file that lists
    all required packages and their versions. A single command installs everything:

        pip install -r requirements.txt
Last updated on