Django视图与URL配置

Web开发专题 · 掌握Django的视图与URL路由系统

专题: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.GETrequest.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文件,实现模块化路由管理。通过设置 namespaceapp_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_listDetailView 展示单个对象详情,从URL中提取主键或slug定位对象,默认上下文变量名为 object

3. CreateView与UpdateView

CreateView 处理对象创建。指定 modelfields 后自动生成表单,验证成功后调用 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_objpaginator 变量访问。标准的分页导航模板包含上一页链接、页码列表、下一页链接。使用 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">&laquo; 第一页</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 }}">最后一页 &raquo;</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 决定存储位置和访问路径。注意控制上传文件的大小和类型,确保应用安全。