Django 模型中如何在 save() 方法内安全处理上传文件
技术百科
碧海醫心
发布时间:2026-01-20
浏览: 次 在 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)⚠️ 重要注意事项:
- 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, ...) 安全构造路径,永远不要假设未保存文件已存在于磁盘。
# ai
# 可选
# 则是
# 图像处理
# 不存在
# 首次
# 仅需
# 会将
# 时才
# js
# json
# go
# 对象
# 编码
# 字节
# 指针
# 接口
# 时用
# 前端
# django
# 应由
相关栏目:
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
AI推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
SEO优化<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
技术百科<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
谷歌推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
百度推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
网络营销<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
案例网站<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
精选文章<?muma echo $count; ?>
】
相关推荐
- Win11键盘快捷键大全_Windows 11常用
- php485读数据时阻塞怎么办_php485非阻塞
- LINUX怎么进行文本内容搜索_Linux gre
- Win11如何设置开机问候语 Win11修改登录界
- C#如何序列化对象为XML XmlSerializ
- php增删改查报错1054怎么办_字段名错误排查修
- C++如何使用std::async进行异步编程?(
- Win11如何设置ipv6 Win11开启IPv6
- Win11怎么关闭任务栏小图标_Windows11
- Python函数缓存机制_lru_cache解析【
- Win11无法安装软件怎么办_Win11解除应用安
- Win11怎么设置DNS服务器_Windows11
- c# 如何用c#实现一个支持优先级的任务队列
- 如何在 Go 中正确测试带 Cookie 的 HT
- 如何在Golang中使用闭包_封装变量与函数作用域
- c++如何获取map中所有的键_C++遍历键值对提
- c# 在高并发下使用反射发射(Reflection
- Win11怎么关闭应用权限_Windows11相机
- Mac电脑进水了怎么办_MacBook进水后紧急处
- Mac如何修改Hosts文件?(本地开发与屏蔽网站
- Win10怎样清理C盘爱奇艺缓存_Win10清理爱
- php文件怎么变mp4保存_php输出视频流保存为
- 如何使用Golang反射将map转换为struct
- 如何在Golang中实现RPC异步返回_Golan
- Win11怎么设置开机密码_Windows11账户
- 如何在 Go 中正确反序列化 XML 多节点数组(
- Win11任务栏怎么固定应用 Win11将软件图标
- php本地部署后session无法保存_sessi
- 如何在Golang中使用内置函数_Golangle
- Win11怎么开启游戏模式_Windows11优化
- 如何在Golang中配置代码格式化工具_使用gof
- 如何使用Golang table-driven基准
- 如何使用Golang sort排序切片_Golan
- c++ namespace命名空间用法_c++避免
- Win11如何设置省电模式 Win11开启电池节电
- 如何使用Golang实现路由参数绑定_使用Mux和
- Win11怎样安装剪映专业版_Win11安装剪映教
- 用lighttpd能运行php吗_lighttpd
- Win11怎么关闭通知消息_屏蔽Windows 1
- Win11怎么关闭触控板_Win11笔记本禁用触摸
- Windows系统时间服务错误_W32Time服务
- Windows10如何更改盘符名称_Win10重命
- Win11怎么关闭资讯和兴趣_Windows11任
- Avalonia如何实现跨窗口通信 Avaloni
- c++的static关键字有什么用 静态变量和静态
- Python生成器表达式内存优化_惰性计算说明【指
- Win11怎么禁用键盘自带键盘_Win11笔记本禁
- Windows系统被恶意软件破坏后的恢复策略_错误
- php高频调试功能有哪些_php常用调试函数与工具
- Win11怎么关闭OneDrive同步_Win11


QQ客服