Django 5 中实现文件上传功能

下面是一个在 Django 5 中实现文件上传功能的完整指南。我们将从最基础的配置开始,逐步构建一个包含模型、表单、视图和模板的完整示例。


📁 第一步:基础配置 (settings.py)

首先,需要在项目的 settings.py 文件中配置媒体文件的保存路径和访问URL。

python 复制代码
# settings.py
import os
from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent.parent

# 1. 媒体文件存储路径 (上传的文件会存到这里)
MEDIA_ROOT = os.path.join(BASE_DIR, 'media') # 项目根目录下的 media 文件夹

# 2. 媒体文件访问URL (通过这个URL前缀可以访问上传的文件)
MEDIA_URL = '/media/'

# 3. (可选) 配置文件上传大小限制
# FILE_UPLOAD_MAX_MEMORY_SIZE = 10485760  # 10MB

完成配置后,需要在项目根目录下手动创建一个 media 文件夹,否则上传时会报错。


🌐 第二步:配置路由 (urls.py)

为了让 Django 在开发环境下能提供媒体文件的访问服务,需要配置路由。

python 复制代码
# 项目的 urls.py
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    path('admin/', admin.site.urls),
    # ... 你的应用路由
]

# 仅在开发环境 (DEBUG=True) 下启用,生产环境请使用 Nginx 等服务器处理
if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

🗄️ 第三步:定义模型 (models.py)

如果你需要将上传文件的信息(如文件名、上传时间)保存到数据库,就需要定义一个模型。核心是使用 FileFieldImageField

python 复制代码
# your_app/models.py
from django.db import models

class UploadedFile(models.Model):
    # upload_to 指定文件存储在 MEDIA_ROOT 下的子目录
    # 可以使用 %Y/%m/%d 等 strftime 格式化参数,按日期自动创建子目录
    file = models.FileField(upload_to='uploads/%Y/%m/%d/')
    uploaded_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.file.name

别忘了执行数据库迁移命令

bash 复制代码
python manage.py makemigrations
python manage.py migrate

📝 第四步:创建表单 (forms.py)

使用 Django 表单能自动处理文件数据的绑定和验证,让代码更简洁、安全。这里我们使用 ModelForm 来直接关联刚才定义的模型。

python 复制代码
# your_app/forms.py
from django import forms
from .models import UploadedFile

class UploadFileForm(forms.ModelForm):
    class Meta:
        model = UploadedFile
        fields = ['file'] # 表单中只显示文件上传字段

👁️ 第五步:编写视图 (views.py)

视图函数负责处理上传逻辑。关键点在于,必须将 request.FILES 传递给表单。

python 复制代码
# your_app/views.py
from django.shortcuts import render, redirect
from .forms import UploadFileForm

def upload_file(request):
    if request.method == 'POST':
        # 注意:必须同时传入 request.POST 和 request.FILES
        form = UploadFileForm(request.POST, request.FILES)
        if form.is_valid():
            # 表单验证通过后,调用 save() 方法,文件会自动保存到 MEDIA_ROOT
            form.save()
            # 上传成功后重定向到成功页面
            return redirect('upload_success') 
    else:
        form = UploadFileForm()
    return render(request, 'upload.html', {'form': form})

def upload_success(request):
    return render(request, 'success.html')

🖼️ 第六步:创建模板 (upload.html)

模板中的 <form> 标签有两个关键点:

  1. method 必须为 "post"
  2. enctype 必须为 "multipart/form-data"
html 复制代码
<!-- your_app/templates/upload.html -->
<!DOCTYPE html>
<html>
<head>
    <title>文件上传</title>
</head>
<body>
    <h2>上传文件</h2>
    <form method="post" enctype="multipart/form-data">
        {% csrf_token %} <!-- Django 的 CSRF 防护令牌,必须包含 -->
        {{ form.as_p }} <!-- 渲染表单,包含 file 字段 -->
        <button type="submit">上传</button>
    </form>
