Django 上传帖子图片触发缩放信号,文件存在却报FileNotFoundError如何解决
FileNotFoundError 报错排查与修复
核心报错直接原因
代码读取原图片时传入了错误的路径:
- 调用
Image.open(img_dir)时的img_dir来自instance.media.url,这个属性返回的是面向前端页面的Web相对路径(例如/media/posts/123.jpg),并非服务器磁盘上的真实文件路径。PIL库操作本地文件时必须传入磁盘绝对路径,直接传入Web路径会找不到对应文件,触发FileNotFoundError。
现存代码全量问题列表
就算修正路径读取逻辑,原代码还存在以下会导致运行失败的问题:
- 扩展名提取逻辑错误:原代码通过
img_dir_list[-1].split(".")[0]提取后缀,实际取到的是去掉后缀的纯文件名,真正的文件扩展名是split后的最后一段内容 - 未定义变量引用:resize逻辑中使用的
image.width、image.height、img都没有提前定义,实际打开的图片对象名为im,运行时会抛出NameError - 文件保存路径逻辑错误:生成缩略图时用
settings.MEDIA_URL拼接存储路径,MEDIA_URL是前端访问的路由前缀(例如默认值为/media/),拼接后得到的是系统根目录下的虚拟路径,和实际存储文件的MEDIA_ROOT磁盘路径完全不匹配,无法正常写入文件 - 无意义的数据库写入:信号处理最后调用的
instance.save()没有修改任何模型字段,会额外触发一次post_save信号流程,产生不必要的数据库IO - 模型定义语法错误:
Post.media字段的upload_to="posts"后面多写了一个闭合右括号,会导致模型加载时报语法错误
完整修复方案
- 替换文件读取路径:使用Django文件字段自带的
instance.media.path属性,该属性会自动根据配置的MEDIA_ROOT拼接出原图片在服务器磁盘上的绝对路径,可直接被PIL读取 - 修正路径拼接逻辑:生成缩略图时,基于原图片的磁盘路径推导存储目录,用
MEDIA_ROOT维度的磁盘路径拼接保存地址,不要使用面向Web的MEDIA_URL - 修正变量引用逻辑:统一使用已加载的原图片对象的宽高计算缩放比例,正确提取原文件的扩展名
- 删除多余的
instance.save()调用:缩略图生成流程不涉及模型字段修改,不需要重新保存数据库记录 - 修正模型定义的语法错误,删掉多余的右括号
修复后的signals.py参考实现
import os from django.conf import settings from django.db.models.signals import post_save from django.dispatch import receiver from PIL import Image from .models import Post @receiver(post_save, sender=Post) def generate_post_thumbnails(sender, instance, created, **kwargs): # 仅在新创建帖子时执行,避免更新操作重复生成缩略图 if not created: return # 获取原图片在服务器磁盘的绝对路径 original_img_disk_path = instance.media.path # 拆分原文件的基础名和真实扩展名 original_base_name, file_ext = os.path.splitext(original_img_disk_path) # 缩略图和原图存放在同一目录下 thumbnail_storage_dir = os.path.dirname(original_img_disk_path) original_img = Image.open(original_img_disk_path) # 依次生成200/400/600宽度的三个尺寸版本 for target_width in [200, 400, 600]: target_height = int(target_width / original_img.width * original_img.height) resized_img = original_img.resize((target_width, target_height)) # 拼接缩略图的完整磁盘保存路径 thumbnail_disk_path = f"{thumbnail_storage_dir}/{os.path.basename(original_base_name)}_{target_width}x{target_height}{file_ext}" resized_img.save(thumbnail_disk_path)
修正后的Post模型定义
class Post(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) # 删除字段定义后多余的右括号 media = models.ImageField(null=False, blank=False, upload_to="posts") caption = models.CharField(max_length=100, default="", null=True, blank=True) date = models.DateTimeField(default=timezone.now, null=True, blank=True)
内容的提问来源于stack exchange,提问作者user14041846
相关产品推荐
相关产品推荐

