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.