上传时更新Django FileField文件内容引发I/O错误
Django上传文件加密时出现"I/O operation on closed file"的解决方案
问题原因
你遇到的错误是因为Django的UploadedFile(上传时的临时文件对象)不支持写入模式重新打开:
- 第一次以
rb模式读取后,底层临时文件会被关闭; UploadedFile的设计仅用于读取上传的原始数据,没有提供写入接口,强行以wb模式打开会触发已关闭文件的I/O错误。
解决方案(符合存储无关、无临时文件夹需求)
我们不需要修改原上传文件,而是直接将加密后的内容包装成Django兼容的ContentFile对象,替换app_file字段的值。这样既不用临时文件夹,也和存储后端(本地/S3等)无关。
修改后的代码:
from django.core.files.base import ContentFile from cryptography.fernet import Fernet class AppFile(models.Model): app_file = models.FileField(upload_to=upload_to, validators=[validate_file_size]) encrypted_data_key = models.CharField(max_length=500, blank=True) def encrypt_file_content(self, data_key): cipher = Fernet(data_key) # 读取原始上传文件内容 with self.app_file.open(mode='rb') as file: file_data = file.read() # 加密内容 encrypted_data = cipher.encrypt(file_data) # 用加密后的内容创建新文件,替换原字段值 # 保留原文件名,若需标识加密文件可修改文件名(如加.encrypted后缀) self.app_file.save( self.app_file.name, ContentFile(encrypted_data), save=False # 不立即保存到数据库,等待统一提交 ) def save(self, *args, **kwargs): if self._state.adding is True: # 从密钥管理服务生成密钥 encrypted_data_key, data_key = self.generate_data_key_from_vault() self.encrypted_data_key = encrypted_data_key # 加密文件内容并替换字段 self.encrypt_file_content(data_key) # 调用父类方法完成最终保存 super().save(*args, **kwargs)
关键说明
- ContentFile的作用:将加密后的字节数据包装成Django能识别的文件对象,无需创建物理临时文件。
- save=False参数:避免单独保存文件字段,确保和模型实例的保存操作原子性。
- 存储无关性:不管你用本地存储还是云存储(如AWS S3),Django的FileField会自动处理加密文件的存储逻辑。
内容的提问来源于stack exchange,提问作者Jeroen Jacobs
相关产品推荐
相关产品推荐

