Skip to content

Django Views and URL Configuration

This chapter details Django's view system and URL configuration, including function-based views, class-based views, URL routing, parameter passing, and other core concepts, helping you build dynamic web applications.

View Basics

What are Views

Views are the "logic" part of Django applications. Views receive web requests and return web responses. Responses can be HTML pages, redirects, 404 errors, XML documents, images, etc.

python
# Simplest view
from django.http import HttpResponse

def hello_world(request):
    return HttpResponse("Hello, World!")

# View with parameters
def hello_name(request, name):
    return HttpResponse(f"Hello, {name}!")

# View returning HTML
def home_page(request):
    html = """
    <html>
    <head><title>My Website</title></head>
    <body>
        <h1>Welcome to My Website</h1>
        <p>This page was created with Django</p>
    </body>
    </html>
    """
    return HttpResponse(html)

HttpRequest Object

python
# views.py
from django.http import HttpResponse
import json

def request_info(request):
    """View showing request information"""
    info = {
        'method': request.method,
        'path': request.path,
        'full_path': request.get_full_path(),
        'host': request.get_host(),
        'is_secure': request.is_secure(),
        'user': str(request.user),
        'session_key': request.session.session_key,
    }
    
    # GET parameters
    if request.GET:
        info['GET_params'] = dict(request.GET)
    
    # POST parameters
    if request.POST:
        info['POST_params'] = dict(request.POST)
    
    # HTTP header information
    info['headers'] = {
        'User-Agent': request.META.get('HTTP_USER_AGENT', ''),
        'Accept': request.META.get('HTTP_ACCEPT', ''),
        'Accept-Language': request.META.get('HTTP_ACCEPT_LANGUAGE', ''),
        'Remote-Addr': request.META.get('REMOTE_ADDR', ''),
    }
    
    # Return JSON response
    return HttpResponse(
        json.dumps(info, indent=2, ensure_ascii=False),
        content_type='application/json; charset=utf-8'
    )

def handle_different_methods(request):
    """View handling different HTTP methods"""
    if request.method == 'GET':
        return HttpResponse("This is a GET request")
    elif request.method == 'POST':
        return HttpResponse("This is a POST request")
    elif request.method == 'PUT':
        return HttpResponse("This is a PUT request")
    elif request.method == 'DELETE':
        return HttpResponse("This is a DELETE request")
    else:
        return HttpResponse(f"Unsupported method: {request.method}", status=405)

HttpResponse Object

python
from django.http import (
    HttpResponse, JsonResponse, HttpResponseRedirect,
    HttpResponseNotFound, HttpResponseForbidden,
    HttpResponseServerError, FileResponse
)
from django.shortcuts import redirect
import json

def different_responses(request):
    """Demonstrate different types of responses"""
    response_type = request.GET.get('type', 'html')
    
    if response_type == 'html':
        response = HttpResponse("<h1>HTML Response</h1>")
        response['Content-Type'] = 'text/html; charset=utf-8'
        return response
    
    elif response_type == 'json':
        data = {'message': 'JSON Response', 'status': 'success'}
        return JsonResponse(data)
    
    elif response_type == 'text':
        return HttpResponse("Plain text response", content_type='text/plain')
    
    elif response_type == 'redirect':
        return HttpResponseRedirect('/success/')
        # Or use shortcut
        # return redirect('success_page')
    
    elif response_type == 'error':
        return HttpResponseNotFound("Page not found")
    
    elif response_type == 'custom':
        response = HttpResponse("Custom response")
        response.status_code = 201
        response['X-Custom-Header'] = 'Custom Value'
        response['Cache-Control'] = 'no-cache'
        return response
    
    else:
        return HttpResponse("Unknown response type")

def download_file(request):
    """File download response"""
    file_path = '/path/to/your/file.pdf'
    
    try:
        response = FileResponse(
            open(file_path, 'rb'),
            content_type='application/pdf'
        )
        response['Content-Disposition'] = 'attachment; filename="document.pdf"'
        response['Content-Length'] = len(file_content)
        return response
    except FileNotFoundError:
        return HttpResponseNotFound("File not found")

def set_cookie_view(request):
    """View setting cookies"""
    response = HttpResponse("Cookie set")
    response.set_cookie('username', 'john_doe', max_age=3600)  # 1 hour expiry
    response.set_cookie('theme', 'dark', secure=True, httponly=True)
    return response

