跳到主要内容

视图层

视图是 Django 应用的核心组件之一,负责处理用户请求并返回响应。视图函数接收一个 HttpRequest 对象,返回一个 HttpResponse 对象。

视图基础

简单视图

一个最简单的视图:

from django.http import HttpResponse
import datetime


def current_time(request):
"""返回当前时间"""
now = datetime.datetime.now()
html = f"<html><body>当前时间: {now}</body></html>"
return HttpResponse(html)

使用 render 函数

render 函数是常用的视图快捷方式,用于渲染模板:

from django.shortcuts import render
from .models import Post


def post_list(request):
"""文章列表视图"""
posts = Post.objects.filter(status='published')
return render(request, 'blog/post_list.html', {
'posts': posts,
'title': '文章列表'
})

render 函数的参数:

  • request:HttpRequest 对象
  • template_name:模板名称
  • context:模板上下文字典
  • content_type:响应内容类型(可选)
  • status:HTTP 状态码(可选)

返回 JSON 响应

对于 API 接口,通常返回 JSON 格式的数据:

from django.http import JsonResponse
from .models import Post


def api_posts(request):
"""返回文章列表的 JSON 数据"""
posts = Post.objects.filter(status='published').values(
'id', 'title', 'content', 'created'
)
return JsonResponse({'posts': list(posts)})


def api_post_detail(request, pk):
"""返回单篇文章的 JSON 数据"""
try:
post = Post.objects.get(pk=pk, status='published')
return JsonResponse({
'id': post.id,
'title': post.title,
'content': post.content,
'created': post.created.isoformat()
})
except Post.DoesNotExist:
return JsonResponse({'error': '文章不存在'}, status=404)

快捷函数

Django 提供了多个快捷函数简化常见操作:

from django.shortcuts import render, get_object_or_404, get_list_or_404, redirect
from .models import Post


def post_detail(request, pk):
"""文章详情 - 使用 get_object_or_404"""
post = get_object_or_404(Post, pk=pk, status='published')
return render(request, 'blog/post_detail.html', {'post': post})


def published_posts(request):
"""已发布文章列表 - 使用 get_list_or_404"""
posts = get_list_or_404(Post, status='published')
return render(request, 'blog/post_list.html', {'posts': posts})


def go_to_admin(request):
"""重定向 - 使用 redirect"""
return redirect('/admin/')

HttpRequest 对象

视图函数的第一个参数是 HttpRequest 对象,包含请求的所有信息:

def request_info(request):
"""展示请求信息"""
info = {
'method': request.method, # 请求方法
'path': request.path, # 请求路径
'scheme': request.scheme, # 协议 (http/https)
'host': request.get_host(), # 主机名
'user_agent': request.META.get('HTTP_USER_AGENT'),
'remote_addr': request.META.get('REMOTE_ADDR'),
'is_secure': request.is_secure(), # 是否 HTTPS
'is_ajax': request.headers.get('x-requested-with') == 'XMLHttpRequest',
}
return JsonResponse(info)

获取请求参数

def query_params(request):
"""获取查询参数"""
# GET 参数
page = request.GET.get('page', 1) # 获取单个参数,可设置默认值
tags = request.GET.getlist('tags') # 获取多个同名参数
search = request.GET.get('q', '') # 搜索关键词

# POST 数据
if request.method == 'POST':
title = request.POST.get('title')
content = request.POST.get('content')

# JSON 数据(需要解析)
import json
if request.content_type == 'application/json':
data = json.loads(request.body)

return JsonResponse({'status': 'ok'})

请求头信息

def headers_info(request):
"""获取请求头"""
# 方式一:通过 headers 属性(Django 3.2+)
content_type = request.headers.get('Content-Type')
authorization = request.headers.get('Authorization')

# 方式二:通过 META 字典
user_agent = request.META.get('HTTP_USER_AGENT')
referer = request.META.get('HTTP_REFERER')

return JsonResponse({
'content_type': content_type,
'user_agent': user_agent,
})

HttpResponse 对象

基本响应

from django.http import HttpResponse, HttpResponseNotFound


def basic_response(request):
"""基本响应"""
# 简单文本响应
return HttpResponse('Hello, Django!')

