Django中如何将生成的WebP文件保存到模型FileField字段
问题
我不确定该问题是否完全与Django相关,目前使用代码生成WebP文件时遇到如下问题:
初始导入依赖:
from io import BytesIO from PIL import Image import requests
定义的数据模型如下:
class UserImage(models.Model): user_provided_image = VersatileImageField(upload_to=folder10, null=True, blank=True) nextgen_image = models.FileField(upload_to=folder10,null=True, blank=True) # 用于存储WebP图片
编写生成WebP的逻辑时,现有代码可以正常生成WebP文件,但文件会被保存到项目根目录,不清楚如何将生成的WebP文件关联保存到模型的nextgen_image(FileField类型)字段中,相关实现代码如下:
def create_webp_image(sender, instance, *args, **kwargs): image_url = instance.image.thumbnail['1920x1080'].url try: response = requests.get(image_url, stream=True) path = image_url except: # 本地环境 path = "http://localhost:8000" + image_url response = requests.get(path, stream=True) img = Image.open(BytesIO(response.content)) # 拼接文件路径 position = path.rfind("/") + 1 newpath = path[0:position] # 拼接文件名 image_name = path[position:] name_of_file = image_name.split('.')[0] + ".webp" # 该行代码会将webp文件生成到项目根目录 img.save(name_of_file,"webp") # 待实现:将图片保存到模型字段 #instance.nextgen_image = ? post_save.connect(create_webp_image, sender=UserImage)
解答
核心原因是直接调用PIL的save()传入本地文件名时,文件会直接写入进程当前工作目录(即项目根目录),完全绕过Django的文件存储体系,自然无法自动关联到模型的FileField字段。不需要先把文件落盘到本地,直接在内存中生成WebP二进制数据后调用Django FileField的save()方法即可,框架会自动按照upload_to配置把文件存到对应位置。
实现注意点
- 新增导入Django的
ContentFile类,用来包装内存中的二进制文件数据 - 原代码存在字段名笔误:模型定义的源图字段是
user_provided_image,信号里写的是instance.image,需要修正 - 原代码使用裸
except捕获所有异常的写法不推荐,改为直接判断URL是否带HTTP协议头拼接地址,逻辑更清晰,也不会误捕获无关异常 post_save信号里修改实例字段后,要避免递归触发信号导致死循环,增加新建实例判断、指定更新字段即可- 用
BytesIO在内存中暂存生成的WebP数据,不会在项目根目录产生冗余临时文件
修正后完整代码
首先补充导入依赖:
from io import BytesIO from PIL import Image import requests from django.core.files.base import ContentFile
修正模型缩进(原代码缩进不规范):
class UserImage(models.Model): user_provided_image = VersatileImageField(upload_to=folder10, null=True, blank=True) nextgen_image = models.FileField(upload_to=folder10, null=True, blank=True) # 存储WebP格式图片
重写信号处理逻辑:
def create_webp_image(sender, instance, created, *args, **kwargs): # 仅新建实例、且未生成过webp图时执行,避免递归触发save if not created or instance.nextgen_image: return image_url = instance.user_provided_image.thumbnail['1920x1080'].url # 拼接完整访问地址 if image_url.startswith(('http://', 'https://')): img_full_url = image_url else: img_full_url = f"http://localhost:8000{image_url}" resp = requests.get(img_full_url, stream=True) resp.raise_for_status() img = Image.open(BytesIO(resp.content)) # 生成webp格式文件名 origin_filename = image_url.rsplit("/", 1)[-1] webp_filename = f"{origin_filename.rsplit('.', 1)[0]}.webp" # 内存中生成webp数据,不写入本地磁盘 webp_io = BytesIO() img.save(webp_io, format="WEBP") webp_io.seek(0) # 保存到模型字段,Django自动按照upload_to规则存储文件 instance.nextgen_image.save(webp_filename, ContentFile(webp_io.read()), save=False) instance.save(update_fields=['nextgen_image']) post_save.connect(create_webp_image, sender=UserImage)
该写法兼容所有Django支持的存储后端,不管是本地文件存储还是S3、OSS等远程对象存储,都不需要额外修改逻辑。
内容的提问来源于stack exchange,提问作者Jason Howard
相关产品推荐
相关产品推荐