def get_cookie_view(request):
    """View getting cookies"""
    username = request.COOKIES.get('username', 'Unknown user')
    theme = request.COOKIES.get('theme', 'Default theme')
    
    return HttpResponse(f"User: {username}, Theme: {theme}")

URL Configuration Details

URL Pattern Matching

python
# urls.py
from django.urls import path, re_path, include
from . import views

urlpatterns = [
    # Basic path matching
    path('', views.home, name='home'),
    path('about/', views.about, name='about'),
    
    # Path with parameters
    path('articles/<int:id>/', views.article_detail, name='article_detail'),
    path('articles/<slug:slug>/', views.article_by_slug, name='article_by_slug'),
    path('users/<str:username>/', views.user_profile, name='user_profile'),
    
    # Optional parameters
    path('blog/', views.blog_list, name='blog_list'),
    path('blog/page/<int:page>/', views.blog_list, name='blog_list_page'),
    
    # Regular expression path
    re_path(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive, name='year_archive'),
    re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive),
    
    # Include other URL configurations
    path('api/', include('api.urls')),
    path('admin/', include('admin.urls')),
]

# Corresponding view functions
def article_detail(request, id):
    return HttpResponse(f"Article ID: {id}")

def article_by_slug(request, slug):
    return HttpResponse(f"Article Slug: {slug}")

def user_profile(request, username):
    return HttpResponse(f"User: {username}")

def blog_list(request, page=1):
    return HttpResponse(f"Blog list - page {page}")

def year_archive(request, year):
    return HttpResponse(f"Articles from {year}")

def month_archive(request, year, month):
    return HttpResponse(f"Articles from {year}-{month}")

URL Naming and Reverse Resolution

python
# urls.py
from django.urls import path
from . import views

app_name = 'blog'  # Application namespace

urlpatterns = [
    path('', views.PostListView.as_view(), name='post_list'),
    path('post/<int:pk>/', views.PostDetailView.as_view(), name='post_detail'),
    path('post/create/', views.PostCreateView.as_view(), name='post_create'),
    path('post/<int:pk>/edit/', views.PostUpdateView.as_view(), name='post_edit'),
    path('post/<int:pk>/delete/', views.PostDeleteView.as_view(), name='post_delete'),
    path('category/<slug:slug>/', views.CategoryView.as_view(), name='category'),
]
python
# Using URL reverse resolution in views
from django.urls import reverse
from django.http import HttpResponseRedirect

def create_post(request):
    if request.method == 'POST':
        # Process form data
        # ...
        
        # Redirect to article detail page
        return HttpResponseRedirect(reverse('blog:post_detail', args=[post.id]))
        
        # Or using kwargs
        return HttpResponseRedirect(reverse('blog:post_detail', kwargs={'pk': post.id}))

# Using redirect shortcut
from django.shortcuts import redirect

def create_post_shortcut(request):
    if request.method == 'POST':
        # Process form data
        # ...
        
        # Direct redirect
        return redirect('blog:post_detail', pk=post.id)
        
        # Redirect to external URL
        return redirect('https://www.example.com/')
html
<!-- Using URL reverse resolution in templates -->
<a href="{% url 'blog:post_list' %}">Article List</a>
<a href="{% url 'blog:post_detail' post.id %}">{{ post.title }}</a>
<a href="{% url 'blog:category' category.slug %}">{{ category.name }}</a>

<!-- With query parameters -->
<a href="{% url 'blog:post_list' %}?page=2&category=tech">Tech Articles Page 2</a>

URL Parameters and Query Strings

python
# views.py
def search_view(request):
    """Handle search requests"""
    # Get query parameters
    query = request.GET.get('q', '')
    category = request.GET.get('category', 'all')
    page = request.GET.get('page', 1)
    
    # Get multiple values
    tags = request.GET.getlist('tags')  # ?tags=python&tags=django
    
    # Process search logic
    results = []
    if query:
        # Perform search
        results = search_articles(query, category, tags)
    
    context = {
        'query': query,
        'category': category,
        'page': int(page),
        'tags': tags,
        'results': results,
    }
    
    return render(request, 'search_results.html', context)

