Django:如何删除任何不再引用的外键对象



我在我的 Django Rest Framework 应用程序中嵌套了数据,如下所示:

class Student(models.Model):
studyGroup = models.ForeignKey(StudyGroup, on_delete=models.SET_NULL,  blank=True, null=True, related_name='student')

每个学生可能有一个学习小组;一个学生可能没有学习小组。

许多学生可以拥有相同的学习小组。

我想自动删除任何未被任何学生引用的学习组,因为学生已被删除或因为它已更新。

我认为这可以通过为学生自定义"保存"和"删除"方法,检查他们的学习组是否被任何其他学生引用,如果未引用则将其删除来完成。或者通过使用信号可能更优雅。但感觉应该有一种更简单的方法可以做到这一点 - 就像on_delete=models.CASCADE的倒数一样。

有没有办法告诉数据库自动执行此操作?还是需要编写自定义代码?

您可以使用以下查询删除不再被Student引用的StudyGroup对象:

StudyGroup.objects.filter(students__isnull=True).delete()

(这给定了你的ForeignKey[Django-doc] 的related_name=参数 [Django-doc] 设置为'students',因为这是反向关系的名称)。

根据数据库后端,您可以实现可以执行某些操作的触发器,例如,当您删除/更新Student记录时。但这是特定于后端的。

我们可以向Student模型添加一个触发器,以便在删除或保存 s 时删除StudyGroupStudent而无需Student

# app/signals.py
from app.models import Student
from django.db.models.signals importpost_delete, post_save
from django.dispatch import receiver
@receiver([post_delete, post_save], sender=Student)
def update_delete_student(sender, instance, **kwargs):
StudyGroup.objects.filter(students__isnull=True).delete()

您需要在应用程序配置中导入signals模块:

# app/app.py
from django.apps import AppConfig
class MyAppConfig(AppConfig):
# ...
def ready(self):
import app.signals

但是有一些方法可以通过ORM绕过Django信号。通过使用QuerySet.update[Django-doc]进行充分说明。

因此,定期运行该方法(例如每天/每小时)可能很有用。我们可以将celery用于[realpython]或django-periodically[GitHub]。

话虽如此,无论如何,删除StudyGroup本身可能并不是最必要的。例如,如果您想检索至少有一个学生的StudyGroupQuerySet,我们可以这样写:

# StudyGroups with at least one Student
StudyGroup.objects.filter(student__isnull=False).distinct()

因此,与其删除StudyGroups,不如决定不显示这些StudyGroups,就像软删除[维基词典]一样。然后,您仍然可以稍后恢复数据,这当然取决于用例。

注意ForeignKeyrelated_name是关系的反向名称,因此检索StudentsQuerySetStudyGroup的属性名称。因此,命名这个'studyGroup'有点">奇怪"。如果有两个或多个ForeignKey指向具有相同名称StudyGroup,也很容易导致冲突

最新更新