URLs and Views

The URL Dispatcher

Django's URL dispatcher is a powerful pattern-matching system that maps incoming URLs to view functions. It provides clean, readable URLs while maintaining flexibility for complex routing requirements.

The URL Dispatcher

Django's URL dispatcher is a powerful pattern-matching system that maps incoming URLs to view functions. It provides clean, readable URLs while maintaining flexibility for complex routing requirements.

URL Configuration Basics

URLconf Structure

# myproject/urls.py (Root URLconf)
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    path('admin/', admin.site.urls),
    path('blog/', include('blog.urls')),
    path('api/v1/', include('api.urls')),
    path('accounts/', include('django.contrib.auth.urls')),
    path('', include('core.urls')),  # Root patterns
]

# Serve media files in development
if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
    urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

# Custom error handlers
handler404 = 'core.views.custom_404'
handler500 = 'core.views.custom_500'
handler403 = 'core.views.custom_403'
handler400 = 'core.views.custom_400'

App-Level URL Configuration

# blog/urls.py
from django.urls import path, re_path
from . import views

app_name = 'blog'  # Namespace for URL reversing

urlpatterns = [
    # Basic patterns
    path('', views.post_list, name='post_list'),
    path('create/', views.post_create, name='post_create'),
    
    # Parameter patterns
    path('<int:pk>/', views.post_detail, name='post_detail'),
    path('<int:pk>/edit/', views.post_edit, name='post_edit'),
    path('<int:pk>/delete/', views.post_delete, name='post_delete'),
    
    # Slug patterns
    path('<slug:slug>/', views.post_by_slug, name='post_by_slug'),
    path('category/<slug:category_slug>/', views.posts_by_category, name='posts_by_category'),
    
    # Date-based patterns
    path('<int:year>/', views.posts_by_year, name='posts_by_year'),
    path('<int:year>/<int:month>/', views.posts_by_month, name='posts_by_month'),
    path('<int:year>/<int:month>/<int:day>/', views.posts_by_day, name='posts_by_day'),
    
    # Complex patterns
    path('author/<str:username>/', views.posts_by_author, name='posts_by_author'),
    path('tag/<str:tag_name>/', views.posts_by_tag, name='posts_by_tag'),
    
    # API endpoints
    path('api/posts/', views.api_post_list, name='api_post_list'),
    path('api/posts/<int:pk>/', views.api_post_detail, name='api_post_detail'),
]

URL Pattern Types

Path Converters

Django provides built-in path converters for common parameter types:

# Built-in converters
urlpatterns = [
    # String (default) - matches any non-empty string, excluding '/'
    path('posts/<str:title>/', views.post_by_title),
    
    # Integer - matches zero or any positive integer
    path('posts/<int:pk>/', views.post_detail),
    
    # Slug - matches any slug string (letters, numbers, hyphens, underscores)
    path('posts/<slug:slug>/', views.post_by_slug),
    
    # UUID - matches a formatted UUID
    path('posts/<uuid:id>/', views.post_by_uuid),
    
    # Path - matches any non-empty string, including '/'
    path('files/<path:file_path>/', views.serve_file),
]

# Example views
def post_by_title(request, title):
    # title is a string
    post = get_object_or_404(Post, title=title)
    return render(request, 'blog/post_detail.html', {'post': post})

def post_detail(request, pk):
    # pk is an integer
    post = get_object_or_404(Post, pk=pk)
    return render(request, 'blog/post_detail.html', {'post': post})

def post_by_slug(request, slug):
    # slug is a slug string
    post = get_object_or_404(Post, slug=slug)
    return render(request, 'blog/post_detail.html', {'post': post})

def serve_file(request, file_path):
    # file_path can contain forward slashes
    # e.g., 'documents/2024/report.pdf'
    return serve_static_file(file_path)

Custom Path Converters

# converters.py
class YearConverter:
    regex = '[0-9]{4}'
    
    def to_python(self, value):
        return int(value)
    
    def to_url(self, value):
        return str(value)

class MonthConverter:
    regex = '(0?[1-9]|1[0-2])'  # 1-12 or 01-12
    
    def to_python(self, value):
        return int(value)
    
    def to_url(self, value):
        return f'{value:02d}'

