级联的反向等价物是什么?
我正在写一个小型的音乐数据库。学习SQL的时间已经过去很久了,我一直想尝试一下Django。但是有一件事我一直搞不明白。
现在,我的模型只有两个类,Album
(专辑)和Song
(歌曲)。Song
类有一个外键,指向它所属的专辑。如果我删除那个Album
,所有“属于”它的Song
都会因为级联效应而被删除。
在我的数据库中,专辑有点像虚拟的,只有歌曲在文件系统中实际存在,专辑是根据歌曲的标签构建的。因此,只有当没有更多的歌曲指向某个专辑时,我才能知道这个专辑不再存在(因为它们在文件系统中已经不存在了)。
简单来说,我想知道如何实现反向级联,也就是说,如果没有更多的歌曲指向一个专辑,那么这个专辑也应该被删除?
4 个回答
我遇到过类似的问题,最后我在相册的代码里加了一个计数器。如果这个计数器的值是0,并且要执行删除操作(delete()),那么就会把这个相册对象删除掉。
另外一种解决办法是重写歌曲模型里的删除方法(delete()),或者在删除后再去删除相册。
这里有一个很微妙的实现细节,我觉得应该补充一下这个讨论。
假设我们有两个模型,其中一个通过外键引用另一个,像这样:
class A(models.Model):
x = models.IntegerField()
class B(models.Model):
a = models.ForeignKey(A, null=True, blank=True)
现在,如果我们删除了A的一个条目,B中的引用也会因为级联删除而被删除。
到这里为止,一切正常。现在我们想要反转这个行为。大家提到的明显方法是使用删除时发出的信号,所以我们可以这样做:
def delete_reverse(sender, **kwargs):
if kwargs['instance'].a:
kwargs['instance'].a.delete()
post_delete.connect(delete_reverse, sender=B)
这看起来很完美,实际上也确实有效!如果我们删除了B中的一个条目,相应的A也会被删除。
但问题是,这样会产生循环行为,导致出现异常:如果我们删除A中的一个项目,由于默认的级联删除行为(我们想保留这个行为),相应的B项目也会被删除,这又会导致调用delete_reverse,试图删除一个已经被删除的项目!
解决这个问题的关键是,你需要进行异常处理,以正确实现反向级联:
def delete_reverse(sender, **kwargs):
try:
if kwargs['instance'].a:
kwargs['instance'].a.delete()
except:
pass
这段代码无论如何都能工作。希望能对一些人有所帮助。
你可以使用 pre_delete
这个信号,当一首歌被删除且没有其他歌曲时,就可以把专辑也删除掉。
from yourapp.models import Album, Song
from django.db.models.signals import pre_delete
def delete_parent(sender, **kwargs):
# Here you check if there are remaining songs.
....
pre_delete.connect(delete_parent, sender=Song)