Django生成文件存入FileField时触发递归深度超限该如何解决?
问题根因
- 你猜测的递归原因完全正确:Django的
FileField.save()方法默认会调用所属模型的save()方法保存字段变更,你在模型save逻辑中调用contract.save,又触发了模型的save,形成无限递归直到栈溢出。 - 代码存在两处明显错误:
super(Refund, self)参数错误,你的模型类是Contract而非Refund;upload_to参数值contrats/大概率是拼写错误,正确应为contracts/。
可行实现方案
方案1:给FileField.save添加save=False参数(最直接)
FileField的save方法支持第三个参数save,设置为False就不会触发模型的save,从根源避免递归,调整后的代码如下:
import os from django.conf import settings from django.db import models class Contract(models.Model): name = models.CharField(max_length=150, null=True) contract = models.FileField(upload_to='contracts/', blank=True, null=True) def save(self, *args, **kwargs): # 仅当合同文件不存在时生成,避免每次更新实例都重复生成覆盖 if not self.contract: filename = self.generate_contract() # 用with上下文管理器自动关闭文件,避免资源泄漏 with open(filename, "rb") as f: # 第三个参数save=False,不触发模型save,彻底避免递归 self.contract.save(os.path.basename(filename), f, save=False) # 修正super调用参数,Python3可直接简写为super() return super().save(*args, **kwargs) @staticmethod def generate_contract(): # 用os.path.join拼接路径,避免不同操作系统斜杠不兼容问题 filename = os.path.join(settings.MEDIA_ROOT, 'test.txt') with open(filename, "w") as f: f.write("content") return filename
如果后续生成合同需要用到当前实例的名称等属性,可以把generate_contract从静态方法改成实例方法,直接调用self.name等字段即可。
方案2:用post_save信号处理文件生成
也可以把文件生成逻辑放到post_save信号中,判断是首次创建实例时再生成文件,完全避开递归问题,同时实现逻辑解耦:
import os from django.conf import settings from django.db import models from django.db.models.signals import post_save from django.dispatch import receiver class Contract(models.Model): name = models.CharField(max_length=150, null=True) contract = models.FileField(upload_to='contracts/', blank=True, null=True) @staticmethod def generate_contract(): filename = os.path.join(settings.MEDIA_ROOT, 'test.txt') with open(filename, "w") as f: f.write("content") return filename @receiver(post_save, sender=Contract) def generate_contract_file(sender, instance, created, **kwargs): # 仅首次创建实例且无合同文件时生成 if created and not instance.contract: filename = instance.generate_contract() with open(filename, "rb") as f: instance.contract.save(os.path.basename(filename), f, save=True)
内容的提问来源于stack exchange,提问作者Tlön Uqbar Orbis Tertius
相关产品推荐
相关产品推荐

