Django 响应对象、文件上传与类视图

Django 响应对象、文件上传与类视图

视图的职责是接收 HttpRequest、执行业务逻辑,并返回 HttpResponse。本章介绍文本、模板、重定向和 JSON 响应的选择方式,说明表单文件上传如何进入 request.FILES,再通过函数视图(FBV)与类视图(CBV)理解 Django 如何按 HTTP 方法分发请求。

一、视图常用响应方式

Django 中最常用的三个响应工具是 HttpResponse、render() 和 redirect():

工具 返回内容 常见场景
HttpResponse 原始文本、字节数据或自定义内容类型 简单文本、CSV、手动构造响应
render() 渲染后的 HTML 响应 返回模板页面
redirect() 3xx 重定向响应 提交成功后跳转、根据路由名跳转

HttpResponse:返回文本或自定义内容

HttpResponse 的内容应是字符串或字节。字典不是 JSON,不能直接作为业务数据返回;若要返回 JSON,应先序列化,或直接使用更合适的 JsonResponse。

python 复制代码
from django.http import HttpResponse


def health_check(request):
    return HttpResponse("ok", content_type="text/plain; charset=utf-8")


def download_csv(request):
    content = "name,age\ndream,18\n"
    response = HttpResponse(content, content_type="text/csv; charset=utf-8")
    response["Content-Disposition"] = 'attachment; filename="students.csv"'
    return response

代码说明:content_type 告诉浏览器如何解释响应内容。返回 HTML 常用 text/html,返回纯文本常用 text/plain,下载文件时可配合 Content-Disposition 指示浏览器作为附件处理。

render:渲染模板页面

render(request, template_name, context=None) 会将上下文字典传给模板并返回 HTML 响应。应显式传递模板真正需要的数据,避免把 locals() 中的所有局部变量意外暴露到模板。

python 复制代码
from django.shortcuts import render


def profile(request):
    user_info = {"name": "dream", "age": 18}
    page_title = "个人资料"

    return render(request, "user/profile.html", {
        "user_info": user_info,
        "page_title": page_title,
    })
html 复制代码
<!-- user/templates/user/profile.html -->
<!doctype html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8">
  <title>{{ page_title }}</title>
</head>
<body>
  <h1>{{ user_info.name }}</h1>
  <p>年龄:{{ user_info.age }}</p>
</body>
</html>

代码说明:render() 的第三个参数是上下文(context)。模板变量默认会进行 HTML 转义,通常不应对不可信内容使用 |safe 或直接输出为 HTML。

redirect:返回跳转响应

redirect() 返回一个重定向响应,默认状态码为 302。优先传入命名路由而不是硬编码 URL,以减少路由变动带来的修改成本。

python 复制代码
from django.shortcuts import redirect


def after_login(request):
    return redirect("user:profile", user_id=1)

代码说明:浏览器收到 302 响应后会再请求目标地址,因此重定向不是"在服务器内部调用另一个视图函数"。处理 POST 提交后常采用"POST-Redirect-GET"模式,避免用户刷新页面时重复提交表单。

二、返回 JSON:JsonResponse 优先

JSON 是接口传输的文本格式,Python 字典不是 JSON 字符串。可以手动调用 json.dumps() 后交给 HttpResponse,但 Django 的 JsonResponse 会自动序列化数据并设置正确的 Content-Type,通常更合适。

python 复制代码
import json

from django.http import HttpResponse, JsonResponse


def manual_json(request):
    data = {"name": "dream", "age": 18}
    content = json.dumps(data, ensure_ascii=False)
    return HttpResponse(content, content_type="application/json; charset=utf-8")


def json_response(request):
    data = {"name": "dream大哥", "age": 18}
    return JsonResponse(data, json_dumps_params={"ensure_ascii": False})

代码说明:ensure_ascii=True(默认值)会把非 ASCII 字符转义为 \uXXXX 形式,这仍是合法 JSON;ensure_ascii=False 则会直接保留中文字符,便于人工阅读。两种写法在浏览器解析后的数据含义相同。

返回列表和其他非字典数据

出于历史安全考虑,JsonResponse 默认只接受字典。若明确需要返回列表,必须传入 safe=False;返回前仍应确认数据中没有敏感字段。

python 复制代码
from django.http import JsonResponse


def student_list(request):
    data = [
        {"id": 1, "name": "dream"},
        {"id": 2, "name": "hope"},
    ]
    return JsonResponse(data, safe=False, json_dumps_params={"ensure_ascii": False})

代码说明:JsonResponse 使用 Django 的 DjangoJSONEncoder,能处理 datetime、Decimal、UUID 等常见类型。模型实例和 QuerySet 不能直接作为 JSON 返回,应先挑选需要的字段并转换为基础类型。

python 复制代码
from django.http import JsonResponse

from .models import Student


def students_api(request):
    students = Student.objects.values("id", "name", "age")
    return JsonResponse({"results": list(students)})

三、表单提交与文件上传

普通 HTML 表单默认使用 application/x-www-form-urlencoded 编码。该编码适合文本字段,文件输入只会传递文件名等文本信息,无法把文件二进制内容作为上传文件交给 Django。