class CategoryConverter:
    regex = '[a-zA-Z0-9-_]+'
    
    def to_python(self, value):
        # Convert to Category object
        from blog.models import Category
        try:
            return Category.objects.get(slug=value)
        except Category.DoesNotExist:
            raise ValueError("Category not found")
    
    def to_url(self, value):
        if hasattr(value, 'slug'):
            return value.slug
        return str(value)

# Register converters
from django.urls import register_converter

register_converter(YearConverter, 'year')
register_converter(MonthConverter, 'month')
register_converter(CategoryConverter, 'category')

# Usage in URLs
urlpatterns = [
    path('archive/<year:year>/', views.posts_by_year),
    path('archive/<year:year>/<month:month>/', views.posts_by_month),
    path('category/<category:cat>/', views.posts_by_category),
]

Regular Expression Patterns

For complex pattern matching, use re_path:

from django.urls import re_path

urlpatterns = [
    # Complex date pattern
    re_path(
        r'^archive/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})/$',
        views.posts_by_date,
        name='posts_by_date'
    ),
    
    # Version-specific API
    re_path(
        r'^api/v(?P<version>[1-9]\d*)/',
        include('api.urls'),
        name='api_versioned'
    ),
    
    # File extension matching
    re_path(
        r'^download/(?P<filename>[\w\-_]+\.(?P<extension>pdf|doc|docx))/$',
        views.download_file,
        name='download_file'
    ),
    
    # Optional parameters
    re_path(
        r'^search/(?P<query>[\w\s]+)(?:/page/(?P<page>\d+))?/$',
        views.search_posts,
        name='search_posts'
    ),
    
    # Multiple format support
    re_path(
        r'^posts/(?P<pk>\d+)\.(?P<format>json|xml|html)$',
        views.post_detail_formatted,
        name='post_detail_formatted'
    ),
]

# Corresponding views
def posts_by_date(request, year, month, day):
    # All parameters are strings from regex groups
    year, month, day = int(year), int(month), int(day)
    date = datetime.date(year, month, day)
    posts = Post.objects.filter(created_at__date=date)
    return render(request, 'blog/posts_by_date.html', {
        'posts': posts,
        'date': date
    })

def download_file(request, filename, extension):
    # filename: 'document-name'
    # extension: 'pdf'
    file_path = f'downloads/{filename}.{extension}'
    return serve_file(request, file_path)

def search_posts(request, query, page=None):
    # page is optional and may be None
    page = int(page) if page else 1
    posts = Post.objects.filter(title__icontains=query)
    
    paginator = Paginator(posts, 10)
    posts_page = paginator.get_page(page)
    
    return render(request, 'blog/search_results.html', {
        'posts': posts_page,
        'query': query
    })

URL Namespaces and Organization

App Namespaces

# blog/urls.py
from django.urls import path
from . import views

app_name = 'blog'  # Define namespace

urlpatterns = [
    path('', views.post_list, name='list'),
    path('<int:pk>/', views.post_detail, name='detail'),
    path('create/', views.post_create, name='create'),
    path('<int:pk>/edit/', views.post_edit, name='edit'),
]

# shop/urls.py
from django.urls import path
from . import views

app_name = 'shop'  # Same name pattern, different namespace

urlpatterns = [
    path('', views.product_list, name='list'),
    path('<int:pk>/', views.product_detail, name='detail'),
    path('create/', views.product_create, name='create'),
]

Instance Namespaces

# myproject/urls.py
from django.urls import path, include

urlpatterns = [
    # Different instances of the same app
    path('blog/', include('blog.urls', namespace='blog')),
    path('news/', include('blog.urls', namespace='news')),
    path('admin-blog/', include('blog.urls', namespace='admin_blog')),
]

# In views or templates, reference with namespace
from django.urls import reverse

# Reverse URL with namespace
blog_url = reverse('blog:detail', args=[post.pk])
news_url = reverse('news:detail', args=[post.pk])
admin_url = reverse('admin_blog:detail', args=[post.pk])

Nested Namespaces

# api/urls.py
from django.urls import path, include

app_name = 'api'

urlpatterns = [
    path('v1/', include('api.v1.urls', namespace='v1')),
    path('v2/', include('api.v2.urls', namespace='v2')),
]

