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

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

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

一、视图常用响应方式

Django 中最常用的三个响应工具是 HttpResponserender()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,能处理 datetimeDecimalUUID 等常见类型。模型实例和 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")

代码说明:不要读取或打印密码等敏感数据。上传对象可能是 InMemoryUploadedFileTemporaryUploadedFile,取决于文件大小和 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 请求方法,如 GETPOST
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.GETrequest.POSTQueryDict,同一键可能有多个值。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,将 GETPOST 等 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 请求。

相关推荐
ltl2 小时前
数据库作为 LLM 记忆体:语义缓存、RAG 与一致性
数据库
ltl2 小时前
WAL 与崩溃恢复:ARIES 协议怎么跑通
数据库
ltl2 小时前
TEE 数据库:EnclaveDB、Oblivious 原语与机密 SQL
数据库
麻瓜code4 小时前
【Mysql】重新学一遍 SQL 执行顺序
数据库·sql
这个DBA有点耶4 小时前
数据库一体机架构演进:从硬件堆叠到软硬深度耦合
服务器·网络·数据库·硬件架构·运维开发·database·数据库架构
安_5 小时前
RAG的向量数据库:为LLM提供语义搜索能力
数据库
喜欢的名字被抢了6 小时前
程序出问题怎么查,以及如何让它不掉线
java·运维·数据库
这个DBA有点耶6 小时前
3000万个应用共享一套数据库:多租户“逻辑表”架构是如何做到的?
数据库·架构·dba
测试运维日常笔记6 小时前
Oracle 数据库连接认证方式详解
数据库·oracle