Django CASCADE 与 post_delete 交互

Django CASCADE and post_delete interaction

我有以下型号:

class A():
  foriegn_id1 = models.CharField  # ref to a database not managed by django
  foriegn_id2 = models.CharField

class B():
  a = models.OneToOneField(A, on_delete=models.CASCADE)

所以我想删除B的同时也删除A:

@receiver(post_delete, sender=B)
def post_delete_b(sender, instance, *args, **kwargs):
  if instance.a:
    instance.a.delete()

关于删除 A,我想从非托管数据库中删除对象:

@receiver(post_delete, sender=A)
def post_delete_b(sender, instance, *args, **kwargs):
  if instance.foriegn_id1:
    delete_foriegn_obj_1(instance.foriegn_id1)
  if instance.foriegn_id2:
    delete_foriegn_obj_2(instance.foriegn_id2)

现在,如果我删除对象 B,它就可以正常工作。但是如果我删除obj A,那么obj B被级联删除,然后它发出一个post_delete信号,再次触发删除A。 Django 知道如何管理它,所以它在到达 delete_foriegn_obj 之前工作正常,然后被调用两次并且 return 第二次尝试失败。

我考虑过验证对象是否存在于 delete_foriegn_obj 中,但它又增加了 3 次对数据库的调用。

所以问题是:在post_delete_b期间有没有办法知道对象a已经被删除了? instance.aA.objects.get(id=instance.a.id) return 对象(我猜 Django 会缓存数据库更新,直到它完成所有删除操作)。

问题是级联删除是在删除请求的对象之前执行的,因此当您查询数据库(A.objects.get(id=instance.a.id))时,相关的a 实例在那里。 instance.a 甚至可以显示缓存的结果,否则就无法显示。

因此,在删除 B 模型实例时,相关的 A 实例将始终存在(如果确实存在的话)。因此,从 B 模型 post_delete 信号接收器,您可以获得相关的 A 实例并检查相关的 B 是否确实存在于数据库中(无法避免DB在这里得到下面的实际图片):

@receiver(post_delete, sender=B)
def post_delete_b(sender, instance, *args, **kwargs):
    try:
        a = instance.a
    except AttributeError:
        return

    try:
        a._state.fields_cache = {}
    except AttributeError:
        pass

    try:
        a.b  # one extra query
    except AttributeError:
        # This is cascaded delete
        return

    a.delete()

我们还需要确保我们不会通过将 a._state.fields_cache 设为空来获取任何缓存结果。 fields_cache(实际上是第一次访问时 returns 和 dict 的描述符)由 ReverseOneToOneDescriptor(对 a 的另一侧相关对象的访问者)使用-to-one) 来缓存相关的字段名称-值。 FWIW,ForwardOneToOneDescriptor 访问器在关系的前端完成了相同的操作。


根据评论编辑:

如果您将此功能用于多个发件人的 post_delete,您可以通过 getattr:

动态获取相关属性
getattr(a, sender.a.field.related_query_name())

这与上面的 a.b 相同,但允许我们通过名称动态获取属性,因此这将导致与您想象的完全相似的查询。