# api/v1/urls.py
from django.urls import path, include

app_name = 'v1'

urlpatterns = [
    path('blog/', include('api.v1.blog_urls', namespace='blog')),
    path('users/', include('api.v1.user_urls', namespace='users')),
]

# Usage: api:v1:blog:list
api_blog_list_url = reverse('api:v1:blog:list')

URL Includes and Modular Organization

Basic Includes

# myproject/urls.py
from django.urls import path, include

urlpatterns = [
    # Include entire app URLs
    path('blog/', include('blog.urls')),
    
    # Include with additional patterns
    path('api/', include([
        path('v1/', include('api.v1.urls')),
        path('v2/', include('api.v2.urls')),
        path('auth/', include('api.auth.urls')),
    ])),
    
    # Include with conditions
    path('debug/', include('debug_toolbar.urls')) if settings.DEBUG else path(''),
]

Conditional URL Inclusion

# myproject/urls.py
from django.conf import settings
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('core.urls')),
]

# Development-only URLs
if settings.DEBUG:
    urlpatterns += [
        path('__debug__/', include('debug_toolbar.urls')),
        path('silk/', include('silk.urls')),
    ]

# Feature flags
if getattr(settings, 'ENABLE_API', False):
    urlpatterns += [
        path('api/', include('api.urls')),
    ]

# Environment-specific URLs
if settings.ENVIRONMENT == 'staging':
    urlpatterns += [
        path('test/', include('testing.urls')),
    ]

Dynamic URL Generation

# utils/urls.py
from django.urls import path
from importlib import import_module

def generate_app_urls(app_configs):
    """Dynamically generate URL patterns for multiple apps"""
    patterns = []
    
    for app_config in app_configs:
        app_name = app_config['name']
        url_prefix = app_config.get('prefix', app_name)
        
        try:
            app_urls = import_module(f'{app_name}.urls')
            patterns.append(
                path(f'{url_prefix}/', include(app_urls, namespace=app_name))
            )
        except ImportError:
            continue
    
    return patterns

# myproject/urls.py
from utils.urls import generate_app_urls

INSTALLED_APPS_CONFIG = [
    {'name': 'blog', 'prefix': 'blog'},
    {'name': 'shop', 'prefix': 'store'},
    {'name': 'forum', 'prefix': 'community'},
]

urlpatterns = [
    path('admin/', admin.site.urls),
] + generate_app_urls(INSTALLED_APPS_CONFIG)

URL Reversing

Basic URL Reversing

from django.urls import reverse
from django.shortcuts import redirect

# In views
def post_create(request):
    if request.method == 'POST':
        # Process form
        post = create_post(request.POST)
        
        # Redirect to detail view
        return redirect('blog:detail', pk=post.pk)
    
    return render(request, 'blog/post_form.html')

# With arguments
def redirect_to_post(request, post_id):
    return redirect('blog:detail', pk=post_id)

# With keyword arguments
def redirect_to_date_archive(request):
    return redirect('blog:archive', year=2024, month=3)

# Reverse to get URL string
def get_post_url(post):
    return reverse('blog:detail', kwargs={'pk': post.pk})

Template URL Reversing

<!-- Basic URL reversing -->
<a href="{% url 'blog:list' %}">All Posts</a>
<a href="{% url 'blog:detail' pk=post.pk %}">{{ post.title }}</a>
<a href="{% url 'blog:edit' post.pk %}">Edit</a>

<!-- With multiple parameters -->
<a href="{% url 'blog:archive' year=2024 month=3 %}">March 2024</a>

<!-- Dynamic parameters -->
{% for post in posts %}
    <a href="{% url 'blog:detail' pk=post.pk %}">{{ post.title }}</a>
{% endfor %}

<!-- Conditional URLs -->
{% if user.is_authenticated %}
    <a href="{% url 'blog:create' %}">Create Post</a>
{% else %}
    <a href="{% url 'accounts:login' %}">Login to Create</a>
{% endif %}

<!-- URL with query parameters -->
<a href="{% url 'blog:list' %}?category={{ category.slug }}">{{ category.name }}</a>

Advanced URL Reversing

# utils/urls.py
from django.urls import reverse
from django.http import QueryDict

