You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)

关键修改说明

  1. 优先获取模型ID:新建对象时先调用一次super().save()生成主键,此时文件会被写入到指定目录,后续可正常访问。
  2. 直接读取上传文件对象:通过self.file.open()读取内存中的上传文件,彻底避免磁盘路径依赖问题。
  3. 自动生成文件URL:使用self.file.url获取Django自动生成的URL,避免硬编码域名和路径,适配不同部署场景。
  4. 安全创建目录:使用os.makedirs(..., exist_ok=True),无需手动判断目录是否存在,代码更简洁且避免竞争条件。
  5. 自动填充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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 07:36:00