Django视图进阶:快捷函数、装饰器与请求响应
在上一篇文章中欧,我们学习了Django视图与路由的基础流程 —— 通过路由映射请求到视图,用视图函数处理简单逻辑并返回响应。但在实际开发中,我们还需要更高效的工具(如快捷函数)、更灵活的功能扩展(如视图装饰器),以及处理复杂场景(如JSON响应。文件上传)。本篇文章将聚焦这些进阶能力,带你提示Django视图开发的效率与灵活性
一、快捷函数:简化视图开发
Django的 django.shortcuts 模块提供了一组快捷函数,封装了视图开发中常用的重复逻辑(如渲染模板、查询数据、重定向),让你用更少的代码实现更多功能
1.1 render():渲染模版并返回响应
最常用的快捷函数,用于将模板与上下文数据结合,生成 HttpResponse 对象,无需手动创建 HttpResponse ,也无需指定模版路径(Django 会自动在templates 目录下查找)
语法:
render(request, template_name, context=None, content_type=None, status=None, using=None)
①request:Http请求对象(必须传) ②template_name:模版文件名(必须传)
③context:传递给模版的上下文字典(键为模版变量名,值为数据) ④status:Http状态码
举例:
# myapp/views.py
from django.shortcuts import render
from .models import Articledef article_detail(request, pk):"""用render()渲染文章详情模板"""article = Article.objects.get(pk=pk)# 上下文字典:模板中可通过{{ article.title }}访问数据context = {'article': article,'author': article.author.username, # 关联数据'is_favorite': request.user.is_authenticated # 业务逻辑判断}# 渲染模板并返回响应(无需手动创建HttpResponse)return render(request, 'article_detail.html', context)
在模版中使用上下文:
<!-- article_detail.html -->
<h1>{{ article.title }}</h1>
<p>作者:{{ author }}</p>
{% if is_favorite %}<button>添加收藏</button>
{% endif %}
<div>{{ article.content }}</div>
1.2 redirect():重定向到其他URL
用于实现页面跳转,支持三种跳转场景:
① 跳转到命名路由(推荐使用) ② 跳转到带参数的命名路由
③ 跳转到外部URL (如 http://example.com)
语法:
redirect(to, *args, permanent=False, **kwargs)
① to:目标URL(可以是命名路由名、路径字符串、外部 URL)
② permanent:是否为永久重定向(True对应301状态码,False对应302,默认为False)
③ args/kwargs:传递给命名路由的参数
举例:
# myapp/views.py
from django.shortcuts import redirect, render, get_object_or_404
from django.urls import reverse
from .models import Articledef add_article(request):"""添加文章后重定向到文章详情页"""if request.method == 'POST':title = request.POST.get('title')content = request.POST.get('content')# 创建文章article = Article.objects.create(title=title, content=content)# 场景1:跳转到带参数的命名路由(推荐)return redirect('article_detail', pk=article.pk) # 等价于 reverse('article_detail', args=[article.pk])# GET请求:显示添加文章表单return render(request, 'add_article.html')def redirect_to_home(request):"""跳转到首页(命名路由为'home')"""# 场景2:跳转到无参数的命名路由return redirect('home')def redirect_to_external(request):"""跳转到外部URL(如Django官网)"""# 场景3:跳转到外部链接return redirect('https://www.djangoproject.com/')
1.3 get_object_or_404():查询对象或返回404
查询单个对象时,若对象不存在,自动抛出 Http404异常(返回404页面),无需手动写 try-except捕获DoesNotExist 异常,代码简洁明了
语法:
get_object_or_404(klass, *args, **kwargs)
① klass:模型类(如Article)或查询集
② *args/**kwargs:查询条件
举例:
# 优化前:手动try-except
def article_detail_old(request, pk):try:article = Article.objects.get(pk=pk)except Article.DoesNotExist:raise Http404('文章不存在')return render(request, 'article_detail.html', {'article': article})# 优化后:用get_object_or_404()
from django.shortcuts import get_object_or_404def article_detail(request, pk):# 若找不到pk=pk的Article,自动返回404article = get_object_or_404(Article, pk=pk, is_published=True) # 可加额外条件(如仅查询已发布文章)return render(request, 'article_detail.html', {'article': article})
1.4 get_list_or_404():查询列表或返回404
与 get_object_or_404()类似,但用于查询多个对象(查询集):若查询集为空,自动返回 404 页面;若有数据,返回查询集
语法:
get_list_or_404(klass, *args, **kwargs)
参数与 get_object_or_404()一致,返回值为查询集(而非单个对象)
举例:
from django.shortcuts import get_list_or_404def published_articles(request):"""查询已发布的文章列表,为空则返回404"""# 若没有is_published=True的文章,自动返回404articles = get_list_or_404(Article, is_published=True)return render(request, 'article_list.html', {'articles': articles})
二、视图装饰器:增强视图功能
视图装饰器是 Python 装饰器在 Django 视图中的应用,用于在不修改视图函数代码的前提下,为其添加额外功能(如限制 HTTP 方法、要求登录、缓存页面)。Django 提供了多个内置装饰器,也支持自定义
2.1 限制 HTTP 方法:控制请求类型
最常用的装饰器之一,用于限制视图仅接受特定的 HTTP 方法(如仅 GET、仅 POST),避免非法请求(如用 DELETE 方法访问列表页)
常用装饰器:
① @require_http_methods(['方法1', '方法2'])
:仅允许指定的 HTTP 方法
② @require_GET
:仅允许 GET 方法(等价于@require_http_methods(['GET'])
);
③ @require_POST
:仅允许 POST 方法(等价于@require_http_methods(['POST'])
);
④ @require_safe
:仅允许 GET 和 HEAD 方法(安全方法,不修改数据)
举例:
# myapp/views.py
from django.views.decorators.http import require_http_methods, require_GET, require_POST
from django.shortcuts import render, redirect
from .models import Article# 1. 仅允许GET和POST方法
@require_http_methods(['GET', 'POST'])
def edit_article(request, pk):article = get_object_or_404(Article, pk=pk)if request.method == 'GET':# GET:显示编辑表单return render(request, 'edit_article.html', {'article': article})elif request.method == 'POST':# POST:处理表单提交article.title = request.POST.get('title')article.content = request.POST.get('content')article.save()return redirect('article_detail', pk=article.pk)# 2. 仅允许GET方法(显示文章列表)
@require_GET
def article_list(request):articles = Article.objects.all()return render(request, 'article_list.html', {'articles': articles})# 3. 仅允许POST方法(处理表单提交)
@require_POST
def delete_article(request, pk):article = get_object_or_404(Article, pk=pk)article.delete()return redirect('article_list')
效果:当使用不允许的方法访问时(如用 PUT 访问edit_article
),Django 会自动返回405 Method Not Allowed
响应
2.2 @gzip_page
:压缩响应内容
用于对视图返回的响应内容进行 Gzip 压缩,减少网络传输数据量,提升页面加载速度(尤其适合大文本响应,如 HTML、JSON)
举例:
from django.views.decorators.gzip import gzip_page
from django.http import HttpResponse# 压缩响应内容
@gzip_page
def large_response(request):"""返回大文本响应,自动压缩"""# 模拟大文本(如长文章、大量数据)large_text = 'Django 视图装饰器...' * 1000return HttpResponse(large_text)
注意:仅在生产环境生效,开发环境(DEBUG=True
)下可能不压缩
2.3 其他常用装饰器
(1)@login_required
:要求用户登录
限制视图仅允许已登录用户访问,未登录用户会被重定向到登录页面(需在settings.py
中配置LOGIN_URL
)
举例:
from django.contrib.auth.decorators import login_required
from django.shortcuts import render# 要求登录才能访问个人资料页
@login_required(login_url='/login/') # 未登录时重定向到/login/
def user_profile(request):# request.user 为当前登录用户(已通过认证)return render(request, 'profile.html', {'user': request.user})
配置LOGIN_URL
(可选,默认/accounts/login/
):
# settings.py
LOGIN_URL = '/login/' # 未登录用户的重定向地址
(2)@permission_required
:要求用户有特定权限
限制视图仅允许具有指定权限的用户访问(如 “修改文章” 权限),未授权用户返回 403 页面
from django.contrib.auth.decorators import permission_required
from django.shortcuts import get_object_or_404, render
from .models import Article# 要求用户有"修改文章"权限(权限名格式:app_label.permission_name)
@permission_required('myapp.change_article', raise_exception=True)
def edit_article(request, pk):"""仅允许有修改权限的用户编辑文章"""article = get_object_or_404(Article, pk=pk)# 编辑逻辑...return render(request, 'edit_article.html', {'article': article})
① 'myapp.change_article'
:权限名,myapp
是应用名,change_article
是 Django 自动为Article
模型生成的权限(默认有add
/change
/delete
/view
四种权限);
② raise_exception=True
:未授权时直接返回 403,而非重定向
(3)@csrf_exempt
:豁免 CSRF 保护
Django 默认对 POST 请求进行 CSRF 保护(需在表单中添加{% csrf_token %}
),但对于 API 接口(如接收外部服务的 POST 请求),可能需要豁免 CSRF 验证
from django.views.decorators.csrf import csrf_exempt
from django.http import JsonResponse
import json# 豁免CSRF保护(仅用于API接口,谨慎使用)
@csrf_exempt
def api_receive_data(request):if request.method == 'POST':# 接收外部POST数据(无需CSRF令牌)data = json.loads(request.body)# 处理数据...return JsonResponse({'status': 'success', 'data': data})return JsonResponse({'status': 'error'}, status=400)
警告:仅在确认安全的场景下使用(如内部 API),公开接口需用其他认证方式(如 Token)
(4)@cache_page
:缓存页面响应
缓存视图返回的响应,指定时间内重复请求会直接返回缓存结果,无需重新执行视图逻辑(提升高频访问页面的性能)
from django.views.decorators.cache import cache_page
from django.shortcuts import render# 缓存15分钟(单位:秒)
@cache_page(60 * 15)
def popular_articles(request):"""热门文章列表:15分钟内仅执行一次视图逻辑"""# 耗时查询(如排序、过滤大量数据)articles = Article.objects.all().order_by('-views')[:10]return render(request, 'popular_articles.html', {'articles': articles})
注意:缓存基于 URL,不同 URL(如/popular/?page=1
和/popular/?page=2
)会分别缓存
三、内置视图:直接复用的“现成功能”
Django 提供了一些内置视图,封装了常见场景的完整逻辑(如处理静态文件、错误页面),无需自己编写视图函数,直接配置路由即可使用
3.1 serve
:开发环境处理媒体文件
在开发环境中,Django 默认不处理用户上传的媒体文件(如头像、附件),需用django.views.static.serve
视图手动配置媒体文件的访问路径
配置步骤
# 项目主 urls.py
from django.contrib import admin
from django.urls import path
from django.conf import settings
from django.views.static import serve
from django.conf.urls.static import staticurlpatterns = [path('admin/', admin.site.urls),# 配置媒体文件访问:/media/xxx/ → 映射到settings.MEDIA_ROOT/xxxpath('media/<path:path>/', serve, {'document_root': settings.MEDIA_ROOT}),
]# (可选)开发环境静态文件配置(Django 1.11+ 可省略,自动处理)
if settings.DEBUG:urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
配置MEDIA_ROOT
和MEDIA_URL
# settings.py
import os# 媒体文件存储路径(用户上传的文件会保存在这里)
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# 媒体文件访问URL前缀(如 /media/avatar.jpg)
MEDIA_URL = '/media/'
注意:仅用于开发环境,生产环境需用 Nginx/Apache 处理媒体文件
3.2 内置错误视图
Django 提供了 4 个内置错误视图,对应 400、403、404、500 错误,可直接复用或自定义模板
错误类型 | 内置视图 | 作用 |
400 Bad Request | django.views.defaults.bad_request | 请求参数错误 |
403 Permission Denied | django.views.defaults.permission_denied | 权限不足 |
404 Page Not Found | django.views.defaults.page_not_found | 页面不存在 |
500 Internal Server Error | django.views.defaults.server_error | 服务器内部错误 |
自定义内置错误视图的模板
无需修改视图,只需在templates
目录下创建对应模板文件(如404.html
),Django 会自动使用自定义模板:
<!-- templates/404.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>404 - 页面未找到</title>
</head>
<body><h1>抱歉,你访问的页面不存在!</h1><a href="{% url 'home' %}">返回首页</a>
</body>
</html>
四、请求与响应对象:处理复杂数据交互
Django 封装了HttpRequest
(请求)和HttpResponse
(响应)对象,用于处理 HTTP 请求的所有信息(如参数、头信息、文件)和返回多样化的响应(如 HTML、JSON、文件)
4.1 HttpRequest
对象:获取请求的所有信息
HttpRequest
对象由 Django 自动创建,作为视图函数的第一个参数(通常命名为request
),包含请求的所有信息
核心属性与方法
属性 / 方法 | 描述 | 示例 |
---|---|---|
request.method | HTTP 请求方法(大写字符串,如'GET' 、'POST' ) | if request.method == 'POST': |
request.GET | GET 请求参数(QueryDict 对象,类似字典) | page = request.GET.get('page', 1) |
request.POST | POST 请求参数(表单数据,QueryDict 对象) | title = request.POST.get('title') |
request.META | 请求头信息(字典,键为大写,前缀HTTP_ ) | user_agent = request.META.get('HTTP_USER_AGENT') |
request.COOKIES | 请求中的 Cookie(字典) | session_id = request.COOKIES.get('sessionid') |
request.FILES | 上传的文件(MultiValueDict 对象) | avatar = request.FILES.get('avatar') |
request.user | 当前登录用户(User 对象,未登录为AnonymousUser ) | if request.user.is_authenticated: |
request.path | 请求的路径(不含域名和查询参数) | /article/1/ |
实战示例:获取请求数据
def handle_request(request):# 1. 获取请求方法method = request.method # 'GET' 或 'POST'# 2. 获取GET参数(如 /search/?q=django)search_query = request.GET.get('q', '') # 默认值为空字符串# 3. 获取POST参数(表单提交)if request.method == 'POST':username = request.POST.get('username')password = request.POST.get('password')# 4. 获取请求头(如用户代理)user_agent = request.META.get('HTTP_USER_AGENT') # 浏览器信息# 5. 获取上传文件(如头像)avatar = request.FILES.get('avatar') # 需表单设置 enctype="multipart/form-data"# 6. 获取当前用户if request.user.is_authenticated:user_info = f"当前用户:{request.user.username}"else:user_info = "未登录"return HttpResponse(f"方法:{method}<br>查询:{search_query}<br>{user_info}")
4.2 HttpResponse
及其子类:返回多样化响应
HttpResponse
是所有响应对象的基类,Django 还提供了多个子类,用于返回特定类型的响应(如 JSON、文件)
(1)基础HttpResponse
:返回文本响应
用于返回简单文本、HTML 等响应,可设置响应头和 Cookie
from django.http import HttpResponsedef simple_response(request):# 1. 基础文本响应response = HttpResponse('Hello, Django!', status=200)# 2. 设置响应头response['Content-Type'] = 'text/plain' # 响应类型response['X-Custom-Header'] = 'MyValue' # 自定义头# 3. 设置Cookie(max_age单位:秒)response.set_cookie('name', 'Django', max_age=3600) # 1小时有效期# 4. 删除Cookieresponse.delete_cookie('old_name')return response
(2)JsonResponse
:返回 JSON 响应
专门用于返回 JSON 格式的响应(如 API 接口),自动设置Content-Type: application/json
,无需手动序列化字典
from django.http import JsonResponsedef api_data(request):# 字典数据(自动序列化为JSON)data = {'status': 'success','data': {'articles': [{'id': 1, 'title': 'Django路由'},{'id': 2, 'title': 'Django视图'}]}}# 返回JSON响应(safe=True表示data必须是字典,否则需设为False)return JsonResponse(data)
返回列表数据(需设safe=False
):
def api_list(request):articles = [{'id': 1, 'title': 'Django'}, {'id': 2, 'title': 'Python'}]return JsonResponse(articles, safe=False) # 列表需设safe=False
(3)StreamingHttpResponse
:流式传输大文件
用于返回大型文件(如视频、压缩包),避免一次性加载整个文件到内存,而是分块传输
from django.http import StreamingHttpResponse
import osdef stream_large_file(request):# 大文件路径file_path = os.path.join(settings.MEDIA_ROOT, 'large_file.mp4')file_size = os.path.getsize(file_path)# 定义文件迭代器:分块读取文件def file_iterator(file_path, chunk_size=8192):with open(file_path, 'rb') as f:while True:chunk = f.read(chunk_size) # 每次读取8KBif not chunk:breakyield chunk# 流式响应response = StreamingHttpResponse(file_iterator(file_path),content_type='video/mp4' # 视频文件类型)# 设置文件大小(可选,用于进度条)response['Content-Length'] = str(file_size)return response
(4)FileResponse
:返回文件下载
StreamingHttpResponse
的子类,专门用于返回文件下载,自动处理文件读取和响应头设置
from django.http import FileResponse
import osdef download_file(request):# 文件路径file_path = os.path.join(settings.MEDIA_ROOT, 'document.pdf')# 打开文件(rb:二进制只读)file = open(file_path, 'rb')# 文件响应:自动设置Content-Type和下载头response = FileResponse(file,content_type='application/pdf' # PDF文件类型)# 设置Content-Disposition:触发浏览器下载(而非预览)response['Content-Disposition'] = 'attachment; filename="document.pdf"'return response
五、模板响应对象:延迟渲染模板
普通render()
会立即渲染模板并返回响应,而 “模板响应对象”(SimpleTemplateResponse
、TemplateResponse
)允许在返回响应前延迟渲染模板,并对模板进行后期处理(如修改上下文、添加响应头)
5.1 SimpleTemplateResponse
:基础延迟渲染
from django.template.response import SimpleTemplateResponsedef delayed_render(request):# 1. 创建模板响应对象(未渲染)context = {'name': 'Django'}response = SimpleTemplateResponse('index.html', context)# 2. 延迟处理:返回前修改上下文response.context_data['version'] = '5.0' # 模板中可访问{{ version }}# 3. 返回响应(此时才渲染模板)return response
5.2 TemplateResponse
:支持请求上下文
SimpleTemplateResponse
的子类,自动包含请求上下文(如request
对象、user
对象),无需手动传递
from django.template.response import TemplateResponsedef template_response(request):# 创建TemplateResponse,自动包含请求上下文response = TemplateResponse(request, 'index.html', {'name': 'Django'})# 添加“渲染后回调”:模板渲染完成后执行def post_render_callback(response):# 在响应内容末尾添加注释response.content += b' <!-- 渲染完成 -->'return response# 注册回调函数response.add_post_render_callback(post_render_callback)return response
六、文件上传:实现用户上传功能
Django 支持用户上传文件(如头像、附件),需通过 “表单 + 视图” 配合实现,核心是处理request.FILES
中的上传文件
6.1 步骤 1:定义文件上传表单
需在表单中设置enctype="multipart/form-data"
(否则无法传递文件数据),可使用 Django 的Form
类简化表单验证
# myapp/forms.py
from django import formsclass UploadAvatarForm(forms.Form):"""用户头像上传表单"""# FileField:文件上传字段,自动验证文件类型avatar = forms.FileField(label="选择头像",help_text="支持JPG、PNG格式,不超过5MB")# 可选:添加其他字段(如用户名)username = forms.CharField(max_length=100, label="用户名")
6.2 步骤 2:编写上传视图
处理 GET 请求(显示表单)和 POST 请求(处理文件上传),将上传的文件保存到指定目录(如media/avatars/
)
# myapp/views.py
from django.shortcuts import render, redirect
from .forms import UploadAvatarForm
import os
from django.conf import settingsdef upload_avatar(request):if request.method == 'POST':# 1. 绑定表单数据(request.POST 为普通字段,request.FILES 为文件字段)form = UploadAvatarForm(request.POST, request.FILES)if form.is_valid():# 2. 获取表单数据username = form.cleaned_data['username']avatar_file = request.FILES['avatar'] # 或 form.cleaned_data['avatar']# 3. 保存文件到 media/avatars/ 目录# 构造保存路径(避免文件名重复,可加用户名前缀)save_dir = os.path.join(settings.MEDIA_ROOT, 'avatars')# 确保目录存在(不存在则创建)os.makedirs(save_dir, exist_ok=True)# 保存文件(分块写入,避免内存溢出)with open(os.path.join(save_dir, f"{username}_avatar.jpg"), 'wb+') as destination:for chunk in avatar_file.chunks(): # 分块读取上传文件destination.write(chunk)# 4. 上传成功,重定向到结果页return redirect('upload_success')else:# GET请求:显示空表单form = UploadAvatarForm()# 渲染表单页面return render(request, 'upload_avatar.html', {'form': form})def upload_success(request):"""上传成功页面"""return render(request, 'upload_success.html')
6.3 步骤 3:编写上传模板
<!-- templates/upload_avatar.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>上传头像</title>
</head>
<body><h1>上传用户头像</h1><!-- 必须设置 enctype="multipart/form-data" 才能传递文件 --><form method="post" enctype="multipart/form-data">{% csrf_token %} <!-- CSRF保护 -->{{ form.as_p }} <!-- 渲染表单字段(含标签和错误提示) --><button type="submit">上传</button></form>
</body>
</html><!-- templates/upload_success.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>上传成功</title>
</head>
<body><h1>头像上传成功!</h1><a href="{% url 'upload_avatar' %}">继续上传</a>
</body>
</html>
6.4 步骤 4:配置媒体文件
确保settings.py
中配置了媒体文件路径(参考 3.1 节),并在urls.py
中配置媒体文件访问路由
七、总结
本篇覆盖了 Django 视图开发的核心进阶能力,总结如下:
1、快捷函数:render()
、redirect()
、get_object_or_404()
等简化重复逻辑,提升开发效率
2、视图装饰器:控制 HTTP 方法、要求登录 / 权限、缓存页面,增强视图功能
3、请求与响应对象:处理复杂请求数据(如 GET/POST 参数、文件),返回多样化响应(JSON、文件)
4、文件上传:通过表单 + 视图 + 媒体文件配置,实现用户文件上传功能
掌握这些内容后,你已经能应对大多数 Django 视图开发场景 —— 从简单的页面渲染到复杂的 API 接口、文件处理。结合之前学习的模型与路由知识,你可以搭建起完整的 Django Web 应用骨架,为后续学习模板美化、用户认证等内容打下坚实基础