def reverse_with_query(viewname, args=None, kwargs=None, query_params=None):
    """Reverse URL with query parameters"""
    url = reverse(viewname, args=args, kwargs=kwargs)
    
    if query_params:
        query_dict = QueryDict(mutable=True)
        query_dict.update(query_params)
        url += f'?{query_dict.urlencode()}'
    
    return url

# Usage
search_url = reverse_with_query(
    'blog:list',
    query_params={'q': 'django', 'category': 'tech', 'page': 2}
)
# Result: /blog/?q=django&category=tech&page=2

# Class-based URL builder
class URLBuilder:
    def __init__(self, viewname, namespace=None):
        self.viewname = f'{namespace}:{viewname}' if namespace else viewname
        self.args = []
        self.kwargs = {}
        self.query_params = {}
    
    def arg(self, *args):
        self.args.extend(args)
        return self
    
    def kwarg(self, **kwargs):
        self.kwargs.update(kwargs)
        return self
    
    def query(self, **params):
        self.query_params.update(params)
        return self
    
    def build(self):
        return reverse_with_query(
            self.viewname,
            args=self.args,
            kwargs=self.kwargs,
            query_params=self.query_params
        )

# Usage
url = (URLBuilder('detail', namespace='blog')
       .kwarg(pk=123)
       .query(ref='homepage', utm_source='email')
       .build())

URL Pattern Optimization

Performance Considerations

# Efficient URL patterns
urlpatterns = [
    # Most specific patterns first
    path('posts/create/', views.post_create, name='create'),
    path('posts/popular/', views.popular_posts, name='popular'),
    path('posts/recent/', views.recent_posts, name='recent'),
    
    # Generic patterns last
    path('posts/<int:pk>/', views.post_detail, name='detail'),
    path('posts/<slug:slug>/', views.post_by_slug, name='by_slug'),
]

# Avoid overlapping patterns
# BAD:
urlpatterns = [
    path('<str:category>/', views.category_posts),  # Too broad
    path('create/', views.post_create),  # Will never match
]

# GOOD:
urlpatterns = [
    path('create/', views.post_create),  # Specific first
    path('<str:category>/', views.category_posts),  # Generic last
]

URL Pattern Testing

# tests/test_urls.py
from django.test import TestCase
from django.urls import reverse, resolve
from django.contrib.auth.models import User
from blog.models import Post

class URLTests(TestCase):
    def setUp(self):
        self.user = User.objects.create_user('testuser', 'test@example.com', 'pass')
        self.post = Post.objects.create(
            title='Test Post',
            slug='test-post',
            content='Test content',
            author=self.user
        )
    
    def test_post_list_url(self):
        """Test post list URL resolves correctly"""
        url = reverse('blog:list')
        self.assertEqual(url, '/blog/')
        
        resolver = resolve('/blog/')
        self.assertEqual(resolver.view_name, 'blog:list')
    
    def test_post_detail_url(self):
        """Test post detail URL with pk"""
        url = reverse('blog:detail', kwargs={'pk': self.post.pk})
        self.assertEqual(url, f'/blog/{self.post.pk}/')
        
        resolver = resolve(f'/blog/{self.post.pk}/')
        self.assertEqual(resolver.view_name, 'blog:detail')
        self.assertEqual(resolver.kwargs['pk'], str(self.post.pk))
    
    def test_post_slug_url(self):
        """Test post detail URL with slug"""
        url = reverse('blog:by_slug', kwargs={'slug': self.post.slug})
        self.assertEqual(url, f'/blog/{self.post.slug}/')
    
    def test_invalid_urls(self):
        """Test invalid URLs return 404"""
        response = self.client.get('/blog/999999/')
        self.assertEqual(response.status_code, 404)
        
        response = self.client.get('/blog/invalid-slug/')
        self.assertEqual(response.status_code, 404)
    
    def test_url_parameters(self):
        """Test URL parameter extraction"""
        resolver = resolve(f'/blog/{self.post.pk}/')
        self.assertIn('pk', resolver.kwargs)
        self.assertEqual(int(resolver.kwargs['pk']), self.post.pk)

Django's URL dispatcher provides powerful, flexible routing capabilities. Understanding pattern matching, namespaces, and URL organization enables you to build maintainable, scalable URL structures that support complex application requirements while maintaining clean, readable URLs.