URL Routing and Pattern Matching
In this tutorial, you will build a robust URL routing system for a blog application. You will learn how to map URL paths to view functions, capture dynamic data using built-in converters, modularize your configuration, and create custom path converters.
Prerequisites
To follow this tutorial, you need the django.urls and django.urls.converters modules available in your environment. You should also have a basic understanding of Python functions and decorators.
Step 1: Defining Basic URL Patterns
The foundation of routing in this codebase is the URLPattern class, which is typically created using the path() function. Each pattern maps a route string to a callback (view function).
Create a file named urls.py and define your first patterns:
from django.urls import path
from . import views
urlpatterns = [
# Matches 'articles/2023/' exactly
path('articles/2023/', views.special_case_2023, name='articles-2023'),
]
When you use path(), it creates a URLPattern instance. The URLPattern stores the route, the callback, and an optional name. Note that you should not include a leading slash in your routes (e.g., use articles/ instead of /articles/), as Django's URLPattern.check() will issue a warning if it detects one.
Step 2: Capturing Data with Built-in Converters
To handle dynamic URLs like /articles/1/ or /articles/my-first-post/, you use path converters. These are classes like IntConverter and SlugConverter that match specific patterns and convert them to Python types.
Update your urlpatterns:
from django.urls import path
from . import views
urlpatterns = [
path('articles/2023/', views.special_case_2023, name='articles-2023'),
# Captures an integer and passes it to the view as 'year'
path('articles/<int:year>/', views.year_archive, name='articles-year'),
# Captures a slug (letters, numbers, hyphens, underscores)
path('articles/<int:year>/<slug:slug>/', views.article_detail, name='article-detail'),
]
Here is how the converters work internally:
IntConverter: Uses the regex[0-9]+and callsto_python(value)to return anint.SlugConverter: Uses the regex[-a-zA-Z0-9_]+and returns a string.StringConverter: The default (e.g.,<name>), matches any non-empty string except the path separator/.
Step 3: Modularizing with URLResolver and include()
As your application grows, you should split your URLs into multiple modules. The include() function allows you to reference other URL configurations, which are handled by the URLResolver class.
In your main project/urls.py:
from django.urls import include, path
urlpatterns = [
# Delegates all paths starting with 'blog/' to the blog app's urls.py
path('blog/', include('blog.urls')),
]
The URLResolver will match the blog/ prefix and then pass the remaining part of the URL to the blog.urls module for further matching. This creates a tree structure of patterns that URLResolver.resolve() traverses recursively.
Step 4: Creating a Custom Path Converter
If the built-in converters don't meet your needs—for example, if you need to match a Base64-encoded ID—you can create a custom converter. A converter must provide a regex attribute and the methods to_python() and to_url().
import base64
from django.urls import path, register_converter
class Base64Converter:
regex = r'[a-zA-Z0-9+/]*={0,2}'
def to_python(self, value):
# Converts the URL string into a bytes object for the view
return base64.b64decode(value, validate=True)
def to_url(self, value):
# Converts bytes back into a string for use in reverse()
return base64.b64encode(value).decode('ascii')
# Register the converter so it can be used in path()
register_converter(Base64Converter, 'base64')
urlpatterns = [
path('archive/<base64:data>/', views.archived_data),
]
If to_python() raises a ValueError, the URLPattern will treat it as a non-match and continue searching other patterns.
Step 5: Manually Resolving URLs
Sometimes you need to inspect which view matches a specific path programmatically. You can use the resolve() function, which returns a ResolverMatch object.
from django.urls import resolve
# Simulate an incoming request path
match = resolve('/blog/articles/2023/my-first-post/')
print(match.func) # The view function: views.article_detail
print(match.kwargs) # Captured data: {'year': 2023, 'slug': 'my-first-post'}
print(match.url_name) # The name of the pattern: 'article-detail'
print(match.route) # The matched route: 'blog/articles/<int:year>/<slug:slug>/'
The ResolverMatch object contains everything Django needs to execute the view, including the captured_kwargs from the converters and any extra_kwargs passed via the path() function.
Summary
You have now built a complete routing system that:
- Maps static paths to views using
URLPattern. - Uses
IntConverterandSlugConverterto extract typed data from URLs. - Organizes patterns hierarchically using
URLResolver. - Extends the system with a custom
Base64Converter. - Inspects matches using
ResolverMatch.
For next steps, explore how to generate URLs from these patterns using the reverse() function, which utilizes the to_url() methods of your converters to reconstruct valid paths.