Django CASCADE和post_delete交互

问题描述 投票:1回答:1

我有以下模型:

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,然后将其调用两次,并在第二次尝试时返回失败。

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

所以问题是:在post_delete_b期间是否有办法知道对象a已被删除?instance.aA.objects.get(id=instance.a.id)都返回对象(我想Django会缓存数据库更新,直到完成所有删除操作为止)。

python django django-models django-signals
1个回答
1
投票

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

因此删除B模型实例时,相关的A实例将始终存在(如果实际存在)。因此,从B模型post_delete信号接收器中,您可以获取相关的A实例并检查DB中是否确实存在相关的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(实际上是第一次访问时返回dict的描述符)由ReverseOneToOneDescriptor(一对一相对侧的相关对象的访问者)使用来缓存相关字段名称值。 FWIW,通过ForwardOneToOneDescriptor访问器在关系的前面也进行了相同的操作。


根据评论进行编辑:

如果您将此功能用于多个发件人的post_delete,则可以通过getattr动态获取相关属性:

getattr(a, sender.a.field.related_query_name())

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

© www.soinside.com 2019 - 2024. All rights reserved.