Skip to content

Django - URL Mapping

When a user requests a page from your Django application, Django needs a way to determine which piece of Python code (which view) should handle that request. This process is managed by Django’s URL dispatcher, configured through URL patterns defined primarily in urls.py files.

The heart of URL mapping is the urlpatterns list found in your project’s main urls.py file (e.g., myproject/urls.py) and potentially within each application’s urls.py file.

Django processes a request by checking the requested URL path against each pattern in urlpatterns in order. The first pattern that matches is used, and Django calls the associated view function or class-based view.

A typical project urls.py might look like this initially:

myproject/urls.py
from django.contrib import admin
from django.urls import path, include # Import include
urlpatterns = [
path('admin/', admin.site.urls),
# Add paths for your applications here
# Example: path('blog/', include('blog.urls')),
]

Defining URL Patterns: path() and re_path()

Section titled “Defining URL Patterns: path() and re_path()”

Django provides two primary functions for defining URL patterns within the urlpatterns list:

  1. path(route, view, kwargs=None, name=None):
  • route: A string defining the URL path pattern. It can include angle brackets (<>) to capture parts of the URL as arguments for the view. Uses simple path converters.
  • view: The view function or class-based view (.as_view()) to call.
  • kwargs: Optional dictionary of extra keyword arguments to pass to the view.
  • name: An optional name for this URL pattern, used for URL reversing (highly recommended).
  1. re_path(route, view, kwargs=None, name=None):
  • route: A string containing a Python regular expression to match the URL path. Use named groups (?P<name>...) to capture parts of the URL.
  • Other arguments (view, kwargs, name) are the same as path().

path() is generally preferred for its simplicity and readability when its converters are sufficient. re_path() provides more power and flexibility using regular expressions.

Let’s create a simple app myapp with a view and map a URL to it.

myapp/views.py
from django.http import HttpResponse
def hello_view(request):
return HttpResponse("Hello from myapp!")
# myapp/urls.py (Create this file)
from django.urls import path
from . import views # Import views from the current directory
urlpatterns = [
path('hello/', views.hello_view, name='hello'), # Map '/hello/' to hello_view
]

Now, include myapp’s URLs in the main project’s urls.py:

myproject/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('myapp/', include('myapp.urls')), # Include URLs from myapp
]

With this setup, a request to /myapp/hello/ will first match 'myapp/' in the project urls.py. Django then strips off the matched part ('myapp/') and passes the remaining string ('hello/') to myapp/urls.py for further matching. There, 'hello/' matches the pattern, and views.hello_view is called.

Using include() as shown above is the standard practice for organizing URLs. Each app manages its own URL namespace within its urls.py. This promotes modularity and reusability – you can plug the myapp app into another project simply by adding it to INSTALLED_APPS and including its URLs.

Often, you need parts of the URL to be dynamic, like product IDs or usernames.

Using path() with Converters:

path() uses converters specified in angle brackets <converter:variable_name>.

  • <str:name>: Matches any non-empty string (excluding ’/’).
  • <int:pk>: Matches zero or any positive integer. Captures as an int.
  • <slug:slug>: Matches any slug string (ASCII letters/numbers, hyphens, underscores).
  • <uuid:id>: Matches a formatted UUID.
  • <path:path>: Matches any non-empty string, including ’/’. Useful for capturing entire paths.
myapp/views.py
def view_article(request, article_id):
# article_id will be an integer thanks to the <int:> converter
text = f"Displaying article Number : {article_id} (Type: {type(article_id).__name__})"
return HttpResponse(text)
def view_articles_by_month(request, year, month):
# year and month will be integers
text = f"Displaying articles for : {month:02d}/{year}"
return HttpResponse(text)
# myapp/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('article/<int:article_id>/', views.view_article, name='article_detail'),
# Example: /myapp/article/123/
path('articles/<int:year>/<int:month>/', views.view_articles_by_month, name='articles_by_month'),
# Example: /myapp/articles/2024/05/
]

Using re_path() with Named Regex Groups:

Use standard Python regex with named groups (?P<variable_name>regex_pattern).

myapp/views.py
# (Views can often remain the same as above)
# myapp/urls.py
from django.urls import re_path
from . import views
urlpatterns = [
re_path(r'^article/(?P<article_id>\d+)/$', views.view_article, name='article_detail'),
# Example: /myapp/article/123/
# \d+ matches one or more digits
re_path(r'^articles/(?P<year>\d{4})/(?P<month>\d{2})/$', views.view_articles_by_month, name='articles_by_month'),
# Example: /myapp/articles/2024/05/
# \d{4} matches exactly 4 digits, \d{2} matches exactly 2 digits
]

Captured values from both path() and re_path() are passed as keyword arguments to the view function (matching the variable name in the angle brackets or the regex group name).

Assigning a name to your URL patterns (e.g., name='article_detail') allows you to refer to them unambiguously from other parts of your code (views, templates, models) using the reverse() function or the {% url %} template tag. This is crucial for maintainability, as you can change the actual URL path structure in urls.py without breaking references elsewhere.

Further Reading: