Signals: Use Cases and Avoidance Techniques

信号:使用案例和避免使用信号的技术

The Short Answer: Use signals as a last resort. 简短的回答: 把使用信号当作最后的手段。

The Long Answer: Often when many Djangonauts first discover signals, they get signal-happy.They start sparkling signals everywhere they can and feeling like real experts at Django. 详细的回答: 当 Django 冒险家们第一次发现信号,他们就会得 “信号快乐症”。他们开始在任何可以使用信号的地方使用它并且觉得自己是 Django 真正的专家。 After coding this way for a while, projects start to run into confusing, knotted hairballs that can't be untangled. Signals are being dispatched everywhere and hopefully getting received somewhere, but at that point it's hard to tell what exactly is going on. 在使用这种方式编码一段时间后,项目开始变得混乱,就像打结的毛球一样不能解开。信号散布在代码的各处,并期望能被代码的某个地方接收,但是在这一点上看很难说出究竟在发生什么。

Many developers also confuse signals with asynchronous message queues such as what Celery (http://www.celeryproject.org/) provides. Make no mistake, signals are synchronous and blocking, and calling performance-heavy processes via signals provides absolutely no benefit from a performance or scaling perspective. In fact, moving such processes unnecessarily to signals is considered code obfuscation. Signals can be useful, but they should be used as a last resort, only when there's no good way to avoid using them. 许多开发人员还将信号和异步消息队列(如 Celery (http://www.celeryproject.org/) 提供的功能)混淆。不要犯糊涂了,信号是同步而且阻塞的, 无论是从性能还是扩展角度来看,通过信号来调用对性能影响较大的处理过程都没有任何好处。实际上,非必要地使用信号来调用这种处理过程会被认为是代码混淆。信号可能是有用的,当实在没有好的办法来避免使用信号的时候, 它们可以作为最后的手段。

28.1 When to Use and Avoid Signals

28.1 什么时候使用信号,什么时候避免使用信号

Do not use signals when: 当出现以下情况时不要使用信号:

  • The signal relates to one particular and can be moved into one of that model's methods, possibly called by save().
  • The signal can be replaced with a custom model manager method.
  • The signal relates to a particular view and can be moved into that view.
  • 这个信号处理涉及到一个特定的模型而且还能被移到模型的一个方法(可能由 save() 调用)中去时。
  • 这个信号处理可以用自定义模型管理器方法替换。
  • 这个信号处理涉及到一个特定的视图而且可以被移到该视图函数中去。

It might be okey to use signal when: 当出现以下情况时使用信号可能是合适的:

  • Your signal receiver needs to make changes to more than one model.
  • You want to dispatch the same signal from multiple apps and have them handled the same way by a common receiver.
  • You want to invalidate a cache after a model save.
  • You have an unusual scenario that needs a callback, and there's no other way to handle it besides using a signal. For example, you want to trigger something based on the save() and init() of a third-party app's model. You can't modify the third-party code and extending it might be impossible, so signal provides a trigger for a callback.
  • 你的信号接收器需要更改多个模型。
  • 你想为多个 app 分配同一个信号,并且使用一个通用的接收器使用同样的方法来处理这些信号。
  • 你希望在保存模型之后清理相应的缓存。
  • 你有一个不寻常的情况,在这个情况下你需要回调,但是除了使用信号没有其它的方式来处理。比如说,你想要基于第三方 app 模型的 save()init() 来触发某个事件。你并不能修改或扩展第三方代码,所以信号机制可以提供一个回调的触发器。
TIP: Aymeric Augustin Thoughts on Signals
Django core developer Aymeric Augustin says "I advise not to use signals as soon as a regular function call will do. Signals obfuscate control flow through inversion of control. They make it difficult to discover what code will actually run
Use a signal only if the piece of code sending it has positively no way to determine what it's receiver will be."

TIP: Aymeric Augustin 对信号的看法
Django 的核心开发者 Aymeric Augustin 说 “我建议不要像一个普通函数所做的那样使用信号。信号会逆转控制从而使控制流变得混乱。这让我们很难去发现代码的实际运行情况。
只有当发送它的代码片段无法确定接收者是什么时,才使用信号。”

28.2 Signal Avoidance Techniques

28.2 避免使用信号的技术

Let's go over some scenarios where you can simplify your code and remove some of the signal you don't need. 让我们讨论你可以简化你的代码并删除不需要的信号的情况。

28.2.1 Using Custom Model Manager Methods Instead of Signals

28.2.1 使用普通的模型管理器方法而不是信号

Let's imagine that our site handles user-submitted ice cream-themed events, and each ice cream event goes through an approval process. These events are set with a status of "Unreviewed" upon creation. The problem is that we want our site administrators to get an email for each event submission so they know to review and post thing quickly. 让我们想象一下,我们的站点会处理用户提交的冰淇凌主题活动,每个冰淇凌活动都要痛多一个审批过程。这些活动在创建时设置为 "Unreviewed" 状态。这其中的问题是,我们希望每个活动提交时我们的站点管理员都会收到一封邮件,以便他们可以快速审核和发布内容。

We could have done this with a signal, but unless we put in extra logic in the post.save() code, even administrator created events would generate emails. 我们可以使用信号机制来完成这个,但除非我们在 post.save() 中加入额外的逻辑,否则站点管理员创建的活动也会发送邮件。

An easier way to handle this use case is to create a custom model manager method and use that in your view. This way, if an event os created by an administrator, they don't have to go through the review process. 处理这个情况的一种更简单的方法是创建自定义模型管理器方法,并在视图中使用这个方法。这样的话,如果是管理员创建的事件,就不用经过审批过程了。

Sine a code example is worth a thousand, here is how we would create such a method: 正如一份代码示例胜千言,这里是我们将如何创建这样一个方法:

EXAMPLE 28.1

# events/managers.py
from django.db import models

class EventManager(models.Manager):

    def create_event(self, title, start, end, creator):
        event = self.model(title=title,
                           start=start,
                           end=end,
                           creator=creator)
        event.save()
        event.notify_admin()
        return event

Now that we have our custom manager with its custom manager method in place, let's attach it to our model(which comes with a notify_admins() method: 现在我们有自定义管理器和它的自定义管理器方法,让我们将它附加到我们的模型中。(它带有一个notify_admins() 方法:

EXAMPLE 28.2
# events/models.py
from django.conf import settings
from django.core.mail import mail_admins
from django.db import models

from model_utils.models import TimeStampedModel

from .managers import EventManager

class Event(TimeStampedModel):
    STATUS_UNREVIEWED, STATUS_REVIEWED = (0, 1)
    STATUS_CHOICES = (
        (STATUS_UNREVIEWED, "Unreviewed"),
        (STATUS_REVIEWED, "Reviewed"),
    )

    title = models.CharField(max_length=100)
    start = models.DateTimeField()
    end = models.DateTimeField()
    status = models.IntegerField(choices=STATUS_CHOICES,
                    default=STATUS_UNREVIEWED)
creator = models.ForeignKey(settings.AUTH_USER_MODEL)

objects = EventManager()

def notify_admins(self):
    # create the subject and message
    subject = "{user} submitted a new event!".format(
    user=self.creator.get_full_name())
    message = """TITLE: {title}
    START: {start}
    END: {end}""".format(title=self.title, start=self.start,
    end=self.end)

    # Send to the admins!
    mail_admins(subject=subject,
                message=message,
                fail_silently=False)

Using this follows a similar pattern to using the User model. To generate an event, instead of calling create(), we call create_event() method. 使用这个遵循于类似于使用 User 模型的方式,要生成一个活动,我们要调用 create_event() 方法,而不是调用 create()

EXAMPLE 28.3

>>> from django.contrib.auth import get_user_model
>>> from django.utils import timezone
>>> from events.models import Event
>>> user = get_user_model().get(username='audreyr')
>>> now = timezone.now()
>>> event = Event.objects.create_event(
...    title="International Ice Cream Tasting Comptition",
...    start=now,
...    end=now,
...    user=user
...    )

28.2.2 Validate Your Model Elsewhere

28.2.2 在其它地方验证你的模型

If you're using a pre_save signal to trigger input cleanup for a specific model, try writing a custom validator for your field(s) instead. If validating through a ModelForm, try overriding your model's clean() method instead. 如果你想要使用 pre_save 信号来触发一个特殊的模型的输入清除,请尝试为你的字段编写自定义验证器而不是用信号。 如果是通过 ModelForm 验证,请尝试重写模型的 clean() 方法。

28.2.3 Override Your Model's Save or Delete Method Instead

28.2.3 改成重写你模型的保存和删除方法

If you're using pre_save and post_save signals to trigger logic that only applied to one particular model, you might not need those signals. You can often simply move the signal logic into your model's save() method. 如果你只是使用 pre_save 和 post_save 信号来触发一个特定模型的处理逻辑,你或许不需要这些信号。你可以简单地将信号逻辑移动到模型的 save() 方法中。

The same applies to overriding delete() instead of using pre_delete signals. 这同样适用于重写 delete(),而不是使用 pre_delete 信号。

28.2.4 Use a Helper Function Instead of Signals

28.2.4 使用辅助函数而不是信号

We find this approach useful under two condition: 我们发现在两种情况下这种方法是有效的:

  1. Refactoring: Once we realize that certain bits of code no longer need to be obfuscated as signals and want to refactor, the question of 'Where do we put code that was in a signal?' arises. If it doesn't belong in a model manager, custom validator, or overloaded model method, where does it belong?
  2. Architecture: Some developers use signals because we feel the model has become too heavyweight and we need a place for code. While Fat Models are a nice approach, we admit it's not much fun to have to parse through a 500 or 2000 line chunk of code.

  3. 重构:一旦我们意识到代码中的某些地方不应该被信号混淆并且想要重构它,那么问题就来了,“我们该把在信号中的代码放到哪?”。如果代码不属于模型管理器,自定义验证器或者重载的模型方法,那它又该属于哪里呢?

  4. 架构:一些开发者使用信号是因为觉得模型太"重"了,所以需要一个其它放置代码的地方。虽然“胖”模型是一个不错的方式,我们承认要分析 500 到 2000 行代码并不是一件有多大乐趣的事。

This solution, suggest by Django core developer Aymeric Augustin, is to place the code in helper function. If done right, this helps us write cleaner, more reusable code. Django 核心开发者 Aymeric Augustin 对于这个提出解决方案是将代码放在帮助函数中。如果做得正确,这有助于我们编写更干净和可重用的代码。

One interesting thing about this approach is to test the transition out of signals. Simply follow these steps: 这种方法的一件有趣的事情就是测试信号的过渡是否正确。只需按照下列步骤操作:

  1. Write a test for the existing signal call.
  2. Write a test for the business logic of the existing signal call as if it were in a separate function.
  3. Write a helper function that duplicates the business logic of the signal, match the assertions of the test written in the second step.
  4. Run the test.
  5. Call the helper function from the signal.
  6. Run the tests again.
  7. Remove the signal and call the helper function from the appropriate location.
  8. Run the test again.
  9. Rinse and repeat until done.
  1. 编写现有信号调用的测试。
  2. 为现有信号调用的业务逻辑编写一个测试,就像它在一个单独的函数中一样。
  3. 编写一个辅助函数,复制信号的业务逻辑,匹配第二步中所写测试的断言。
  4. 运行测试。
  5. 从信号中调用帮助函数。
  6. 再次运行测试。
  7. 删除信号,并从适当的位置调用辅助函数。
  8. 再次运行测试。
  9. 重新启动并重复直到测试通过。

This approach allows us to carefully remove the signal without breaking things. It also help us identify when an existing signal is required for a specific process. 这种方法允许我们可以小心地删除信号而不破坏之前的事物。它还帮助我们识别什么时候一个特定的处理需要已经存在的信号。

28.3 Summary

28.3 总结

Signal are powerful tool in Django developer's toolbox. However, they are easy to misuse and it's good practice to delve into why and when to use them. 信号是 Django 开发人员工具箱中的强大工具。但是,它们容易被滥用,深入了解为什么和何时使用它们是一个好的实践。

results matching ""

    No results matching ""