如何避免重新触发 django 信号?

How can I avoid re-triggering a django signal?

提问人:Grimidk 提问时间:11/15/2023 最后编辑:Grimidk 更新时间:11/15/2023 访问量:43

问:

我有这个信号,我希望它一旦已经检查了每个实例一次就不要运行,目前它陷入了无限递归循环,因为它每次运行时都会触发自己。

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()

任何帮助都是值得赞赏的,如果答案不如 n^2 复杂,则加分。

编辑:这是表单模型本身:

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
python django django-models django-signals

评论

0赞 willeM_ Van Onsem 11/15/2023
实际上,我建议不要将其存储在项目中,这不仅会使保存它变得更加容易,而且会更有效,更可靠。scoreform
0赞 willeM_ Van Onsem 11/15/2023
您能分享一下这些模型以及您的目标吗?
0赞 Grimidk 11/15/2023
@willeM_VanOnsem恐怕我确实需要保存在表单模型中的max_score,因为每当有人回答它时都需要它,而不是每次有人发送响应并查询表单中的每个问题时都计算它。这有意义吗?

答:

1赞 willeM_ Van Onsem 11/15/2023 #1

我强烈建议不要将乐谱存储在对象中。事实上,这不仅可以避免信号的问题:我们可以在数据库端更有效地做到这一点,并且只有在我们需要分数时才能做到这一点,并且还可以使其更加健壮。form

信号通常是一个坏主意。事实上,信号可以被规避,例如使用.bulk_create(... [Django-文档]这不会触发已创建对象的信号。在很多情况下,数据可能会发生变化:创建记录、更新记录、删除记录。事实证明,即使在同一个数据库上,保持相同的数据同步也并不容易。我在这篇文章中总结了一些信号问题 [django-antipatterns]。

因此,最好省略:score

class QuestionForm(models.Model):
    questions = models.ManyToManyField(
        Question,
        related_name='questions'
    )
    # …
    # no score field
    pass


class Question(models.Model):
    points = models.IntegerField()

现在,如果我们希望 s 具有相关 s 的相应分数,我们可以使用:FormQuestion

from django.db.models import Sum

QuestionForm.objects.annotate(score=Sum('questions__points'))

由此产生的 s 将具有一个额外的属性,该属性将包含相关 s 的总和。如果查询集被过滤掉,它也不会聚合我们不需要的查询集,而且由于聚合是由数据库完成的,这通常非常有效。FormQuerySet.score.pointsQuestion


注意related_name=... 参数 [Django-doc]反向关系的名称,因此在本例中从模型到模型。因此,将其命名为 与正向关系相同。因此,您可能需要考虑将问题重命名为 。QuestionQuestionFormforms

评论

0赞 Grimidk 11/15/2023
这听起来不错,但我将如何引用分数属性?“Form.score”不起作用。
0赞 willeM_ Van Onsem 11/15/2023
@Grimidk:如果使用带注释的查询集:.for form in queryset: print(form.score)
1赞 Grimidk 11/15/2023
在定义查询集并过滤以仅匹配我当前正在检查的查询集后,就像您所说的那样,这就成功了。