Django回滚数据迁移时Historical model不允许删除报错咨询
Django 迁移函数中传入的 apps 实例返回的是当前迁移节点对应的历史模型版本,所有历史模型都是独立的 Python 类,哪怕字段、关联关系完全一致,和其他迁移节点的历史模型、运行时的真实模型也属于不同的类,ORM 做关联校验时会严格匹配类身份。
你在原始的 delete_as 函数中仅获取了 A 的历史模型,删除 A 时触发的级联删除会涉及关联的 B、C 模型,此时 ORM 自动解析关联关系用到的 B、C 模型类,和当前迁移上下文的历史模型类不匹配,因此抛出类型校验错误。
你修改后直接导入运行时的 apps 虽然能解决问题,但不符合规范的原因是:如果后续 B、C 模型的字段、关联关系发生变更,再执行这个迁移时,运行时的最新模型会和迁移节点对应的数据库 Schema 不匹配,极易出现更难排查的脏数据或者执行错误。
额外说明:你贴出的 B 模型代码中 b = models.ForeignKey(B, on_delete=models.CASCADE) 属于明显笔误,正常场景下应该是指向 A 的外键(例如 a = models.ForeignKey(A, on_delete=models.CASCADE)),该笔误不影响本次问题的根因判定。
只需要在 delete_as 函数中,通过迁移传入的 apps 实例预先获取级联链上所有涉及的模型即可,不需要导入运行时模型。同一迁移上下文中通过同一个 apps 实例获取的所有历史模型属于同一批次,ORM 做关联校验时会匹配通过:
def delete_as(apps, schema_editor): # 预先获取级联删除涉及的所有历史模型,确保是同一批次的类 A = apps.get_model('super_app', 'A') B = apps.get_model('super_app', 'B') C = apps.get_model('super_app', 'C') # 直接删除A即可,级联删除逻辑会正常执行 A.objects.all().delete()
如果担心级联删除的顺序问题,也可以按照子表到父表的顺序主动删除:
def delete_as(apps, schema_editor): A = apps.get_model('super_app', 'A') B = apps.get_model('super_app', 'B') C = apps.get_model('super_app', 'C') # 从最子表开始删,避免级联校验 C.objects.all().delete() B.objects.all().delete() A.objects.all().delete()
内容的提问来源于stack exchange,提问作者Ebram Shehata