要上传文件,表单必须同时满足两个条件:使用 method="post",并设置 enctype="multipart/form-data"。此外,Django 的 POST 表单应包含 {% csrf_token %}。

html 复制代码
<form method="post" enctype="multipart/form-data">
  {% csrf_token %}

  <label>
    用户名
    <input type="text" name="username" required>
  </label>

  <label>
    头像
    <input type="file" name="avatar" accept="image/*">
  </label>

  <fieldset>
    <legend>爱好</legend>
    <label><input type="checkbox" name="hobby" value="music">音乐</label>
    <label><input type="checkbox" name="hobby" value="reading">阅读</label>
  </fieldset>

  <fieldset>
    <legend>性别</legend>
    <label><input type="radio" name="gender" value="male">男</label>
    <label><input type="radio" name="gender" value="female">女</label>
  </fieldset>

  <button type="submit">提交</button>
</form>

代码说明:accept="image/*" 只是一种浏览器端提示,不能替代服务端文件类型校验。多个复选框使用同一个 name,因此后端需要用 getlist() 获取全部值。

在视图中读取表单和文件数据

文本字段在 request.POST,上传文件在 request.FILES。当未选择文件或字段名不匹配时,request.FILES.get() 返回 None,必须先处理这个情况。

python 复制代码
from django.http import HttpResponseBadRequest
from django.shortcuts import redirect, render


def register(request):
    if request.method == "GET":
        return render(request, "user/register.html")

    username = request.POST.get("username", "").strip()
    hobbies = request.POST.getlist("hobby")
    gender = request.POST.get("gender", "")
    avatar = request.FILES.get("avatar")

    if not username:
        return HttpResponseBadRequest("用户名不能为空")
    if avatar is None:
        return HttpResponseBadRequest("请选择头像")

    # 保存前还需校验文件大小、真实类型和权限。
    print(username, hobbies, gender, avatar.name, avatar.size)
    return redirect("user:register-success")

代码说明:不要读取或打印密码等敏感数据。上传对象可能是 InMemoryUploadedFile 或 TemporaryUploadedFile,取决于文件大小和 Django 的上传处理配置;业务代码只需按 UploadedFile 的公共 API 使用它。

使用存储系统保存上传文件

不应直接将用户提供的文件名拼接到本地路径中,否则可能造成路径穿越、重名覆盖等问题。Django 的 FileSystemStorage 会使用存储后端管理文件名和目录,实际项目还可替换为对象存储。

python 复制代码
from pathlib import Path

from django.conf import settings
from django.core.files.storage import FileSystemStorage


def save_avatar(uploaded_file):
    storage = FileSystemStorage(location=Path(settings.MEDIA_ROOT) / "avatars")
    saved_name = storage.save(uploaded_file.name, uploaded_file)
    return storage.url(saved_name)
python 复制代码
# settings.py(开发环境示例)
MEDIA_URL = "/media/"
MEDIA_ROOT = BASE_DIR / "media"

代码说明:生产环境应将用户上传内容存放在受控目录或对象存储,并限制文件大小、扩展名、MIME 类型和内容。不要把用户上传文件写入 static/;静态资源通常由部署时收集,上传文件属于媒体资源(media)。

四、常用 request 属性

request 是 Django 封装的 HttpRequest 对象,包含请求方法、路径、查询参数、表单数据、上传文件、请求头和用户会话等信息。不同属性代表不同来源的数据,不能互相替代。

属性或方法 作用
request.method 请求方法,如 GET、POST
request.GET URL 查询字符串参数
request.POST 表单编码的 POST 数据,不含文件
request.FILES multipart/form-data 上传的文件
request.body 原始请求体字节,例如 JSON API 请求体
request.headers 请求头的映射对象
request.COOKIES 浏览器发送的 Cookie
request.path 不含查询参数的路径
request.get_full_path() 包含查询字符串的完整路径
request.user 认证中间件提供的当前用户
python 复制代码
import json

from django.http import JsonResponse


def request_info(request):
    if request.method == "POST" and request.content_type == "application/json":
        try:
            payload = json.loads(request.body.decode("utf-8"))
        except (UnicodeDecodeError, json.JSONDecodeError):
            return JsonResponse({"detail": "JSON 格式错误"}, status=400)
        return JsonResponse({"received": payload})

    return JsonResponse({
        "method": request.method,
        "path": request.path,
        "full_path": request.get_full_path(),
        "query": request.GET.dict(),
        "user_agent": request.headers.get("User-Agent", ""),
    })

代码说明:request.GET 和 request.POST 是 QueryDict,同一键可能有多个值。request.GET.dict() 只保留每个键的最后一个值,处理复选框、多选项等重复字段时仍应使用 getlist()。request.body 是原始字节,适用于 JSON 等非表单请求体。

五、FBV 与 CBV

函数视图(FBV,Function-Based View)用一个函数处理请求;类视图(CBV,Class-Based View)用一个类的不同方法处理不同 HTTP 方法。两者都能完成相同业务,选择重点是复杂度与复用需求,而不是谁"更高级"。

