专题:Python Web开发系统学习
关键词:Python, Web开发, Django视图, URL配置, 类视图, ListView, CreateView, Mixin, 分页
一、视图函数(Function-Based Views)
视图函数是Django中最基本的视图处理方式。每个视图函数接收一个 HttpRequest 对象作为第一个参数,并返回一个 HttpResponse 对象。视图函数的本质就是一个Python函数,它从请求中提取数据、处理业务逻辑、最终生成响应返回给客户端。
1. request对象常用属性
request 对象封装了HTTP请求的全部信息。常用的属性包括:request.META 包含所有HTTP头信息,如 REMOTE_ADDR(客户端IP)、HTTP_USER_AGENT(浏览器标识);request.GET 和 request.POST 分别是 QueryDict 类型的GET和POST参数;request.FILES 包含上传的文件数据;request.session 提供会话读写接口;request.user 是当前登录用户的实例(AUTH 认证后才填充)。此外还有 request.method(请求方法)、request.path(路径)、request.body(原始请求体)等。
2. HttpResponse与常用快捷函数
视图函数需要返回 HttpResponse 或其子类的实例。HttpResponse(content) 直接返回字符串内容,可设置 status 状态码和 Content-Type 头。render(request, template, context) 是最常用的快捷函数,将模板与上下文字典结合后返回HTML。redirect(to) 返回一个重定向响应(301/302),参数可以是URL字符串、视图名称或模型对象。JsonResponse(data) 自动将Python字典序列化为JSON格式并设置正确的 Content-Type。Http404(exception) 配合 raise Http404 可以主动返回404错误页面,常用于对象不存在的场景。
from django.http import HttpResponse, JsonResponse, Http404
from django.shortcuts import render, redirect
from django.views.decorators.http import require_http_methods
from .models import Article
@require_http_methods(["GET", "POST"])
def article_detail(request, article_id):
# request.method == 'GET' / 'POST'
# request.GET.get('page', 1) 获取查询参数
# request.POST.get('title') 获取表单数据
try:
article = Article.objects.get(id=article_id)
except Article.DoesNotExist:
raise Http404("文章不存在")
if request.method == 'POST':
# 处理POST请求 ...
return redirect('article_detail', article_id=article_id)
return render(request, 'blog/detail.html', {
'article': article,
'user': request.user,
})
def api_articles(request):
# 返回JSON响应
articles = Article.objects.all().values('id', 'title')
return JsonResponse(list(articles), safe=False)
要点:视图函数始终接收 HttpRequest、返回 HttpResponse。render 渲染模板,redirect 执行重定向,JsonResponse 返回API数据,Http404 用于主动抛出404。使用 @require_http_methods 装饰器可以限制视图接受的HTTP方法。
二、URL配置进阶
Django的URL配置(URLconf)将URL模式映射到对应的视图函数或类视图上。理解URL配置是构建Django应用的基础,良好的URL设计直接影响项目的可维护性和SEO效果。
1. path()路由语法
path(route, view, kwargs=None, name=None) 是Django 2.0之后推荐的路由定义方式。route是URL路径字符串,不支持正则表达式,而是使用尖括号定义的路由参数:<类型:名称>。
2. 路径转换器
Django内置了五种路径转换器。str 匹配任意非空字符串但不含斜杠,是默认转换器;int 匹配零或正整数,传递给视图时转换为Python int对象;slug 匹配由ASCII字母、数字、连字符和下划线组成的短标签;uuid 匹配UUID格式字符串并转为uuid.Uuid对象;path 匹配包含斜杠的完整路径,适合处理文件路径。
3. re_path()正则路由
当内置转换器无法满足需求时,可以使用 re_path() 使用正则表达式定义路由。提取的参数是字符串类型,需在视图函数中手动转换类型。
4. include()与命名空间
include() 用于包含其他URLconf文件,实现模块化路由管理。通过设置 namespace 和 app_name 可以创建路由命名空间,确保不同应用间同名路由不冲突。reverse(viewname, args, kwargs) 根据视图名称和参数反向解析出URL字符串,在视图和模板中均可使用。reverse_lazy() 是延迟求值版本,适合在类属性定义等需要文件加载时就解析的场景。
# urls.py 主路由
from django.urls import path, include, re_path, reverse
from . import views
urlpatterns = [
# path() 基本用法
path('articles/', views.article_list, name='article_list'),
path('articles/<int:year>/', views.article_archive, name='article_archive'),
path('articles/<int:year>/<slug:slug>/', views.article_detail, name='article_detail'),
# re_path() 正则路由
re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.article_month),
# include() 包含子路由 + 命名空间
path('blog/', include('blog.urls', namespace='blog')),
]
# blog/urls.py 子路由
app_name = 'blog'
urlpatterns = [
path('<int:pk>/', views.PostDetailView.as_view(), name='post_detail'),
]
# 在视图中反向解析
def my_view(request):
url = reverse('blog:post_detail', kwargs={'pk': 42})
# url == '/blog/42/'
提示:始终为你的路由命名(name参数),这将使后续维护和重构更加方便。命名空间(namespace + app_name)允许不同应用使用相同的路由名而不冲突。使用 reverse() 替代硬编码URL可以避免修改URL结构时全局搜索替换的麻烦。
三、类视图(Class-Based Views)
类视图将视图逻辑封装在类的方法中,通过继承和混入(Mixin)实现代码复用。Django提供了大量预置的通用类视图,覆盖了CRUD操作的常见场景。类视图的核心思想是"组合优于继承"——通过Mixin组合来构建功能丰富的视图。
1. View基类与TemplateView
django.views.generic.View 是所有类视图的基类。它将HTTP方法映射到同名小写方法:GET请求调用 get() 方法,POST请求调用 post() 方法,以此类推。TemplateView 继承自View,专门用于展示模板,只需设置 template_name 属性并覆盖 get_context_data() 方法即可传递模板变量。
2. ListView与DetailView
ListView 用于展示对象列表。自动从 model 属性获取查询集,也可覆盖 get_queryset() 方法自定义数据来源。默认模板名为 <应用名>/<模型名>_list.html,上下文变量名为 object_list。DetailView 展示单个对象详情,从URL中提取主键或slug定位对象,默认上下文变量名为 object。
3. CreateView与UpdateView
CreateView 处理对象创建。指定 model 和 fields 后自动生成表单,验证成功后调用 form_valid() 保存数据并重定向到详情页。UpdateView 用于编辑已有对象,需额外识别对象主键。两者的 success_url 属性指定成功后的跳转地址。
4. DeleteView与FormView
DeleteView 用于删除对象。通常GET请求确认删除页面,POST请求执行删除操作。需设置 success_url 指定删除后的跳转地址。FormView 提供更灵活的表单处理,需指定 form_class,适合处理不与模型直接关联的表单场景。
from django.views.generic import (
ListView, DetailView, CreateView,
UpdateView, DeleteView, TemplateView
)
from django.urls import reverse_lazy
from .models import Article
# ListView - 文章列表
class ArticleListView(ListView):
model = Article
template_name = 'blog/article_list.html'
context_object_name = 'articles' # 模板中使用的变量名
paginate_by = 10 # 分页
def get_queryset(self):
return Article.objects.filter(published=True)
# DetailView - 文章详情
class ArticleDetailView(DetailView):
model = Article
template_name = 'blog/article_detail.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['related_articles'] = Article.objects.exclude(id=self.object.id)[:5]
return context
# CreateView - 创建文章
class ArticleCreateView(CreateView):
model = Article
fields = ['title', 'content', 'category']
template_name = 'blog/article_form.html'
success_url = reverse_lazy('blog:article_list')
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
# UpdateView - 更新文章
class ArticleUpdateView(UpdateView):
model = Article
fields = ['title', 'content', 'category']
template_name = 'blog/article_form.html'
# DeleteView - 删除文章
class ArticleDeleteView(DeleteView):
model = Article
template_name = 'blog/article_confirm_delete.html'
success_url = reverse_lazy('blog:article_list')
要点:类视图通过属性和方法覆盖进行配置。关键属性包括 model、template_name、context_object_name、paginate_by、fields、success_url。关键方法包括 get_queryset()、get_context_data()、form_valid()。合理利用这些可配置点可以避免编写大量重复代码。
四、类视图Mixin
Mixin是类视图架构中的核心概念。Django通过Mixin为视图添加认证、权限、缓存、CSRF保护等横切关注点。一个视图可以同时继承多个Mixin,但需注意继承顺序——父类在左、View在右的原则。
1. LoginRequiredMixin认证限制
LoginRequiredMixin 限制视图仅允许已认证用户访问。未登录用户会被重定向到登录页面(可通过 login_url 配置)。通常放置在继承列表的最左侧以确保优先执行。
2. PermissionRequiredMixin权限控制
PermissionRequiredMixin 检查用户是否拥有特定权限。设置 permission_required 属性指定所需的权限字符串(如 'blog.add_article'),也可覆盖 has_permission() 方法实现自定义权限逻辑。无权限用户返回403页面。
3. UserPassesTestMixin自定义测试
UserPassesTestMixin 提供最灵活的控制方式。覆盖 test_func() 方法并返回布尔值,决定当前用户是否有访问权限。适合检查用户是否是文章作者、是否属于特定用户组等场景。
4. 多重继承组合Mixin
可以将多个Mixin与通用视图组合使用。常见的组合模式是 LoginRequiredMixin + UserPassesTestMixin + UpdateView,既确保用户已登录,又验证用户是对象的作者。继承顺序需遵循"Mixin在前、通用视图在中间、View基类在最后"的原则。
from django.contrib.auth.mixins import (
LoginRequiredMixin, PermissionRequiredMixin,
UserPassesTestMixin
)
from django.views.generic import UpdateView
# LoginRequiredMixin - 仅允许已登录用户
class ProfileView(LoginRequiredMixin, UpdateView):
model = UserProfile
fields = ['bio', 'avatar']
template_name = 'accounts/profile.html'
login_url = '/accounts/login/' # 未登录时跳转地址
# PermissionRequiredMixin - 权限控制
class AdminArticleEditView(PermissionRequiredMixin, UpdateView):
model = Article
fields = '__all__'
permission_required = 'blog.change_article'
# 无权限时返回 403 Forbidden
# UserPassesTestMixin - 自定义测试
class AuthorArticleEditView(UserPassesTestMixin, UpdateView):
model = Article
fields = ['title', 'content']
def test_func(self):
# 只有作者本人才能编辑
article = self.get_object()
return self.request.user == article.author
注意:Mixin的继承顺序至关重要。Python的方法解析顺序(MRO)决定了哪个类的方法被优先调用。按此规则组织:认证Mixin → 权限Mixin → 通用视图 → View。错误顺序可能导致认证检查被跳过。
五、分页
当数据量较大时,分页是改善用户体验的重要手段。Django内置的 Paginator 类提供了完善的分页功能。在ListView中设置 paginate_by 属性即可自动启用分页。
1. Paginator核心API
Paginator(object_list, per_page) 接收一个对象列表和每页条目数。其 page(number) 方法返回指定页码的 Page 对象。Paginator 本身提供 count(总条目数)、num_pages(总页数)、page_range(页码范围)等属性。Page 对象提供 object_list(当前页面数据)、has_previous() / has_next()、previous_page_number() / next_page_number()、start_index() / end_index() 等方法。
2. 模板中的分页导航
在ListView的模板中,分页数据通过 page_obj 和 paginator 变量访问。标准的分页导航模板包含上一页链接、页码列表、下一页链接。使用 page_obj.has_previous() 判断是否有上一页,用 paginator.page_range 迭代显示页码。
3. 自定义分页
可以通过继承 Paginator 覆盖方法,或使用第三方库如 django-el-pagination 实现滚动加载、AJAX分页等高级功能。
# 视图中的手动分页
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.shortcuts import render
from .models import Article
def article_list(request):
articles = Article.objects.filter(published=True).order_by('-created_at')
paginator = Paginator(articles, 10) # 每页10条
page_number = request.GET.get('page', 1)
try:
page_obj = paginator.page(page_number)
except PageNotAnInteger:
page_obj = paginator.page(1)
except EmptyPage:
page_obj = paginator.page(paginator.num_pages)
return render(request, 'blog/article_list.html', {
'page_obj': page_obj,
'paginator': paginator,
})
<!-- 模板中的分页导航 -->
<div class="pagination">
<span class="step-links">
{% if page_obj.has_previous %}
<a href="?page=1">« 第一页</a>
<a href="?page={{ page_obj.previous_page_number }}">上一页</a>
{% endif %}
<span class="current">
第 {{ page_obj.number }} / {{ paginator.num_pages }} 页
</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">下一页</a>
<a href="?page={{ paginator.num_pages }}">最后一页 »</a>
{% endif %}
</span>
</div>
六、文件上传处理
文件上传是Web应用中常见的需求,Django提供了完整的文件上传处理机制。从请求接收、模型存储到服务配置,形成了一套端到端的解决方案。
1. request.FILES获取上传文件
当表单使用 enctype="multipart/form-data" 编码时,上传的文件数据存储在 request.FILES 中。每个文件是一个 UploadedFile 对象,可通过 request.FILES['field_name'] 获取。UploadedFile提供 name(文件名)、size(字节数)、content_type(MIME类型)、read() / chunks() 等方法。
2. FileField与ImageField模型字段
FileField(upload_to='path/') 和 ImageField 是Django模型中的文件字段。upload_to 参数指定上传文件的存储子目录,支持使用 strftime 格式(如 uploads/%Y/%m/%d/)自动按日期组织文件。上传后模型实例的相应字段返回一个 FieldFile 对象,提供 .url(文件URL)和 .path(文件系统路径)属性。
3. MEDIA_ROOT与MEDIA_URL配置
在 settings.py 中需配置 MEDIA_ROOT(上传文件在文件系统中的存储根目录)和 MEDIA_URL(上传文件的URL访问前缀)。开发环境下需在 urls.py 中增加静态文件服务路由,以便通过浏览器访问上传的文件。生产环境推荐使用Nginx或CDN托管上传文件。
4. 文件上传大小限制
Django通过 DATA_UPLOAD_MAX_MEMORY_SIZE(默认2.5MB)和 FILE_UPLOAD_MAX_MEMORY_SIZE(默认2.5MB)控制上传大小。超过限制会抛出 RequestDataTooBig 异常。也可在Web服务器层(Nginx的 client_max_body_size)设置更严格的上限。
# models.py - 定义文件上传模型
from django.db import models
class Document(models.Model):
title = models.CharField(max_length=100)
file = models.FileField(upload_to='documents/%Y/%m/%d/')
image = models.ImageField(upload_to='images/%Y/%m/%d/')
uploaded_at = models.DateTimeField(auto_now_add=True)
# settings.py - 文件上传配置
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'
DATA_UPLOAD_MAX_MEMORY_SIZE = 5242880 # 5MB
# urls.py - 开发环境文件服务
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
# ... 其他路由
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL,
document_root=settings.MEDIA_ROOT)
# views.py - 处理文件上传
from django.views.generic.edit import CreateView
class DocumentUploadView(CreateView):
model = Document
fields = ['title', 'file', 'image']
template_name = 'upload.html'
success_url = '/upload/success/'
def form_valid(self, form):
uploaded_file = self.request.FILES['file']
print(f"文件名: {uploaded_file.name}")
print(f"文件大小: {uploaded_file.size} bytes")
print(f"文件类型: {uploaded_file.content_type}")
return super().form_valid(form)
要点:文件上传流程:表单设置 enctype → 视图通过 request.FILES 获取 → 模型用 FileField/ImageField 存储 → 配置 MEDIA_ROOT/MEDIA_URL 决定存储位置和访问路径。注意控制上传文件的大小和类型,确保应用安全。