Django模型save方法处理图片遇FileNotFoundError问题解决问询
问题解决:Django模型save()中处理上传图片时的FileNotFoundError
问题根源
在调用父类super().save()之前,Django的FileField不会将上传的文件写入到upload_to指定的磁盘目录中,此时直接通过硬编码路径media/upload/{self.filename}打开文件必然会报错。
解决方案
修改模型的save()方法,直接从上传的文件对象读取内容,而非依赖磁盘路径;若需使用模型ID命名缩略图,可先临时保存一次生成ID,或改用UUID作为唯一标识。以下是修改后的完整代码:
修改后的Media模型
import os from uuid import uuid4 from PIL import Image from django.db import models 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): # 新建对象时先保存一次获取主键ID(用于缩略图命名) if not self.pk: super().save(*args, **kwargs) kwargs.pop('force_insert', None) # 直接从上传的文件对象读取图片,无需依赖磁盘路径 with self.file.open('rb') as f: image = Image.open(f) # 获取MIME类型与格式 mime_type = image.get_format_mimetype() format = mime_type.split("/")[1] # 确保缓存目录存在,exist_ok=True避免重复创建报错 cache_dir = "media/cache" os.makedirs(cache_dir, exist_ok=True) thumbnail = {} sizes = [(150, 150), (256, 256)] for i, size in enumerate(sizes): resized_image = image.resize(size) index = "small" if i == 0 else "medium" # 用模型ID+文件名生成唯一缩略图路径 file_path = os.path.join( cache_dir, f"{self.pk}-resized-{self.filename}-{index}.{format}", ) resized_image.save(file_path, format=format) thumbnail[f"{size[0]}*{size[1]}"] = file_path # 更新模型字段 self.mime_type = mime_type self.size = self.file.size / 1024 # 转换为KB(可选) self.thumbnail = thumbnail self.url = self.file.url # 使用Django自动生成的文件URL self.thumbhash = image_to_thumbhash(image) # 保存最终修改 super().save(*args, **kwargs)
修改后的Media序列化器
from rest_framework import serializers from .models import Media class MediaSerializer(serializers.ModelSerializer): class Meta: model = Media fields = [ "id", "title", "file", "filename", ] def create(self, validated_data): # 若前端未传入filename,自动从上传文件中提取 if 'filename' not in validated_data and 'file' in validated_data: validated_data['filename'] = validated_data['file'].name return Media.objects.create(**validated_data)
关键修改说明
- 优先获取模型ID:新建对象时先调用一次
super().save()生成主键,此时文件会被写入到指定目录,后续可正常访问。 - 直接读取上传文件对象:通过
self.file.open()读取内存中的上传文件,彻底避免磁盘路径依赖问题。 - 自动生成文件URL:使用
self.file.url获取Django自动生成的URL,避免硬编码域名和路径,适配不同部署场景。 - 安全创建目录:使用
os.makedirs(..., exist_ok=True),无需手动判断目录是否存在,代码更简洁且避免竞争条件。 - 自动填充filename:序列化器中自动从上传文件提取文件名,减少前端传参负担。
替代方案(无需两次save())
若不想分两次保存模型,可改用UUID作为缩略图的唯一标识,示例:
unique_id = uuid4().hex file_path = os.path.join( cache_dir, f"{unique_id}-resized-{self.filename}-{index}.{format}", )
内容的提问来源于stack exchange,提问作者Fyo-saadati
相关产品推荐
相关产品推荐