函数视图(FBV)

python 复制代码
from django.http import HttpResponseNotAllowed
from django.shortcuts import render


def login(request):
    if request.method == "GET":
        return render(request, "user/login.html")

    if request.method == "POST":
        username = request.POST.get("username", "").strip()
        return render(request, "user/login_success.html", {"username": username})

    return HttpResponseNotAllowed(["GET", "POST"])

代码说明:FBV 直观且适合简单流程。请求方法增多、多个页面共享逻辑,或需要复用 mixin 时,CBV 往往更便于组织。

类视图(CBV)

CBV 继承 django.views.View,将 GET、POST 等 HTTP 方法分别实现为同名小写方法。注册路由时必须调用 .as_view(),它会把类转换为 Django 可调用的视图函数。

python 复制代码
# user/views.py
from django.http import HttpResponseBadRequest
from django.shortcuts import redirect, render
from django.views import View


class LoginView(View):
    template_name = "user/login.html"

    def get(self, request, *args, **kwargs):
        return render(request, self.template_name)

    def post(self, request, *args, **kwargs):
        username = request.POST.get("username", "").strip()
        if not username:
            return HttpResponseBadRequest("用户名不能为空")

        # 完成认证后,使用 PRG 模式跳转。
        return redirect("user:profile", user_id=1)
python 复制代码
# user/urls.py
from django.urls import path

from .views import LoginView

urlpatterns = [
    path("login/", LoginView.as_view(), name="login"),
]

代码说明:每个请求都会创建一个新的 LoginView 实例,因此不要把当前用户、表单数据等可变请求状态放在类属性中。类属性适合保存模板名、固定配置等不随请求改变的数据。

六、CBV 的方法分发机制

as_view() 的主要工作是返回一个普通可调用对象,使 URL 路由可以像使用函数视图一样调用它。收到请求后,Django 大致会执行以下过程:

text 复制代码
path(..., LoginView.as_view())
  -> as_view() 返回 view 函数
  -> 每次请求创建 LoginView 实例
  -> setup() 保存 request、args、kwargs
  -> dispatch() 根据 request.method 查找 get/post/... 方法
  -> 调用对应处理方法并返回 HttpResponse

可以通过重写 dispatch() 为整个类视图添加统一逻辑,例如权限检查;重写时必须调用 super().dispatch(),否则后续的 HTTP 方法分发不会执行。

python 复制代码
from django.http import HttpResponseForbidden
from django.views import View


class StaffOnlyView(View):
    def dispatch(self, request, *args, **kwargs):
        if not request.user.is_authenticated or not request.user.is_staff:
            return HttpResponseForbidden("无权访问")
        return super().dispatch(request, *args, **kwargs)

    def get(self, request, *args, **kwargs):
        return render(request, "staff/dashboard.html")

代码说明:若请求方法不在 http_method_names 中,或类中没有实现对应方法,View.dispatch() 会返回 405 Method Not Allowed。Django 还会自动用 get() 处理 HEAD 请求(当未显式定义 head() 时)。

七、实践要点

  1. 页面响应使用 render(),跳转使用 redirect(),JSON 接口优先使用 JsonResponse。
  2. 对外返回的数据必须经过筛选,不能直接序列化模型全部字段或用户隐私数据。
  3. 文件上传需要 multipart/form-data,文本数据从 POST 读取,文件从 FILES 读取。
  4. 上传文件必须进行服务端校验,并使用媒体存储系统保存,不要拼接用户提供的路径或写入 static/。
  5. 简单逻辑使用 FBV;需要按方法拆分、复用 mixin 或统一权限控制时使用 CBV。

掌握这些响应与视图组织方式后,就可以安全地处理模板页面、JSON 接口和表单上传等常见 Web 请求。

相关推荐
这个DBA有点耶3 天前
MVCC深入:Read View、版本链与快照读——InnoDB并发控制的内核
数据库·mysql·架构
DBA_G3 天前
从地面到云霄:GBase数据库在民航三大场景的落地实践
数据库
自由能燃气设备3 天前
商用全预混低氮冷凝锅炉免费方案vs付费方案对比+选型避坑指南
大数据·数据库·人工智能
科创致远3 天前
科创致远 ESOP 系统核心效能与实战价值展示
大数据·数据库·人工智能·精益工程
kybs19913 天前
全球灾害数据分析可视化 毕业设计-附源码66794
vue.js·spring boot·mysql·安全·django·c#·asp.net
2601_962218613 天前
万象生鲜系统称重自动多退少补算法解决生鲜非标品痛点
大数据·数据库·人工智能·python·算法
张洛闻Eren3 天前
k8s云原生【第十课】:水平 Pod 自动扩缩容
运维·数据库·云原生·kubernetes·github
醇氧3 天前
Python`__pycache__` 被 Git 跟踪的排查与解决
python·django
于平安3 天前
MySQL-触发器
数据库·mysql
白远山3 天前
上海24小时自助健身房解决方案实战指南与经验分享
java·数据库·架构·需求分析