Skip to content

Latest commit

 

History

History
186 lines (124 loc) · 8.65 KB

第二十八章:信号使用案例和规避技术.md

File metadata and controls

186 lines (124 loc) · 8.65 KB

28 | Signals:Use Cases and Avoidance Techniques 第二十八章:信号使用案例和规避技术


The Short Answer:
简而言之

Use signals as a last resort.

The Long Answer: 长篇大论

Often when new Djangonauts first discover signals,they get signal-happy. They start sprinkling signals everywhere they can and feeling like real experts at Django.

通常,Django新手在第一次发现信号——他们觉得很开心。他们开始把信号尽可能地用到所有地方,而且他们觉得自己的行为像个Django方面的专家。

After coding this way for a while, projects start to turn 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 bene t 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.

信号虽然很好用,但是它们应该

28.1 When to Use and Avoid Signals

Do not use signals when:

  • The signal relates to one particular model 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.

It might be okay to use signals 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() or init() of a third-party app’s model. You can’t modify the third-party code and extending it might be impossible, so a signal provides a trigger for a callback.

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 ow through inversion of control. ey 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 its receivers will be.”

28.2 Signal Avoidance Techniques

Let’s go over some scenarios where you can simplify your code and remove some of the signals that you don’t need.

28.2.1 Using Custom Model Manager Methods Instead of Signals

Let’s imagine that our site handles user-submitted ice cream-themed events, and each ice cream event goes through an approval process. ese 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 things quickly.

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.

An easier way to handle this use case is to create a custom model manager method and use that in your views. is way, if an event is created by an administrator, they don’t have to go through the review process.

Since a code example is worth a thousand words, 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_admins()
        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:

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 a create event() method.

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 Competition",
...     start=now,
...     end=now,
...     user=user
...     )

28.2.2 Validate Your Model Elsewhere

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.

28.2.3 Override Your Model's Save or Delete Method Instead

If you’re using pre_save and post_save signals to trigger logic that only applies to one particular model, you might not need those signals. You can often simply move the signal logic into your model’s save() method.

The same applies to overriding delete() instead of using pre_delete and post_delete signals.

28.2.4 Use a Helper Function Instead of Signals

We find this approach useful under two conditions:

  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 the 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: Sometimes 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 ad- mit it’s not much fun to have to parse through a 500 or 2000 line chunk of code.

This solution, suggested to us by Django core developer Aymeric Augustin, is to place the code in helper functions. If done right, this helps us write cleaner, more reusable code.

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, matching the assertions of the test written in the second step.

4. Run the tests.

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 tests again.

9. Rinse and repeat until done.

This approach allows us to carefully remove the signal without breaking things. It also helps us iden- tify when an existing signal is required for a specific process.

28.3 Summary

Signals are a powerful tool in any Django developer’s toolbox. However, they are easy to misuse and it’s good practice to delve into why and when to use them.