Django视图和URL配置
本章将详细介绍Django的视图系统和URL配置,包括函数视图、类视图、URL路由、参数传递等核心概念,帮助你构建动态的Web应用程序。
视图基础
什么是视图
视图是Django应用程序中的"逻辑"部分。视图接收Web请求并返回Web响应。响应可以是HTML页面、重定向、404错误、XML文档、图片等。
python
# 最简单的视图
from django.http import HttpResponse
def hello_world(request):
return HttpResponse("Hello, World!")
# 带参数的视图
def hello_name(request, name):
return HttpResponse(f"Hello, {name}!")
# 返回HTML的视图
def home_page(request):
html = """
<html>
<head><title>我的网站</title></head>
<body>
<h1>欢迎来到我的网站</h1>
<p>这是用Django创建的页面</p>
</body>
</html>
"""
return HttpResponse(html)HttpRequest对象
python
# views.py
from django.http import HttpResponse
import json
def request_info(request):
"""显示请求信息的视图"""
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参数
if request.GET:
info['GET_params'] = dict(request.GET)
# POST参数
if request.POST:
info['POST_params'] = dict(request.POST)
# HTTP头信息
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', ''),
}
# 返回JSON响应
return HttpResponse(
json.dumps(info, indent=2, ensure_ascii=False),
content_type='application/json; charset=utf-8'
)
def handle_different_methods(request):
"""处理不同HTTP方法的视图"""
if request.method == 'GET':
return HttpResponse("这是GET请求")
elif request.method == 'POST':
return HttpResponse("这是POST请求")
elif request.method == 'PUT':
return HttpResponse("这是PUT请求")
elif request.method == 'DELETE':
return HttpResponse("这是DELETE请求")
else:
return HttpResponse(f"不支持的方法: {request.method}", status=405)HttpResponse对象
python
from django.http import (
HttpResponse, JsonResponse, HttpResponseRedirect,
HttpResponseNotFound, HttpResponseForbidden,
HttpResponseServerError, FileResponse
)
from django.shortcuts import redirect
import json
def different_responses(request):
"""演示不同类型的响应"""
response_type = request.GET.get('type', 'html')
if response_type == 'html':
response = HttpResponse("<h1>HTML响应</h1>")
response['Content-Type'] = 'text/html; charset=utf-8'
return response
elif response_type == 'json':
data = {'message': 'JSON响应', 'status': 'success'}
return JsonResponse(data)
elif response_type == 'text':
return HttpResponse("纯文本响应", content_type='text/plain')
elif response_type == 'redirect':
return HttpResponseRedirect('/success/')
# 或使用快捷方式
# return redirect('success_page')
elif response_type == 'error':
return HttpResponseNotFound("页面未找到")
elif response_type == 'custom':
response = HttpResponse("自定义响应")
response.status_code = 201
response['X-Custom-Header'] = 'Custom Value'
response['Cache-Control'] = 'no-cache'
return response
else:
return HttpResponse("未知响应类型")
def download_file(request):
"""文件下载响应"""
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"'
return response
except FileNotFoundError:
return HttpResponseNotFound("文件未找到")
def set_cookie_view(request):
"""设置Cookie的视图"""
response = HttpResponse("Cookie已设置")
response.set_cookie('username', 'john_doe', max_age=3600) # 1小时过期
response.set_cookie('theme', 'dark', secure=True, httponly=True)
return response
def get_cookie_view(request):
"""获取Cookie的视图"""
username = request.COOKIES.get('username', '未知用户')
theme = request.COOKIES.get('theme', '默认主题')
return HttpResponse(f"用户: {username}, 主题: {theme}")URL配置详解
URL模式匹配
python
# urls.py
from django.urls import path, re_path, include
from . import views
urlpatterns = [
# 基本路径匹配
path('', views.home, name='home'),
path('about/', views.about, name='about'),
# 带参数的路径
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'),
# 可选参数
path('blog/', views.blog_list, name='blog_list'),
path('blog/page/<int:page>/', views.blog_list, name='blog_list_page'),
# 正则表达式路径
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),
# 包含其他URL配置
path('api/', include('api.urls')),
path('admin/', include('admin.urls')),
]
# 对应的视图函数
def article_detail(request, id):
return HttpResponse(f"文章ID: {id}")
def article_by_slug(request, slug):
return HttpResponse(f"文章Slug: {slug}")
def user_profile(request, username):
return HttpResponse(f"用户: {username}")
def blog_list(request, page=1):
return HttpResponse(f"博客列表 - 第{page}页")
def year_archive(request, year):
return HttpResponse(f"{year}年的文章")
def month_archive(request, year, month):
return HttpResponse(f"{year}年{month}月的文章")URL命名和反向解析
python
# urls.py
from django.urls import path
from . import views
app_name = 'blog' # 应用命名空间
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
# 在视图中使用URL反向解析
from django.urls import reverse
from django.http import HttpResponseRedirect
def create_post(request):
if request.method == 'POST':
# 处理表单数据
# ...
# 重定向到文章详情页
return HttpResponseRedirect(reverse('blog:post_detail', args=[post.id]))
# 或使用kwargs
return HttpResponseRedirect(reverse('blog:post_detail', kwargs={'pk': post.id}))
# 使用redirect快捷方式
from django.shortcuts import redirect
def create_post_shortcut(request):
if request.method == 'POST':
# 处理表单数据
# ...
# 直接重定向
return redirect('blog:post_detail', pk=post.id)
# 重定向到外部URL
return redirect('https://www.example.com/')html
<!-- 在模板中使用URL反向解析 -->
<a href="{% url 'blog:post_list' %}">文章列表</a>
<a href="{% url 'blog:post_detail' post.id %}">{{ post.title }}</a>
<a href="{% url 'blog:category' category.slug %}">{{ category.name }}</a>
<!-- 带查询参数 -->
<a href="{% url 'blog:post_list' %}?page=2&category=tech">技术文章第2页</a>URL参数和查询字符串
python
# views.py
def search_view(request):
"""处理搜索请求"""
# 获取查询参数
query = request.GET.get('q', '')
category = request.GET.get('category', 'all')
page = request.GET.get('page', 1)
# 获取多个值
tags = request.GET.getlist('tags') # ?tags=python&tags=django
# 处理搜索逻辑
results = []
if query:
# 执行搜索
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):
"""文章归档视图"""
from datetime import datetime
# 构建日期过滤条件
date_filter = {'created_at__year': year}
if month:
date_filter['created_at__month'] = month
if day:
date_filter['created_at__day'] = day
# 查询文章
articles = Article.objects.filter(**date_filter)
# 构建面包屑导航
breadcrumbs = [{'name': '首页', '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)函数视图详解
基本函数视图
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):
"""文章列表视图"""
# 获取所有已发布的文章
articles = Article.objects.filter(published=True).order_by('-created_at')
# 分页处理
from django.core.paginator import Paginator
paginator = Paginator(articles, 10) # 每页10篇文章
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):
"""文章详情视图"""
# 获取文章或返回404
article = get_object_or_404(Article, id=id, published=True)
# 增加浏览次数
article.views += 1
article.save(update_fields=['views'])
# 获取相关文章
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 = 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)处理表单的视图
python
from django.shortcuts import render, redirect
from django.contrib import messages
from .forms import ContactForm, ArticleForm
def contact_view(request):
"""联系表单视图"""
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid():
# 处理表单数据
name = form.cleaned_data['name']
email = form.cleaned_data['email']
message = form.cleaned_data['message']
# 发送邮件或保存到数据库
send_contact_email(name, email, message)
# 显示成功消息
messages.success(request, '您的消息已发送成功!')
# 重定向避免重复提交
return redirect('contact_success')
else:
form = ContactForm()
return render(request, 'contact.html', {'form': form})
def create_article(request):
"""创建文章视图"""
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, '文章创建成功!')
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):
"""编辑文章视图"""
article = get_object_or_404(Article, id=id)
# 权限检查
if article.author != request.user:
messages.error(request, '您没有权限编辑这篇文章')
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, '文章更新成功!')
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)视图装饰器
内置装饰器
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方法限制
@require_http_methods(["GET", "POST"])
def my_view(request):
if request.method == 'GET':
return render(request, 'form.html')
else: # POST
# 处理表单提交
return redirect('success')
@require_POST
def delete_article(request, id):
"""只允许POST请求删除文章"""
article = get_object_or_404(Article, id=id)
# 权限检查
if article.author != request.user:
return HttpResponseForbidden("没有权限删除")
article.delete()
messages.success(request, '文章已删除')
return redirect('blog:article_list')
# 登录要求
@login_required
def profile_view(request):
"""需要登录才能访问的视图"""
return render(request, 'profile.html', {'user': request.user})
# 权限要求
@permission_required('blog.add_article')
def create_article_view(request):
"""需要特定权限才能创建文章"""
# 创建文章的逻辑
pass
# 管理员要求
@staff_member_required
def admin_dashboard(request):
"""只有管理员可以访问"""
return render(request, 'admin_dashboard.html')
# 缓存装饰器
@cache_page(60 * 15) # 缓存15分钟
def cached_view(request):
"""缓存的视图"""
# 执行一些耗时操作
expensive_data = perform_expensive_operation()
return render(request, 'cached_template.html', {'data': expensive_data})
# CSRF豁免(谨慎使用)
@csrf_exempt
def api_endpoint(request):
"""API端点,豁免CSRF检查"""
if request.method == 'POST':
import json
data = json.loads(request.body)
# 处理API请求
return JsonResponse({'status': 'success'})
return JsonResponse({'error': 'Method not allowed'}, status=405)自定义装饰器
python
from functools import wraps
from django.http import HttpResponseForbidden
from django.shortcuts import redirect
import time
def timing_decorator(func):
"""计时装饰器"""
@wraps(func)
def wrapper(request, *args, **kwargs):
start_time = time.time()
response = func(request, *args, **kwargs)
end_time = time.time()
# 添加执行时间到响应头
response['X-Execution-Time'] = f"{end_time - start_time:.3f}s"
return response
return wrapper
def owner_required(model_class, pk_param='pk'):
"""要求用户是对象的所有者"""
def decorator(func):
@wraps(func)
def wrapper(request, *args, **kwargs):
if not request.user.is_authenticated:
return redirect('login')
# 获取对象ID
object_id = kwargs.get(pk_param)
if not object_id:
return HttpResponseForbidden("缺少对象ID")
# 检查所有权
try:
obj = model_class.objects.get(pk=object_id)
if obj.author != request.user:
return HttpResponseForbidden("您不是该对象的所有者")
except model_class.DoesNotExist:
raise Http404("对象不存在")
return func(request, *args, **kwargs)
return wrapper
return decorator
def rate_limit(max_requests=10, window=60):
"""简单的速率限制装饰器"""
def decorator(func):
@wraps(func)
def wrapper(request, *args, **kwargs):
from django.core.cache import cache
# 获取客户端IP
ip = request.META.get('REMOTE_ADDR')
cache_key = f"rate_limit_{ip}_{func.__name__}"
# 检查请求次数
current_requests = cache.get(cache_key, 0)
if current_requests >= max_requests:
return HttpResponse("请求过于频繁,请稍后再试", status=429)
# 增加请求计数
cache.set(cache_key, current_requests + 1, window)
return func(request, *args, **kwargs)
return wrapper
return decorator
# 使用自定义装饰器
@timing_decorator
@login_required
@owner_required(Article, 'id')
def edit_my_article(request, id):
"""编辑我的文章"""
article = get_object_or_404(Article, id=id)
# 编辑逻辑
return render(request, 'edit_article.html', {'article': article})
@rate_limit(max_requests=5, window=300) # 5分钟内最多5次请求
def api_search(request):
"""搜索API"""
query = request.GET.get('q', '')
results = perform_search(query)
return JsonResponse({'results': results})实践示例
示例1:博客系统视图
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):
"""首页视图"""
# 获取最新文章
latest_articles = Article.objects.filter(published=True)[:5]
# 获取热门文章
popular_articles = Article.objects.filter(published=True).order_by('-views')[:5]
# 获取所有分类
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):
"""文章列表视图"""
articles = Article.objects.filter(published=True)
# 分类过滤
category_slug = request.GET.get('category')
if category_slug:
articles = articles.filter(category__slug=category_slug)
# 搜索功能
search_query = request.GET.get('search')
if search_query:
articles = articles.filter(
Q(title__icontains=search_query) |
Q(content__icontains=search_query)
)
# 分页
paginator = Paginator(articles, 12)
page_number = request.GET.get('page')
page_obj = paginator.get_page(page_number)
# 获取所有分类用于过滤器
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 = get_object_or_404(Article, slug=slug, published=True)
# 增加浏览次数
article.views += 1
article.save(update_fields=['views'])
# 获取上一篇和下一篇文章
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()
# 获取同分类的相关文章
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):
"""创建文章视图"""
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, '文章创建成功!')
return redirect('blog:article_detail', slug=article.slug)
else:
form = ArticleForm()
return render(request, 'blog/create_article.html', {'form': form})
def search_view(request):
"""搜索视图"""
query = request.GET.get('q', '').strip()
results = []
if query:
# 搜索文章标题和内容
results = Article.objects.filter(
Q(title__icontains=query) |
Q(content__icontains=query),
published=True
).order_by('-created_at')
# 高亮搜索关键词(简单实现)
for article in results:
article.highlighted_title = article.title.replace(
query, f'<mark>{query}</mark>'
)
# 分页
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)本章小结
本章详细介绍了Django的视图和URL配置:
关键要点:
- 视图概念:处理HTTP请求并返回响应的Python函数
- HttpRequest对象:包含请求信息的对象
- HttpResponse对象:各种类型的HTTP响应
- URL配置:将URL模式映射到视图函数
- URL参数:从URL中提取参数传递给视图
重要概念:
- MVT模式:Model-View-Template架构
- URL反向解析:通过名称生成URL
- 视图装饰器:为视图添加额外功能
- 请求方法处理:GET、POST等不同方法的处理
最佳实践:
- 使用有意义的URL模式和命名
- 合理使用装饰器增强视图功能
- 正确处理不同的HTTP方法
- 使用快捷函数简化常见操作
- 实现适当的错误处理和权限检查
在下一章中,我们将学习Django的模板系统,了解如何创建动态的HTML页面。