I have this signal and I want it to not run once it has already checked each instance once, currently it falls into an infinite recursion loop since it triggers itself each time it runs.
from django.db.models.signals import (
post_save,
)
from django.dispatch import receiver
from app.models import (
QuestionForm,
Question,
)
@receiver(post_save, sender=form)
def get_max_score(
sender: form,
instance: form,
**kwargs: dict,
) -> None:
forms = form.objects.all()
for form in forms.iterator():
total = 0
questions = form.questions.all()
for item in questions.iterator():
total += item.points
form.set_score(total)
form.save()
Any help is appreciated, bonus points if the answer is less complex than n^2.
Edit: this is the form model itself:
class QuestionForm(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=100)
questions = models.ManyToManyField(
Question,
related_name='questions'
)
created_at = models.DateTimeField(
auto_now_add=True,
editable=False,
)
updated_at = models.DateTimeField(
auto_now=True,
editable=False,
)
max_score = models.IntegerField(
default=0,
)
def __str__(self):
return self.name
def get_score(self):
return self.max_score
def set_score(self, score):
self.max_score = score
I would strongly advise not to store the score in the
formobjects. Indeed, not only will this avoid the problem with the signals: we can do this more efficient at the database side, and only when we need the score, and also make it more robust.Signals are often a bad idea. Indeed, signals can be circumventing, for example with a
.bulk_create(…)[Django-doc] that will not trigger signals for created objects. There are also a lot of scenarios where the data can change: creating records, updating records, removing records. It turns out that keeping the same data in sync, even on the same database, is not easy. I summarized some problems with signals in this article [django-antipatterns].Therefore it might be better to just omit the
score:Now if we want the
Forms with the corresponding score for the relatedQuestions, we can use:The
Forms that arise from thisQuerySetwill have an extra attribute.scorethat will contain the sum of the.pointsof the relatedQuestions. If the queryset is filtered down, it will also not aggregate the ones we don't need, and since the aggregate is done by the database, that is usually quite efficient.