Django从零开始(三):视图与URL路由详解
一、Django视图概述
视图(View)是Django的MVT架构中的"V",负责处理用户的HTTP请求,执行业务逻辑,并返回HTTP响应。你可以把视图理解为URL与数据处理之间的桥梁。
Django提供了两种定义视图的方式:
- 函数视图(FBV):基于函数,简单直观,适合初学者
- 类视图(CBV):基于类,面向对象,支持继承和混入,适合复杂场景
二、函数视图(FBV)
2.1 基本函数视图
# blog/views.py
from django.http import HttpResponse, JsonResponse, HttpResponseRedirect
from django.shortcuts import render, get_object_or_404, redirect
from .models import Article, Category, Tag
def article_list(request):
"""文章列表视图"""
articles = Article.objects.filter(status='published').order_by('-created_at')
categories = Category.objects.all()
context = {
'articles': articles,
'categories': categories,
'total_count': articles.count(),
}
return render(request, 'blog/article_list.html', context)
def article_detail(request, slug):
"""文章详情视图"""
article = get_object_or_404(Article, slug=slug, status='published')
# 使用F对象更新浏览量,避免竞态条件
Article.objects.filter(pk=article.pk).update(views=F('views') + 1)
related_articles = Article.objects.filter(
category=article.category,
status='published'
).exclude(pk=article.pk)[:5]
context = {
'article': article,
'related_articles': related_articles,
}
return render(request, 'blog/article_detail.html', context)
2.2 处理不同HTTP方法
def article_create(request):
"""创建文章(处理GET和POST)"""
if request.method == 'POST':
title = request.POST.get('title', '').strip()
content = request.POST.get('content', '').strip()
category_id = request.POST.get('category')
# 数据验证
if not title or not content:
return render(request, 'blog/article_form.html', {
'error': '标题和内容不能为空',
'categories': Category.objects.all(),
})
article = Article.objects.create(
title=title,
slug=slugify(title),
content=content,
author=request.user,
category_id=category_id,
status='draft',
)
return redirect('blog:article_detail', slug=article.slug)
# GET请求 - 显示创建表单
categories = Category.objects.all()
return render(request, 'blog/article_form.html', {'categories': categories})
def article_api(request):
"""处理多种HTTP方法的API视图"""
if request.method == 'GET':
articles = list(Article.objects.filter(status='published').values(
'id', 'title', 'slug', 'views', 'created_at'
))
return JsonResponse({'articles': articles, 'total': len(articles)})
elif request.method == 'POST':
import json
data = json.loads(request.body)
article = Article.objects.create(
title=data['title'],
content=data['content'],
author=request.user,
)
return JsonResponse({'id': article.id, 'message': '创建成功'}, status=201)
elif request.method == 'PUT':
data = json.loads(request.body)
article = Article.objects.get(pk=data['id'])
article.title = data.get('title', article.title)
article.save()
return JsonResponse({'message': '更新成功'})
elif request.method == 'DELETE':
data = json.loads(request.body)
Article.objects.filter(pk=data['id']).delete()
return JsonResponse({'message': '删除成功'})
return JsonResponse({'error': '不支持的请求方法'}, status=405)
2.3 常用请求对象属性
def request_info(request):
"""展示request对象的常用属性"""
# 请求方法
request.method # 'GET', 'POST', 'PUT', 'DELETE'等
# 请求头信息
request.META # 所有请求头的字典
request.META.get('HTTP_USER_AGENT') # 浏览器信息
request.META.get('REMOTE_ADDR') # 客户端IP
# GET参数(URL查询参数)
request.GET.get('page', 1) # ?page=2
request.GET.getlist('tags') # ?tags=1&tags=2(多值)
# POST数据
request.POST.get('title') # 表单字段
request.POST.getlist('categories') # 多选字段
# JSON请求体
import json
data = json.loads(request.body)
# 文件上传
request.FILES.get('avatar') # 上传的文件对象
file = request.FILES['document']
file.name # 文件名
file.size # 文件大小(字节)
file.content_type # MIME类型
# Cookie
request.COOKIES.get('sessionid')
# 用户信息(需要认证中间件)
request.user # 当前用户对象
request.user.is_authenticated # 是否已登录
request.user.username # 用户名
三、类视图(CBV)
3.1 基本类视图
from django.views import View
from django.views.generic import (
ListView, DetailView, CreateView, UpdateView, DeleteView,
TemplateView
)
class ArticleListView(ListView):
"""文章列表视图"""
model = Article
template_name = 'blog/article_list.html'
context_object_name = 'articles'
paginate_by = 10 # 每页显示10条
def get_queryset(self):
"""自定义查询集"""
queryset = Article.objects.filter(status='published')
# 支持分类筛选
category_slug = self.kwargs.get('category_slug')
if category_slug:
queryset = queryset.filter(category__slug=category_slug)
# 支持搜索
search = self.request.GET.get('q')
if search:
queryset = queryset.filter(title__icontains=search)
return queryset.select_related('category', 'author').prefetch_related('tags')
def get_context_data(self, **kwargs):
"""添加额外上下文数据"""
context = super().get_context_data(**kwargs)
context['categories'] = Category.objects.all()
context['tags'] = Tag.objects.all()
context['search'] = self.request.GET.get('q', '')
return context
class ArticleDetailView(DetailView):
"""文章详情视图"""
model = Article
template_name = 'blog/article_detail.html'
context_object_name = 'article'
slug_field = 'slug'
slug_url_kwarg = 'slug'
def get_queryset(self):
return Article.objects.filter(status='published')
def get_object(self, queryset=None):
"""重写获取对象方法,增加浏览量"""
article = super().get_object(queryset)
Article.objects.filter(pk=article.pk).update(views=F('views') + 1)
# 重新获取更新后的对象
article.refresh_from_db()
return article
class ArticleCreateView(CreateView):
"""创建文章视图"""
model = Article
template_name = 'blog/article_form.html'
fields = ['title', 'category', 'tags', 'content', 'status']
success_url = '/blog/'
def form_valid(self, form):
"""表单验证通过后,自动设置作者"""
form.instance.author = self.request.user
form.instance.slug = slugify(form.instance.title)
return super().form_valid(form)
class ArticleUpdateView(UpdateView):
"""更新文章视图"""
model = Article
template_name = 'blog/article_form.html'
fields = ['title', 'category', 'tags', 'content', 'status']
context_object_name = 'article'
def get_success_url(self):
return reverse('blog:article_detail', kwargs={'slug': self.object.slug})
class ArticleDeleteView(DeleteView):
"""删除文章视图"""
model = Article
success_url = '/blog/'
def form_valid(self, form):
messages.success(self.request, '文章删除成功!')
return super().form_valid(form)
3.2 通用类视图继承关系
# Django通用视图继承层次
View # 最基础的视图类
├── TemplateView # 渲染模板
├── RedirectView # 重定向
├── ListView # 列表展示
├── DetailView # 详情展示
├── FormView # 表单展示
├── CreateView # 创建记录
├── UpdateView # 更新记录
└── DeleteView # 删除记录
# ListView 提供的方法
get_queryset() # 获取查询集
get_context_data() # 获取上下文数据
get_paginate_by() # 获取每页数量
paginate_queryset() # 分页处理
# DetailView 提供的方法
get_object() # 获取单个对象
get_context_data() # 获取上下文数据
get_slug_field() # 获取slug字段名
3.3 使用Mixin扩展功能
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
class LoginRequiredMixin(LoginRequiredMixin):
"""要求用户登录"""
login_url = '/accounts/login/'
redirect_field_name = 'next'
class AuthorRequiredMixin:
"""要求当前用户是文章作者"""
def get_queryset(self):
return super().get_queryset().filter(author=self.request.user)
class ArticleCreateView(LoginRequiredMixin, CreateView):
"""只有登录用户才能创建文章"""
model = Article
fields = ['title', 'category', 'tags', 'content', 'status']
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
class ArticleUpdateView(LoginRequiredMixin, AuthorRequiredMixin, UpdateView):
"""只有文章作者才能编辑"""
model = Article
fields = ['title', 'category', 'tags', 'content', 'status']
四、URL路由配置
4.1 基本URL配置
# blog/urls.py
from django.urls import path, re_path
from . import views
app_name = 'blog'
urlpatterns = [
# 基本路径
path('', views.article_list, name='article_list'),
path('detail/<slug:slug>/', views.article_detail, name='article_detail'),
# 类视图URL
path('list/', views.ArticleListView.as_view(), name='article_list_cbv'),
path('create/', views.ArticleCreateView.as_view(), name='article_create'),
path('update/<slug:slug>/', views.ArticleUpdateView.as_view(), name='article_update'),
path('delete/<int:pk>/', views.ArticleDeleteView.as_view(), name='article_delete'),
]
4.2 路径转换器(Path Converters)
# Django内置路径转换器
# int - 匹配正整数
path('article/<int:id>/', views.article_by_id)
# str - 匹配非空字符串(不含/)
path('category/<str:name>/', views.category_articles)
# slug - 匹配slug字符串(字母、数字、连字符、下划线)
path('article/<slug:slug>/', views.article_detail)
# uuid - 匹配UUID格式
path('doc/<uuid:uid>/', views.document_detail)
# path - 匹配任意字符串(包含/)
path('file/<path:filepath>/', views.serve_file)
4.3 正则表达式路由
# 使用re_path进行正则匹配
from django.urls import re_path
urlpatterns = [
# 匹配年份和月份
re_path(r'^archive/(?P<year>\d{4})/(?P<month>\d{2})/$', views.archive),
# 匹配文章ID(1-99999)
re_path(r'^article/(?P<pk>\d{1,5})/$', views.article_detail),
# 匹配多种格式
re_path(r'^feed\.(json|xml)$', views.feed),
]
4.4 URL反向解析
# 在视图和模板中使用reverse反向解析URL
from django.urls import reverse
# 基本反向解析
url = reverse('blog:article_list') # /blog/
# 带参数的反向解析
url = reverse('blog:article_detail', kwargs={'slug': 'django-tutorial'})
# /blog/detail/django-tutorial/
# 带查询参数
url = reverse('blog:article_list') + '?page=2'
# /blog/?page=2
# 在模板中使用
# <a href="{% url 'blog:article_detail' slug=article.slug %}">{{ article.title }}</a>
# 获取当前URL
current_url = request.path
full_url = request.build_absolute_uri()
4.5 URL命名空间
# 项目urls.py - 使用include创建命名空间
urlpatterns = [
path('blog/', include('blog.urls', namespace='blog')),
path('shop/', include('shop.urls', namespace='shop')),
path('admin/', admin.site.urls),
]
# 在视图和模板中通过 命名空间:name 引用
reverse('blog:article_list')
reverse('shop:product_detail', kwargs={'pk': 1})
# 模板中
# {% url 'blog:article_detail' slug=article.slug %}
五、请求与响应对象
5.1 响应类型
from django.http import (
HttpResponse, JsonResponse, HttpResponseRedirect,
FileResponse, StreamingHttpResponse, Http404
)
from django.template.loader import render_to_string
# HTML响应
def html_response(request):
return HttpResponse('<h1>Hello</h1>', content_type='text/html')
# JSON响应
def json_response(request):
data = {'message': 'success', 'code': 200}
return JsonResponse(data, safe=False) # safe=False允许返回非字典对象
# 重定向
def redirect_view(request):
return HttpResponseRedirect('/blog/')
# 或使用快捷方式
return redirect('blog:article_list')
# 文件下载
def download_file(request, filename):
file_path = os.path.join(settings.MEDIA_ROOT, filename)
return FileResponse(open(file_path, 'rb'), as_attachment=True)
# 流式响应(大文件)
def stream_file(request):
def file_generator():
with open('large_file.zip', 'rb') as f:
while chunk := f.read(8192):
yield chunk
return StreamingHttpResponse(file_generator(), content_type='application/zip')
# 404错误
def custom_404(request):
raise Http404('页面不存在')
# 自定义状态码
def custom_status(request):
return HttpResponse('Created', status=201)
return JsonResponse({'error': '参数错误'}, status=400)
5.2 快捷函数
from django.shortcuts import render, get_object_or_404, get_list_or_404, redirect
# render - 渲染模板
def my_view(request):
return render(request, 'template.html', {'key': 'value'})
# 指定内容类型
return render(request, 'template.html', context, content_type='text/html')
# get_object_or_404 - 获取对象或返回404
article = get_object_or_404(Article, pk=1)
article = get_object_or_404(Article, slug='django', status='published')
# get_list_or_404 - 获取列表或返回404
articles = get_list_or_404(Article, status='published')
# redirect - 重定向
return redirect('/blog/')
return redirect('blog:article_detail', slug='django')
return redirect(article) # 自动调用get_absolute_url()
六、装饰器
from django.views.decorators.http import require_http_methods, require_GET, require_POST
from django.views.decorators.csrf import csrf_exempt
from django.contrib.auth.decorators import login_required
# 限制HTTP方法
@require_http_methods(['GET', 'POST'])
def article_form(request):
pass
@require_GET
def article_list(request):
pass
@require_POST
def article_create(request):
pass
# 要求登录
@login_required(login_url='/accounts/login/')
def dashboard(request):
pass
# 类视图使用装饰器
from django.utils.decorators import method_decorator
@method_decorator(login_required, name='dispatch')
class ArticleCreateView(CreateView):
model = Article
fields = ['title', 'content']
# 豁免CSRF(API场景)
@csrf_exempt
def api_endpoint(request):
pass
七、错误处理
# blog/views.py
def custom_error_404(request, exception):
"""自定义404错误页面"""
return render(request, 'errors/404.html', status=404)
def custom_error_500(request):
"""自定义500错误页面"""
return render(request, 'errors/500.html', status=500)
# urls.py 中配置
handler404 = 'blog.views.custom_error_404'
handler500 = 'blog.views.custom_error_500'
八、总结
本章我们学习了Django视图和URL路由的核心知识:
- 函数视图(FBV)和类视图(CBV)的用法和区别
- 请求对象(request)的常用属性和方法
- 多种响应类型和快捷函数
- URL路由配置:path、re_path、路径转换器
- URL反向解析和命名空间
- 装饰器和Mixin的使用
下一章我们将学习 Django模板系统,掌握如何在前端页面中展示动态数据。
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。







