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.a
和 A.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
相同,但允许我们通过名称动态获取属性,因此这将导致与您想象的完全相似的查询。
我有以下型号:
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.a
和 A.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
相同,但允许我们通过名称动态获取属性,因此这将导致与您想象的完全相似的查询。