# 设置状态码
return HttpResponse('Created', status=201)

# 设置响应头
response = HttpResponse('Hello')
response['X-Custom-Header'] = 'Custom Value'
return response


def not_found(request):
"""404 响应"""
return HttpResponseNotFound('<h1>页面未找到</h1>')

不同类型的响应

from django.http import HttpResponse, JsonResponse, FileResponse, StreamingHttpResponse


def json_response(request):
"""JSON 响应"""
return JsonResponse({
'status': 'success',
'data': {'name': 'Django', 'version': '5.0'}
})


def file_download(request):
"""文件下载"""
file = open('report.pdf', 'rb')
response = FileResponse(file)
response['Content-Disposition'] = 'attachment; filename="report.pdf"'
return response


def streaming_response(request):
"""流式响应"""
def generate():
for i in range(10000):
yield f"Line {i}\n"

return StreamingHttpResponse(generate(), content_type='text/plain')

HTTP 状态码

常用的 HTTP 状态码:

from django.http import HttpResponse


def status_codes(request):
"""演示各种状态码"""
# 2xx 成功
return HttpResponse('OK', status=200) # 成功
return HttpResponse('Created', status=201) # 创建成功
return HttpResponse('No Content', status=204) # 无内容

# 3xx 重定向
from django.shortcuts import redirect
return redirect('/home/') # 302 临时重定向
return redirect('/home/', permanent=True) # 301 永久重定向

# 4xx 客户端错误
return HttpResponse('Bad Request', status=400) # 错误请求
return HttpResponse('Unauthorized', status=401) # 未授权
return HttpResponse('Forbidden', status=403) # 禁止访问
return HttpResponse('Not Found', status=404) # 未找到

# 5xx 服务器错误
return HttpResponse('Internal Error', status=500) # 服务器错误

错误处理

Http404 异常

当需要返回 404 错误时,可以抛出 Http404 异常:

from django.http import Http404
from django.shortcuts import render
from .models import Post


def post_detail(request, pk):
"""文章详情"""
try:
post = Post.objects.get(pk=pk)
if post.status != 'published':
raise Http404("文章未发布")
except Post.DoesNotExist:
raise Http404("文章不存在")

return render(request, 'blog/post_detail.html', {'post': post})

自定义错误页面

在 URLconf 中设置自定义错误处理视图:

# urls.py
handler404 = 'myapp.views.page_not_found'
handler500 = 'myapp.views.server_error'
handler403 = 'myapp.views.permission_denied'
handler400 = 'myapp.views.bad_request'
# views.py
def page_not_found(request, exception):
"""自定义 404 页面"""
return render(request, 'errors/404.html', status=404)


def server_error(request):
"""自定义 500 页面"""
return render(request, 'errors/500.html', status=500)


def permission_denied(request, exception):
"""自定义 403 页面"""
return render(request, 'errors/403.html', status=403)


def bad_request(request, exception):
"""自定义 400 页面"""
return render(request, 'errors/400.html', status=400)

视图装饰器

Django 提供了多种视图装饰器:

HTTP 方法限制

from django.views.decorators.http import require_http_methods, require_GET, require_POST


@require_http_methods(["GET", "POST"])
def edit_post(request, pk):
"""编辑文章 - 只允许 GET 和 POST"""
if request.method == 'POST':
# 处理表单提交
pass
return render(request, 'blog/edit_post.html')


@require_GET
def post_list(request):
"""文章列表 - 只允许 GET"""
posts = Post.objects.all()
return render(request, 'blog/post_list.html', {'posts': posts})


@require_POST
def delete_post(request, pk):
"""删除文章 - 只允许 POST"""
post = get_object_or_404(Post, pk=pk)
post.delete()
return redirect('blog:post_list')

登录验证

from django.contrib.auth.decorators import login_required, permission_required


@login_required
def profile(request):
"""用户资料 - 需要登录"""
return render(request, 'accounts/profile.html')


@login_required(login_url='/accounts/login/')
def dashboard(request):
"""仪表盘 - 自定义登录 URL"""
return render(request, 'dashboard.html')


@permission_required('blog.add_post')
def create_post(request):
"""创建文章 - 需要特定权限"""
pass


