Skip to main content

URL Dispatching and Routing Logic

The URL dispatching system in this codebase is built on a recursive tree structure of resolvers and patterns. When a request enters the system, the URLResolver at the root of the application (defined by ROOT_URLCONF) traverses this tree to find a matching view.

The Routing Tree: Resolvers and Patterns

The routing logic distinguishes between branch nodes and leaf nodes in the URL configuration:

  • URLResolver (Branch Node): Represents a collection of URL patterns, often created via include(). It can contain other URLResolver instances or URLPattern instances.
  • URLPattern (Leaf Node): Represents a final mapping to a view function or class.

In django/urls/conf.py, the _path function determines which class to instantiate based on the view argument:

if isinstance(view, (list, tuple)):
# For include(...) processing, create a branch node
pattern = Pattern(route, is_endpoint=False)
urlconf_module, app_name, namespace = view
return URLResolver(pattern, urlconf_module, kwargs, app_name=app_name, namespace=namespace)
elif callable(view):
# For a direct view mapping, create a leaf node
pattern = Pattern(route, name=name, is_endpoint=True)
return URLPattern(pattern, view, kwargs, name)

Pattern Matching Engines

The system supports two primary matching strategies, implemented in django/urls/resolvers.py:

RoutePattern

Used by path(), RoutePattern handles simplified path strings with bracketed converters (e.g., <int:pk>). It performs two main tasks:

  1. Regex Conversion: It converts the simplified route into a regular expression using _route_to_regex.
  2. Type Conversion: During a match, it uses registered converters to transform string captures into Python types.
# From RoutePattern.match in django/urls/resolvers.py
if self.converters:
if match := self.regex.search(path):
kwargs = match.groupdict()
for key, value in kwargs.items():
converter = self.converters[key]
try:
kwargs[key] = converter.to_python(value)
except ValueError:
return None
return path[match.end() :], (), kwargs

RegexPattern

Used by re_path(), RegexPattern uses standard regular expressions. It supports both named groups (returned as kwargs) and unnamed groups (returned as args). If any named groups are present, all unnamed groups are ignored.

The Resolution Process

Resolution is a recursive depth-first search performed by URLResolver.resolve().

  1. Prefix Matching: The resolver first checks if its own pattern (the prefix from include()) matches the start of the current path.
  2. Sub-pattern Iteration: If the prefix matches, it strips the matched portion and iterates through its url_patterns.
  3. Recursive Call: For each sub-pattern, it calls pattern.resolve(new_path).
    • If a URLPattern matches, it returns a ResolverMatch.
    • If a URLResolver matches, it continues the recursion.
  4. Error Handling: If no sub-pattern matches, it raises a Resolver404, which includes a list of all patterns tried to assist in debugging.
# From URLResolver.resolve in django/urls/resolvers.py
match = self.pattern.match(path)
if match:
new_path, args, kwargs = match
for pattern in self.url_patterns:
try:
sub_match = pattern.resolve(new_path)
except Resolver404 as e:
self._extend_tried(tried, pattern, e.args[0].get("tried"))
else:
if sub_match:
# Merge captured arguments and return ResolverMatch
...

The Result: ResolverMatch

A successful resolution returns a ResolverMatch object. This object acts as a container for everything needed to execute the view:

  • func: The view function or class-based view (specifically the result of as_view()).
  • args and kwargs: The arguments captured from the URL path, merged with any default_args provided in the pattern definition.
  • url_name: The name assigned to the pattern.
  • namespaces and app_names: The hierarchy of namespaces the match was found in.

ResolverMatch also provides a view_name property that combines the namespaces and the URL name (e.g., admin:index).

Reverse Resolution (URL Reversing)

The URLResolver also handles the inverse operation: generating a URL from a view name or callable via the reverse() method.

To make this efficient, the resolver uses a "populated" cache. The first time reverse() is called, URLResolver._populate() is triggered. This method iterates through all url_patterns and builds three internal dictionaries:

  • _reverse_dict: Maps view names and callables to their patterns and converters.
  • _namespace_dict: Maps namespaces to their corresponding URLResolver.
  • _app_dict: Maps application names to their namespaces.

When reverse(lookup_view, *args, **kwargs) is called, the resolver looks up the view in _reverse_dict, iterates through possible matches, and uses the associated converters' to_url() methods to reconstruct the path string.

# From URLResolver._reverse_with_prefix in django/urls/resolvers.py
possibilities = self.reverse_dict.getlist(lookup_view)
for possibility, pattern, defaults, converters in possibilities:
for result, params in possibility:
# ... validate args/kwargs ...
for k, v in candidate_subs.items():
if k in converters:
text_candidate_subs[k] = converters[k].to_url(v)
else:
text_candidate_subs[k] = str(v)