Reputation: 2837
I have nested data in my Django Rest Framework app, something like this:
class Student(models.Model):
studyGroup = models.ForeignKey(StudyGroup, on_delete=models.SET_NULL, blank=True, null=True, related_name='student')
Each student may have a study group; a student may have no study group.
Many students can have the same study group.
I would like to automatically delete any StudyGroup that is not referenced by any students, either because the student was deleted or because it was updated.
I think this can be done by customising the 'save' and 'delete' methods for Student, checking whether their StudyGroup is referenced by any other Student, and deleting it if is not referenced. Or perhaps more elegantly by using signals. But it feels like there should be a simpler way to do this - like an inverse of on_delete=models.CASCADE
.
Is there a way to tell the database to do this automatically? Or do I need to write the custom code?
Upvotes: 4
Views: 2628
Reputation: 476574
You can remove the StudyGroup
objects that are no longer referenced by a Student
with the following query:
StudyGroup.objects.filter(students__isnull=True).delete()
(this given the related_name=
parameter [Django-doc] of your ForeignKey
[Django-doc] is set to 'students'
, since this is the name of the relation in reverse).
Depending on the database backend, you could implement a trigger that can perform certain actions, for example when you remove/update an Student
record. But that is backend-specific.
We can add a trigger to the Student
model to remove the StudyGroup
s without a Student
when we delete or save Student
s:
# app/signals.py
from app.models import Student
from django.db.models.signals import post_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()
You will need to import the signals
module in your application config:
# app/app.py
from django.apps import AppConfig
class MyAppConfig(AppConfig):
# ...
def ready(self):
import app.signals
But there are ways to bypass the Django signals through the ORM. For examply by using QuerySet.update
[Django-doc].
Therefore it might be useful to run the method periodically, for example each day/hour. We can use celery
for that [realpython] or django-periodically
[GitHub].
That being said, it might not be per se the most necessary to remove the StudyGroup
s anyway. If you for example want to retrieve a QuerySet
of StudyGroup
s that have at least one student, we can write this like:
# StudyGroups with at least one Student
StudyGroup.objects.filter(student__isnull=False).distinct()
So instead of removing the StudyGroup
s, you might decide not to show these StudyGroup
s, like a soft delete [wiktionary]. Then you can still recover the data later on, this of course depends on the use case.
Note: the
related_name
of aForeignKey
is the name of the relation in reverse, so the name of the attribute of aStudyGroup
to retrieve theQuerySet
ofStudent
s. Therefore naming this'studyGroup'
is a bit "weird". It would also easily result in collisions if there are two or moreForeignKey
s that point toStudyGroup
with the same name.
Upvotes: 4