@permission_required('blog.delete_post', raise_exception=True)
def delete_post(request, pk):
"""删除文章 - 无权限时抛出 403"""
pass

缓存控制

from django.views.decorators.cache import cache_page, never_cache


@cache_page(60 * 15) # 缓存 15 分钟
def post_list(request):
"""文章列表 - 缓存页面"""
posts = Post.objects.all()
return render(request, 'blog/post_list.html', {'posts': posts})


@never_cache
def dashboard(request):
"""仪表盘 - 永不缓存"""
return render(request, 'dashboard.html')

其他装饰器

from django.views.decorators.csrf import csrf_exempt, csrf_protect
from django.views.decorators.gzip import gzip_page
from django.views.decorators.vary import vary_on_headers


@csrf_exempt
def api_webhook(request):
"""Webhook 接口 - 豁免 CSRF"""
pass


@gzip_page
def large_content(request):
"""大内容页面 - 启用 GZIP 压缩"""
pass


@vary_on_headers('User-Agent')
def mobile_view(request):
"""根据 User-Agent 缓存不同版本"""
pass

异步视图

Django 3.1+ 支持异步视图:

import asyncio
from django.http import HttpResponse


async def async_view(request):
"""异步视图"""
await asyncio.sleep(1) # 模拟异步操作
return HttpResponse('Hello, async Django!')


async def async_database_view(request):
"""异步数据库查询"""
from .models import Post

# 异步查询
posts = await Post.objects.filter(status='published').aall()

return render(request, 'blog/post_list.html', {'posts': posts})

文件上传

处理文件上传:

from django.shortcuts import render, redirect
from django.core.files.storage import default_storage


def upload_file(request):
"""文件上传"""
if request.method == 'POST' and request.FILES.get('file'):
uploaded_file = request.FILES['file']

# 保存文件
path = default_storage.save(f'uploads/{uploaded_file.name}', uploaded_file)

return JsonResponse({
'status': 'success',
'path': path,
'size': uploaded_file.size,
'content_type': uploaded_file.content_type,
})

return render(request, 'upload.html')


def multiple_upload(request):
"""多文件上传"""
if request.method == 'POST':
files = request.FILES.getlist('files')
paths = []

for uploaded_file in files:
path = default_storage.save(f'uploads/{uploaded_file.name}', uploaded_file)
paths.append(path)

return JsonResponse({'status': 'success', 'paths': paths})

return render(request, 'upload.html')

视图最佳实践

1. 保持视图简洁

# 不好的做法:视图包含太多业务逻辑
def bad_view(request):
if request.method == 'POST':
title = request.POST.get('title')
content = request.POST.get('content')
# 大量验证逻辑...
# 大量业务逻辑...
post = Post.objects.create(title=title, content=content)
# 更多处理...
return redirect('post_detail', pk=post.pk)
return render(request, 'create_post.html')


# 好的做法:使用表单处理验证和业务逻辑
def good_view(request):
if request.method == 'POST':
form = PostForm(request.POST)
if form.is_valid():
post = form.save(commit=False)
post.author = request.user
post.save()
return redirect('post_detail', pk=post.pk)
else:
form = PostForm()
return render(request, 'create_post.html', {'form': form})

2. 使用视图函数处理复杂逻辑

def post_list(request):
"""文章列表 - 处理搜索、过滤、分页"""
posts = Post.objects.filter(status='published')

# 搜索
search = request.GET.get('q')
if search:
posts = posts.filter(title__icontains=search)

# 分类过滤
category_slug = request.GET.get('category')
if category_slug:
posts = posts.filter(category__slug=category_slug)

# 分页
from django.core.paginator import Paginator
paginator = Paginator(posts, 10)
page = request.GET.get('page', 1)
posts = paginator.get_page(page)

return render(request, 'blog/post_list.html', {
'posts': posts,
'search': search,
'category_slug': category_slug,
})

3. 使用上下文处理器共享数据

# context_processors.py
def site_settings(request):
"""全局站点设置"""
return {
'site_name': '我的博客',
'site_description': '分享技术和生活',
}

在 settings.py 中注册:

# settings.py
TEMPLATES = [
{
'OPTIONS': {
'context_processors': [
# ...
'myapp.context_processors.site_settings',
],
},
},
]