def article_archive(request, year, month=None, day=None):
    """Article archive view"""
    from datetime import datetime
    
    # Build date filtering conditions
    date_filter = {'created_at__year': year}
    
    if month:
        date_filter['created_at__month'] = month
    
    if day:
        date_filter['created_at__day'] = day
    
    # Query articles
    articles = Article.objects.filter(**date_filter)
    
    # Build breadcrumb navigation
    breadcrumbs = [{'name': 'Home', 'url': '/'}]
    breadcrumbs.append({'name': f'{year}', 'url': f'/archive/{year}/'})
    
    if month:
        breadcrumbs.append({'name': f'{month}', 'url': f'/archive/{year}/{month}/'})
    
    if day:
        breadcrumbs.append({'name': f'{day}', 'url': f'/archive/{year}/{month}/{day}/'})
    
    context = {
        'articles': articles,
        'year': year,
        'month': month,
        'day': day,
        'breadcrumbs': breadcrumbs,
    }
    
    return render(request, 'archive.html', context)

Function-based Views Details

Basic Function-based Views

python
# views.py
from django.shortcuts import render, get_object_or_404
from django.http import HttpResponse, Http404
from .models import Article, Category

def article_list(request):
    """Article list view"""
    # Get all published articles
    articles = Article.objects.filter(published=True).order_by('-created_at')
    
    # Pagination handling
    from django.core.paginator import Paginator
    paginator = Paginator(articles, 10)  # 10 articles per page
    page_number = request.GET.get('page')
    page_obj = paginator.get_page(page_number)
    
    context = {
        'articles': page_obj,
        'page_obj': page_obj,
    }
    
    return render(request, 'blog/article_list.html', context)

def article_detail(request, id):
    """Article detail view"""
    # Get article or return 404
    article = get_object_or_404(Article, id=id, published=True)
    
    # Increase view count
    article.views += 1
    article.save(update_fields=['views'])
    
    # Get related articles
    related_articles = Article.objects.filter(
        category=article.category,
        published=True
    ).exclude(id=article.id)[:5]
    
    context = {
        'article': article,
        'related_articles': related_articles,
    }
    
    return render(request, 'blog/article_detail.html', context)

def category_view(request, slug):
    """Category view"""
    category = get_object_or_404(Category, slug=slug)
    articles = Article.objects.filter(
        category=category,
        published=True
    ).order_by('-created_at')
    
    context = {
        'category': category,
        'articles': articles,
    }
    
    return render(request, 'blog/category.html', context)

Views Handling Forms

python
from django.shortcuts import render, redirect
from django.contrib import messages
from .forms import ContactForm, ArticleForm

def contact_view(request):
    """Contact form view"""
    if request.method == 'POST':
        form = ContactForm(request.POST)
        if form.is_valid():
            # Process form data
            name = form.cleaned_data['name']
            email = form.cleaned_data['email']
            message = form.cleaned_data['message']
            
            # Send email or save to database
            send_contact_email(name, email, message)
            
            # Show success message
            messages.success(request, 'Your message has been sent successfully!')
            
            # Redirect to avoid duplicate submissions
            return redirect('contact_success')
    else:
        form = ContactForm()
    
    return render(request, 'contact.html', {'form': form})

def create_article(request):
    """Create article view"""
    if request.method == 'POST':
        form = ArticleForm(request.POST, request.FILES)
        if form.is_valid():
            article = form.save(commit=False)
            article.author = request.user
            article.save()
            
            messages.success(request, 'Article created successfully!')
            return redirect('blog:article_detail', id=article.id)
    else:
        form = ArticleForm()
    
    return render(request, 'blog/create_article.html', {'form': form})

def edit_article(request, id):
    """Edit article view"""
    article = get_object_or_404(Article, id=id)
    
    # Permission check
    if article.author != request.user:
        messages.error(request, 'You don\'t have permission to edit this article')
        return redirect('blog:article_detail', id=article.id)
    
    if request.method == 'POST':
        form = ArticleForm(request.POST, request.FILES, instance=article)
        if form.is_valid():
            form.save()
            messages.success(request, 'Article updated successfully!')
            return redirect('blog:article_detail', id=article.id)
    else:
        form = ArticleForm(instance=article)
    
    context = {
        'form': form,
        'article': article,
    }
    
    return render(request, 'blog/edit_article.html', context)

View Decorators

Built-in Decorators

python
from django.views.decorators.http import require_http_methods, require_POST, require_GET
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.cache import cache_page
from django.contrib.auth.decorators import login_required, permission_required
from django.contrib.admin.views.decorators import staff_member_required

# HTTP method restrictions
@require_http_methods(["GET", "POST"])
def my_view(request):
    if request.method == 'GET':
        return render(request, 'form.html')
    else:  # POST
        # Handle form submission
        return redirect('success')

@require_POST
def delete_article(request, id):
    """Only allow POST requests to delete articles"""
    article = get_object_or_404(Article, id=id)
    
    # Permission check
    if article.author != request.user:
        return HttpResponseForbidden("No permission to delete")
    
    article.delete()
    messages.success(request, 'Article deleted')
    return redirect('blog:article_list')

