0

0

Django 模型中如何在 save() 方法内安全处理上传文件

碧海醫心

碧海醫心

发布时间:2026-01-20 09:22:48

|

881人浏览过

|

来源于php中文网

原创

Django 模型中如何在 save() 方法内安全处理上传文件

在 django 中,直接通过文件路径访问 `filefield` 未保存的文件会导致 filenotfounderror;正确做法是读取文件字节流,在内存中完成图像处理后再写回字段,避免依赖尚未创建的物理路径。

Django 的 FileField 在模型实例调用 save() 前不会将文件写入磁盘——它仅在 super().save() 执行时才触发文件上传与存储。因此,像 media_path = "media/upload/..." 这样的硬编码路径在 save() 早期阶段必然不存在,Image.open(media_path) 必然失败。

✅ 正确方案:全程操作文件字节流(in-memory),不依赖磁盘路径。以下是优化后的 Media.save() 实现:

from PIL import Image
from io import BytesIO
import os

class Media(models.Model):
    title = models.CharField(max_length=255, null=True, blank=True)
    file = models.FileField(upload_to="upload/")
    filename = models.CharField(max_length=255, null=True, blank=True)
    mime_type = models.CharField(max_length=255, null=True, blank=True)
    thumbnail = models.JSONField(null=True, blank=True)
    size = models.FloatField(null=True, blank=True)
    url = models.CharField(max_length=300, null=True, blank=True)
    thumbhash = models.CharField(max_length=255, blank=True, null=True)
    is_public = models.BooleanField(blank=True, null=True)

    def save(self, *args, **kwargs):
        # ✅ 1. 重置文件指针并读取原始字节(关键!)
        self.file.seek(0)  # 确保从头读取
        original_bytes = self.file.read()

        # ✅ 2. 使用 BytesIO 构建内存图像对象
        image = Image.open(BytesIO(original_bytes))
        mime_type = image.get_format_mimetype()
        format_ext = mime_type.split("/")[-1].lower()

        # ✅ 3. 处理缩略图(同样在内存中完成)
        sizes = [(150, 150), (256, 256)]
        thumbnail = {}

        # 创建 cache 目录(确保存在)
        cache_dir = os.path.join("media", "cache")
        os.makedirs(cache_dir, exist_ok=True)

        for i, (w, h) in enumerate(sizes):
            resized = image.resize((w, h), Image.Resampling.LANCZOS)
            index = "small" if i == 0 else "medium"

            # 生成唯一缓存路径(注意:使用 self.pk 仅在更新时可靠;新建时用临时命名或延迟生成)
            cache_name = f"{self.pk or 'tmp'}-resized-{self.filename or 'unknown'}-{index}.{format_ext}"
            cache_path = os.path.join(cache_dir, cache_name)

            # 保存到磁盘(此时 media/cache 已存在)
            resized.save(cache_path, format=format_ext.upper())
            thumbnail[f"{w}x{h}"] = f"cache/{cache_name}"  # 存储相对路径,便于前端访问

        # ✅ 4. 更新字段(注意:filename 应由 upload_to 或逻辑自动推导,不建议手动设)
        if not self.filename:
            self.filename = os.path.basename(self.file.name)
        self.mime_type = mime_type
        self.size = len(original_bytes)
        self.thumbnail = thumbnail
        self.url = self.file.url  # ✅ 使用 Django 自动提供的 URL(更健壮)
        self.thumbhash = image_to_thumbhash(image)  # 假设该函数接受 PIL.Image

        # ✅ 5. 写回处理后的字节(可选:若需修改原文件内容)
        # self.file = ContentFile(processed_bytes, name=self.file.name)

        super().save(*args, **kwargs)

⚠️ 重要注意事项:

站长俱乐部购物系统
站长俱乐部购物系统