</body>
</html>

🚀 第七步:添加应用路由 (urls.py)

最后,将应用的视图添加到路由中。

python 复制代码
# your_app/urls.py
from django.urls import path
from . import views

app_name = 'your_app'
urlpatterns = [
    path('', views.upload_file, name='upload_file'),
    path('success/', views.upload_success, name='upload_success'),
]

# 别忘了在项目的 urls.py 中 include 这个应用的 urls.py

📚 深入理解:处理上传的文件对象

在视图中,通过 request.FILES 获取的文件对象(如 myFile)提供了以下常用属性和方法:

  • myFile.name: 获取上传文件的文件名。
  • myFile.size: 获取上传文件的大小(字节)。
  • myFile.content_type: 获取文件的 MIME 类型。
  • myFile.chunks(): 推荐的读取方式。它将文件分成数据块(chunk)进行迭代,适合处理大文件,能有效节省内存。
  • myFile.read(): 一次性读取整个文件,仅适用于小文件
  • myFile.multiple_chunks(): 判断文件是否大于 FILE_UPLOAD_MAX_MEMORY_SIZE (默认2.5MB),可用于决定使用 read() 还是 chunks()

🔒 重要安全提示

  1. 文件大小限制 :应在 Web 服务器层面(如 Nginx)配置文件大小限制,而不仅仅依赖 Django 的 FILE_UPLOAD_MAX_MEMORY_SIZE 设置。
  2. 内容验证:不要仅依赖文件扩展名。应验证文件内容(如检查文件的"魔数"字节)来确保其类型与声称的一致。
  3. 安全标头 :提供用户上传的文件时,应设置安全的响应头,例如 Content-Disposition: attachmentX-Content-Type-Options: nosniff,以防止跨站脚本攻击。
  4. 保持更新:请关注 Django 官方安全公告,并及时升级到最新版本,以修复已知的安全漏洞。

💎 总结

至此,你就完成了一个健壮的文件上传功能。这个流程涵盖了从项目配置到前端展示的完整链路,你可以根据实际需求(例如上传图片、限制文件类型等)在此基础上进行扩展。


这段代码是一种手动处理文件上传 的方式,使用的是 Django 的底层 API。它直接操作 request.FILES,自行打开文件流,分块写入到服务器指定目录。


🔍 逐行拆解

python 复制代码
myFile = request.FILES.get("myfile", None)
  • request.FILES 是一个类似字典的对象,包含了所有上传的文件数据,仅当请求方法为 POST 且表单的 enctype="multipart/form-data" 时才有内容。
  • .get("myfile", None) 尝试获取名为 myfile 的文件字段,如果不存在则返回 None(而不是抛出异常)。
python 复制代码
if myFile:
    f = open(os.path.join("D:\\myFile", myFile.name), "wb+")
  • 假设存在该文件,则使用 Python 内置的 open 函数以二进制写入模式("wb+")打开一个目标文件,路径硬编码为 D:\myFile,文件名取自 myFile.name(用户上传的原始文件名)。
  • 注意:硬编码路径非常危险,且没有做任何文件名过滤(可能导致路径遍历攻击)。
python 复制代码
for chunk in myFile.chunks():
    f.write(chunk)
f.close()
  • chunks() 是 Django 上传文件对象提供的方法,它会将文件内容分块(默认每块 2.5MB)迭代返回,适合处理大文件,避免一次性加载到内存。
  • 逐块写入磁盘。
python 复制代码
return HttpResponse("文件上传成功!")
  • 返回一个简单的成功响应。

📜 这是哪种写法?

这是 "原始(raw)文件上传处理" ,在 Django 早期版本或一些需要完全自定义上传逻辑的场景中常见。它不依赖任何 Django 的表单(forms.Form)、模型(models.Model)或内置的存储系统(django.core.files.storage),纯粹手动管理文件流的读写。