# Login requirement
@login_required
def profile_view(request):
    """View requiring login"""
    return render(request, 'profile.html', {'user': request.user})

# Permission requirement
@permission_required('blog.add_article')
def create_article_view(request):
    """View requiring specific permission to create articles"""
    # Article creation logic
    pass

# Admin requirement
@staff_member_required
def admin_dashboard(request):
    """Only admins can access"""
    return render(request, 'admin_dashboard.html')

# Cache decorator
@cache_page(60 * 15)  # Cache for 15 minutes
def cached_view(request):
    """Cached view"""
    # Perform some expensive operation
    expensive_data = perform_expensive_operation()
    return render(request, 'cached_template.html', {'data': expensive_data})

# CSRF exemption (use with caution)
@csrf_exempt
def api_endpoint(request):
    """API endpoint, exempt from CSRF check"""
    if request.method == 'POST':
        import json
        data = json.loads(request.body)
        # Process API request
        return JsonResponse({'status': 'success'})
    
    return JsonResponse({'error': 'Method not allowed'}, status=405)

Custom Decorators

python
from functools import wraps
from django.http import HttpResponseForbidden
from django.shortcuts import redirect
import time

def timing_decorator(func):
    """Timing decorator"""
    @wraps(func)
    def wrapper(request, *args, **kwargs):
        start_time = time.time()
        response = func(request, *args, **kwargs)
        end_time = time.time()
        
        # Add execution time to response header
        response['X-Execution-Time'] = f"{end_time - start_time:.3f}s"
        return response
    return wrapper

def owner_required(model_class, pk_param='pk'):
    """Require user to be object owner"""
    def decorator(func):
        @wraps(func)
        def wrapper(request, *args, **kwargs):
            if not request.user.is_authenticated:
                return redirect('login')
            
            # Get object ID
            object_id = kwargs.get(pk_param)
            if not object_id:
                return HttpResponseForbidden("Missing object ID")
            
            # Check ownership
            try:
                obj = model_class.objects.get(pk=object_id)
                if obj.author != request.user:
                    return HttpResponseForbidden("You are not the owner of this object")
            except model_class.DoesNotExist:
                raise Http404("Object does not exist")
            
            return func(request, *args, **kwargs)
        return wrapper
    return decorator

def rate_limit(max_requests=10, window=60):
    """Simple rate limiting decorator"""
    def decorator(func):
        @wraps(func)
        def wrapper(request, *args, **kwargs):
            from django.core.cache import cache
            
            # Get client IP
            ip = request.META.get('REMOTE_ADDR')
            cache_key = f"rate_limit_{ip}_{func.__name__}"
            
            # Check request count
            current_requests = cache.get(cache_key, 0)
            if current_requests >= max_requests:
                return HttpResponse("Too many requests, please try again later", status=429)
            
            # Increase request count
            cache.set(cache_key, current_requests + 1, window)
            
            return func(request, *args, **kwargs)
        return wrapper
    return decorator

# Using custom decorators
@timing_decorator
@login_required
@owner_required(Article, 'id')
def edit_my_article(request, id):
    """Edit my article"""
    article = get_object_or_404(Article, id=id)
    # Edit logic
    return render(request, 'edit_article.html', {'article': article})

@rate_limit(max_requests=5, window=300)  # Max 5 requests in 5 minutes
def api_search(request):
    """Search API"""
    query = request.GET.get('q', '')
    results = perform_search(query)
    return JsonResponse({'results': results})

Practical Examples

Example 1: Blog System Views

python
# models.py
from django.db import models
from django.contrib.auth.models import User
from django.urls import reverse

class Category(models.Model):
    name = models.CharField(max_length=100)
    slug = models.SlugField(unique=True)
    description = models.TextField(blank=True)
    
    def __str__(self):
        return self.name
    
    def get_absolute_url(self):
        return reverse('blog:category', kwargs={'slug': self.slug})

class Article(models.Model):
    title = models.CharField(max_length=200)
    slug = models.SlugField(unique=True)
    content = models.TextField()
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    category = models.ForeignKey(Category, on_delete=models.CASCADE)
    published = models.BooleanField(default=False)
    views = models.PositiveIntegerField(default=0)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    
    class Meta:
        ordering = ['-created_at']
    
    def __str__(self):
        return self.title
    
    def get_absolute_url(self):
        return reverse('blog:article_detail', kwargs={'slug': self.slug})