功能介绍:1、模块化的程序设计,使得前台页面设计与程序设计几乎完全分离。在前台页面采用过程调用方法。在修改页面设计时只需要在相应位置调用设计好的过程就可以了。另外,这些过程还提供了不同的调用参数,以实现不同的效果;2、阅读等级功能,可以加密产品,进行收费管理;3、可以完全可视化编辑文章内容,所见即所得;4、无组件上传文件,服务器无需安装任何上传组件,无需支持FSO,即可上传文件。可限制文件上传的类

下载
  • self.file.seek(0) 不可省略:Django 文件对象在序列化/上传后指针可能位于末尾,不重置将读取空内容;
  • 不要硬编码 media/ 路径:Django 静态/媒体文件路径应通过 settings.MEDIA_ROOT 获取,或使用 default_storage;
  • self.pk 在首次保存时为 None:生成缓存文件名时需兼容(如用 uuid.uuid4() 替代);
  • 避免重复保存大文件:若仅需生成缩略图而无需修改原图,无需 self.file.write(...) —— 上述示例中 write 并非必须,除非你确实要覆盖原始文件内容;
  • Serializer 中 create 方法有误:当前 return Media(**validated_data) 不会调用 save(),应改为 Media.objects.create(...) 或显式调用 save()。

? 总结:Django 文件处理的核心原则是——信任 FileField 的抽象接口,用 .read()/.seek() 操作字节流,用 default_storage 或 os.path.join(settings.MEDIA_ROOT, ...) 安全构造路径,永远不要假设未保存文件已存在于磁盘。

相关专题

更多
硬盘接口类型介绍
硬盘接口类型介绍

硬盘接口类型有IDE、SATA、SCSI、Fibre Channel、USB、eSATA、mSATA、PCIe等等。详细介绍:1、IDE接口是一种并行接口,主要用于连接硬盘和光驱等设备,它主要有两种类型:ATA和ATAPI,IDE接口已经逐渐被SATA接口;2、SATA接口是一种串行接口,相较于IDE接口,它具有更高的传输速度、更低的功耗和更小的体积;3、SCSI接口等等。

1027

2023.10.19

PHP接口编写教程
PHP接口编写教程

本专题整合了PHP接口编写教程,阅读专题下面的文章了解更多详细内容。

66

2025.10.17

php8.4实现接口限流的教程
php8.4实现接口限流的教程

PHP8.4本身不内置限流功能,需借助Redis(令牌桶)或Swoole(漏桶)实现;文件锁因I/O瓶颈、无跨机共享、秒级精度等缺陷不适用高并发场景。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

454

2025.12.29

java接口相关教程
java接口相关教程

本专题整合了java接口相关内容,阅读专题下面的文章了解更多详细内容。

10

2026.01.19

Java JVM 原理与性能调优实战
Java JVM 原理与性能调优实战

本专题系统讲解 Java 虚拟机(JVM)的核心工作原理与性能调优方法,包括 JVM 内存结构、对象创建与回收流程、垃圾回收器(Serial、CMS、G1、ZGC)对比分析、常见内存泄漏与性能瓶颈排查,以及 JVM 参数调优与监控工具(jstat、jmap、jvisualvm)的实战使用。通过真实案例,帮助学习者掌握 Java 应用在生产环境中的性能分析与优化能力。

13

2026.01.20

PS使用蒙版相关教程
PS使用蒙版相关教程

本专题整合了ps使用蒙版相关教程,阅读专题下面的文章了解更多详细内容。

60

2026.01.19

java用途介绍
java用途介绍

本专题整合了java用途功能相关介绍,阅读专题下面的文章了解更多详细内容。

87

2026.01.19

java输出数组相关教程
java输出数组相关教程

本专题整合了java输出数组相关教程,阅读专题下面的文章了解更多详细内容。

39

2026.01.19

java接口相关教程
java接口相关教程

本专题整合了java接口相关内容,阅读专题下面的文章了解更多详细内容。

10

2026.01.19

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
WEB前端教程【HTML5+CSS3+JS】
WEB前端教程【HTML5+CSS3+JS】

共101课时 | 8.4万人学习

JS进阶与BootStrap学习
JS进阶与BootStrap学习

共39课时 | 3.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号