⚖️ 与"推荐写法"的对比

维度 手动写法(如你贴的代码) 推荐写法(使用 Form / ModelForm)
代码量 较多,需要处理打开、写入、关闭、异常等 极少,只需 form.save() 即可完成存储
验证 无任何验证(文件大小、类型、内容) 可通过表单字段的 validators 轻松添加
安全性 文件名未处理,易受路径遍历攻击;缺少 MIME 类型校验 Django 自动清理文件名(get_available_name),并可配置白名单
存储位置 硬编码路径,不易维护 通过 MEDIA_ROOT + upload_to 动态生成,可切换存储后端(如云存储)
数据库关联 无,文件信息不保存,无法追踪 模型字段自动关联,文件信息与记录绑定
错误处理 需自己写 try/except 表单验证自动返回错误信息到模板
大文件处理 手动使用 chunks() 已经很好 同样支持分块,且可配合 FileUploadHandler 自定义

⚠️ 潜在风险和问题

  1. 路径安全os.path.join("D:\\myFile", myFile.name) 若用户上传 ../../etc/passwd 这样的文件名,可能会覆盖系统文件(取决于权限)。Django 的 upload_to 会正确处理路径并保证安全。
  2. 文件名冲突 :如果两个用户上传同名文件,后一个会覆盖前一个。推荐做法是使用 uuid 重命名文件。
  3. 硬编码路径 :部署到其他服务器时可能 D: 盘不存在,导致上传失败。应使用 settings.MEDIA_ROOT
  4. 缺少 MIME 检查 :攻击者可以上传 .php.html 文件,结合服务器配置可能导致远程代码执行。

🤔 什么时候还会用这种方式?

  • 学习/演示目的,想理解底层原理。
  • 需要高度定制化的存储逻辑(例如同时写入多个后端、加密存储等),但即便如此,通常也可以通过自定义存储后端来实现,而不是完全放弃 Django 的封装。

💡 如何改进这段代码(逐步向推荐方式靠拢)

  1. 使用 settings.MEDIA_ROOT 替代硬编码路径。
  2. 使用 django.core.files.storage.default_storage.save() 替代手动 open
  3. 引入表单验证 ,至少用 forms.FileField 检查文件大小和类型。
  4. 将文件路径存入数据库,以便后续检索和管理。
  5. 返回更友好的页面(而不是纯文本 HttpResponse)。

如果你只是需要一个简单的上传功能,我更推荐你使用我们之前讲解的 Form + Model 方式,既省心又安全。手动写法虽然"能跑",但在实际生产环境中请务必避免。😊

如果还想了解如何安全地处理文件名(防止覆盖、路径遍历),或如何限制文件类型,我可以再展开细说。

相关推荐
vx-程序开发2 小时前
【java项目分享】springboot农产品销售平台14952
java·javascript·spring boot·python·eclipse·django·php
zuozewei2 小时前
《GB/T 47241-2026 - 虚拟电厂技术导则》国标解析.md
数据库
小张同学a.3 小时前
LAMP架构4——MySQL高可用
linux·运维·服务器·数据库·mysql·架构·负载均衡
Xxtaoaooo13 小时前
工业 IoT 存储引擎选型:多模引擎在数据模型与存储设计上的取舍
数据库·物联网·dolphindb
凌虚15 小时前
面向 MySQL 用户的 PostgreSQL 快速上手指南
数据库·后端·架构
五阿哥永琪15 小时前
MySQL中操作json的函数!
数据库·mysql·json
来者皆善15 小时前
了解Mysql优化吗?如何优化索引?
数据库·mysql
赤壁小虾18 小时前
【渲染流水线】[逐片元阶段]-[透明度测试]以UnityURP为例
java·前端·数据库
MC皮蛋侠客18 小时前
SQLAlchemy 系列(七):高级建模与高效写入——批量 DML、方言与扩展
数据库·python