# views.py
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from django.core.paginator import Paginator
from django.db.models import Q
from .models import Article, Category

def home_view(request):
    """Home page view"""
    # Get latest articles
    latest_articles = Article.objects.filter(published=True)[:5]
    
    # Get popular articles
    popular_articles = Article.objects.filter(published=True).order_by('-views')[:5]
    
    # Get all categories
    categories = Category.objects.all()
    
    context = {
        'latest_articles': latest_articles,
        'popular_articles': popular_articles,
        'categories': categories,
    }
    
    return render(request, 'blog/home.html', context)

def article_list_view(request):
    """Article list view"""
    articles = Article.objects.filter(published=True)
    
    # Category filtering
    category_slug = request.GET.get('category')
    if category_slug:
        articles = articles.filter(category__slug=category_slug)
    
    # Search functionality
    search_query = request.GET.get('search')
    if search_query:
        articles = articles.filter(
            Q(title__icontains=search_query) |
            Q(content__icontains=search_query)
        )
    
    # Pagination
    paginator = Paginator(articles, 12)
    page_number = request.GET.get('page')
    page_obj = paginator.get_page(page_number)
    
    # Get all categories for filtering
    categories = Category.objects.all()
    
    context = {
        'page_obj': page_obj,
        'categories': categories,
        'current_category': category_slug,
        'search_query': search_query,
    }
    
    return render(request, 'blog/article_list.html', context)

def article_detail_view(request, slug):
    """Article detail view"""
    article = get_object_or_404(Article, slug=slug, published=True)
    
    # Increase view count
    article.views += 1
    article.save(update_fields=['views'])
    
    # Get previous and next articles
    prev_article = Article.objects.filter(
        published=True,
        created_at__lt=article.created_at
    ).order_by('-created_at').first()
    
    next_article = Article.objects.filter(
        published=True,
        created_at__gt=article.created_at
    ).order_by('created_at').first()
    
    # Get related articles in same category
    related_articles = Article.objects.filter(
        category=article.category,
        published=True
    ).exclude(id=article.id)[:4]
    
    context = {
        'article': article,
        'prev_article': prev_article,
        'next_article': next_article,
        'related_articles': related_articles,
    }
    
    return render(request, 'blog/article_detail.html', context)

@login_required
def create_article_view(request):
    """Create article view"""
    if request.method == 'POST':
        form = ArticleForm(request.POST, request.FILES)
        if form.is_valid():
            article = form.save(commit=False)
            article.author = request.user
            article.save()
            
            messages.success(request, 'Article created successfully!')
            return redirect('blog:article_detail', slug=article.slug)
    else:
        form = ArticleForm()
    
    return render(request, 'blog/create_article.html', {'form': form})

def search_view(request):
    """Search view"""
    query = request.GET.get('q', '').strip()
    results = []
    
    if query:
        # Search article titles and content
        results = Article.objects.filter(
            Q(title__icontains=query) |
            Q(content__icontains=query),
            published=True
        ).order_by('-created_at')
        
        # Highlight search keywords (simple implementation)
        for article in results:
            article.highlighted_title = article.title.replace(
                query, f'<mark>{query}</mark>'
            )
    
    # Pagination
    paginator = Paginator(results, 10)
    page_number = request.GET.get('page')
    page_obj = paginator.get_page(page_number)
    
    context = {
        'query': query,
        'page_obj': page_obj,
        'total_results': results.count() if results else 0,
    }
    
    return render(request, 'blog/search_results.html', context)

Chapter Summary

This chapter detailed Django's views and URL configuration:

Key Points:

  • View Concept: Python functions handling HTTP requests and returning responses
  • HttpRequest Object: Object containing request information
  • HttpResponse Object: Various types of HTTP responses
  • URL Configuration: Mapping URL patterns to view functions
  • URL Parameters: Extracting parameters from URLs to pass to views

Important Concepts:

  • MVT Pattern: Model-View-Template architecture
  • URL Reverse Resolution: Generating URLs by name
  • View Decorators: Adding extra functionality to views
  • Request Method Handling: Handling different HTTP methods

Best Practices:

  • Use meaningful URL patterns and names
  • Use decorators appropriately to enhance view functionality
  • Handle different HTTP methods correctly
  • Use shortcut functions to simplify common operations
  • Implement proper error handling and permission checks

In the next chapter, we will learn about Django's template system to understand how to create dynamic HTML pages.

Further Reading

Content is for learning and research only.