Django模型save()读取上传文件报已关闭文件I/O操作错误
问题原因
当你在模型的save方法中调用self.save_file_content_to_attachment(self.file)时,此时Django尚未将上传的文件持久化到指定存储位置,且上传文件的流在请求处理阶段可能已被关闭,导致执行file.open('r')时触发"I/O operation on closed file"错误。
另外你的模型定义存在几个潜在问题:
UUIDField的默认值使用str(uuid4())会导致所有新创建实例共享同一个UUID(该表达式在模型加载时仅执行一次)date和time字段的默认值使用datetime.datetime.now().date()/.time(),同样会导致所有新实例使用模型加载时的时间,而非创建实例的实时时间
解决方案
方案1:调整保存顺序,先持久化文件再读取内容
修改save方法,先调用父类save确保文件存入存储,再读取内容更新attachment字段,同时避免无限递归:
def save(self, *args, **kwargs): update_attachment = False if self.file and not self.pk: # 新建对象时先保存文件到存储 super(CsvFile, self).save(*args, **kwargs) update_attachment = True if update_attachment: # 读取已存储的文件内容 with self.file.open('r') as f: self.attachment = f.read() # 二次保存更新attachment字段 super(CsvFile, self).save(*args, **kwargs) else: # 编辑对象或无文件上传时直接保存 super(CsvFile, self).save(*args, **kwargs) self.original_filename = self.file.name if self.file else ''
方案2:直接读取上传文件的临时内容(更高效)
上传的文件在请求周期内可直接读取,无需调用open(),修改save_file_content_to_attachment方法:
def save_file_content_to_attachment(self, file): try: # 直接读取上传文件内容,根据实际编码调整decode参数 self.attachment = file.read().decode('utf-8') except (FileNotFoundError, ValueError, UnicodeDecodeError): self.attachment = '' def save(self, *args, **kwargs): self.original_filename = self.file.name if self.file else '' if self.file: # 判断是否为上传的临时文件(新建场景) if hasattr(self.file, 'temporary_file_path'): self.save_file_content_to_attachment(self.file) else: # 编辑场景下读取已存储的文件 with self.file.open('r') as f: self.attachment = f.read() super(CsvFile, self).save(*args, **kwargs)
额外优化建议
修正模型字段的默认值问题:
class CsvFile(models.Model): processed = models.BooleanField(default=False) # 使用uuid4函数作为默认值,而非执行后的结果 uid = models.UUIDField(unique=True, default=uuid4) # 使用lambda延迟执行,确保每次创建实例时获取当前时间 date = models.DateField(null=False, default=lambda: datetime.datetime.now().date()) time = models.TimeField(null=False, default=lambda: datetime.datetime.now().time()) original_filename = models.CharField(max_length=600, blank=True) attachment = models.TextField(blank=True) file = models.FileField(upload_to="csv/", blank=True) # 其余方法保持修改后的版本
内容的提问来源于stack exchange,提问作者toni
相关产品推荐
相关产品推荐

