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

Django自引用Folder模型删除含子项文件夹时触发__init__递归错误

递归错误触发原因
  • 核心问题出在重写的模型__init__方法执行时机不对:Django模型实例的__init__方法会在所有实例化场景被调用,包括级联删除时的待删除对象构造、关联对象预取、表单数据绑定等场景,此时实例内部的字段缓存、关联关系状态还未完全初始化完成。
  • 你在__init__中直接调用filepath()方法,方法内通过if not self.parent_folder判断父节点是否存在,这行代码会触发Django ORM的正向关联查询逻辑:首先检查实例的关联对象缓存,缓存未命中时就会去数据库查询父Folder实例,再构造一个新的父Folder模型实例。
  • 新构造的父Folder实例同样会执行__init__方法,再次调用filepath()触发自身的父节点查询,而此时Django的关联对象缓存还未填充,哪怕是同一个父节点也会被反复重新实例化,无法走到根节点parent_folder为None的终止条件,最终触发无限递归。
  • 级联删除场景会批量加载所有层级的子文件夹实例,会放大这个问题,直接触发递归深度超限的报错。
修复方案

方案1:最小改动修复(不调整原有字段结构)

  1. 不要在__init__中写业务逻辑,改用Django提供的from_db类方法记录初始路径,该方法仅在实例从数据库完整加载完成后执行,此时实例字段状态完全就绪,不会触发内部状态异常。
  2. 优化filepath()方法的判断逻辑,优先判断外键ID字段parent_folder_id,避免无意义的关联对象提前加载,只有确实存在父节点ID时才访问父对象拼接路径。

修复后的模型代码:

class Folder(models.Model):
    folder_name = models.CharField(max_length=200)
    parent_folder = models.ForeignKey(
        'self', 
        on_delete=models.CASCADE, 
        blank=True, 
        null=True,
        related_name='child_folders'
    )

    @classmethod
    def from_db(cls, db, field_names, values):
        instance = super().from_db(db, field_names, values)
        # 实例从数据库加载完成后再记录初始路径
        instance.initial_filepath = instance.filepath()
        return instance

    def filepath(self):
        # 先判断外键ID,不触发关联查询
        if not self.parent_folder_id:
            return f'media/scripts/{self.folder_name}'
        return f'{self.parent_folder.filepath()}/{self.folder_name}'

方案2:性能更优的彻底修复(推荐)

动态递归计算文件路径的方式本身存在性能问题,层级越深查询次数越多,还容易出现递归异常。建议直接将filepath作为实际数据库字段存储,重写save方法在文件夹保存时自动计算更新路径,完全避免运行时递归查询:

class Folder(models.Model):
    folder_name = models.CharField(max_length=200)
    parent_folder = models.ForeignKey(
        'self', 
        on_delete=models.CASCADE, 
        blank=True, 
        null=True,
        related_name='child_folders'
    )
    # 直接存储路径,避免运行时递归计算
    filepath = models.CharField(max_length=500, editable=False)

    def save(self, *args, **kwargs):
        if not self.parent_folder_id:
            self.filepath = f'media/scripts/{self.folder_name}'
        else:
            self.filepath = f'{self.parent_folder.filepath}/{self.folder_name}'
        super().save(*args, **kwargs)

这个方案下不需要重写__init__或者from_db,删除、查询时都不会触发额外的递归查询,从根源上避免递归错误,查询路径时也不需要反复查数据库,性能提升明显。


内容的提问来源于stack exchange,提问作者Balizok

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 08:09:21