Code

Changes between Version 7 and Version 8 of Signals


Ignore:
Timestamp:
10/04/06 20:52:57 (8 years ago)
Author:
ubernostrum
Comment:

Rewrite in detail

Legend:

Unmodified
Added
Removed
Modified
  • Signals

    v7 v8  
    11== What Are Signals? == 
    22 
    3 Signals allow you to make Django call custom methods after common actions using a dispatcher. Beyond that, I really don't know much else. Please help expand this documentation, if you can. 
     3Django includes an internal "dispatcher" which provides two pieces of functionality: 
    44 
    5 The author of Zyons [http://feh.holsman.net/articles/2006/06/13/django-signals talks about signals a bit here]. 
     5 1. Pieces of code which want to advertise what they're doing can use the dispatcher to send "signals" which contain information about what's happening. 
     6 2. Pieces of code which want to do something whenever a certain event happens can use the dispatcher to "listen" for particular signals, then execute the functions they want when those signals are sent out. 
    67 
    7 == Built-In Signals == 
     8The actual mechanism comes from a third-party library called [http://pydispatcher.sourceforge.net/ PyDispatcher], which is bundled with Django and which lives at `django.dispatch.dispatcher`. 
    89 
    9 Django has the following build in signalers: 
     10== How signals work == 
    1011 
    11  * class_prepared 
    12  * pre_init 
    13  * post_init 
    14  * pre_save 
    15  * post_save 
    16  * pre_delete 
    17  * post_delete 
    18  * post_syncdb 
     12A signal is just a simple Python object, and requires nothing special to set up. For example, the `post_save` signal, found in `django.db.models.signals`, is defined like so: 
    1913 
    20 All the above are related to classes in your models.py files. 
     14{{{ post_save = object() }}} 
    2115 
    22 == Custom Signals == 
     16When a piece of code wants to send a signal, it needs to do three things: 
    2317 
    24 You can send custom signals from the dispatcher, as well. In the following example from [http://feh.holsman.net/articles/2006/06/13/django-signals this intro to signals], a signal called "object_viewed" is sent whenever the following bit is called: 
     18 1. Import the signal from wherever it's been defined. 
     19 2. Import the dispatcher. 
     20 3. Use the dispatcher's `send` function to send the signal. 
    2521 
    26 {{{ 
    27 dispatcher.send(signal=signals.object_viewed, request=request, object=object) 
    28 }}} 
     22An example of this can be found in the `save` method of the base model class, `django.db.models.Model`; the file in which that class is defined imports the signals defined in the `django.db.models` module: 
    2923 
    30 You'll have to instantiate the signal in a file called "signals.py" in your app's folder: 
     24{{{ from django.db.models import signals }}} 
    3125 
    32 {{{ 
    33 object_viewed=object() 
    34 }}} 
     26It also imports the dispatcher: 
    3527 
    36 And then, in any file in which you use that signal (typically, models.py and views.py), you'll need to import your signals file: 
     28{{{ from django.dispatch import dispatcher }}} 
    3729 
    38 {{{ 
    39 from project.app import signals 
    40 }}} 
     30And in the very last line of the `save` method, it sends the `post_save` signal: 
    4131 
    42 == Example == 
     32{{{ dispatcher.send(signal=signals.post_save, sender=self.__class__, instance=self) }}} 
    4333 
    44 The following is taken from [http://zyons.com/ Zyons], the only place I've seen signals used so far. Unless I'm completely incorrect, the following code has the dispatcher call "increment_tag_summary" before a !TagUserObject object is saved, and "decrement_tag_summary" after a !TagUserObject object is deleted. 
     34The first two arguments are fairly clear, and are common for all uses of the dispatcher: 
    4535 
    46 === models.py === 
     36 1. `signal` is the signal to be sent. 
     37 2. `sender` is the object which is sending the signal; in this case, `self.__class__`; this will be whatever model class the object which was just saved belongs to. 
    4738 
    48 {{{ 
    49 from django.db.models import signals 
    50 from django.dispatch import dispatcher 
     39The third argument, `instance`, is one of the more interesting features of the dispatcher: when sending a signal, you are free to include ''any'' extra arguments beyond these two; if a function which is listening for the signal is expecting certain information to be passed in as arguments, the dispatcher will ensure that the extra arguments to `send` are used. In this case the extra argument `instance` is `self`, which means it will be the object which was just saved. This means that functions which are listening for the `post_save` signal can, if they want to do something with the object that was just saved, specify that they take an argument named `instance`, and the dispatcher will pass it to them. 
    5140 
    52 [...] 
     41To listen for a signal, first define a function that you want to execute when the signal is sent; if you know that the signal will be sent with extra arguments and you want to use those arguments, make sure your function accepts them. Then you need to do three things: 
    5342 
    54 def increment_tag_summary(sender, instance, signal, *args, **kwargs): 
    55     [...] 
     43 1. Import the signal object you'll be listening for. 
     44 2. Import the dispatcher. 
     45 3. Use the dispatcher's `connect` signal to tell the dispatcher you want to listen for something. 
    5646 
    57 def decrement_tag_summary(sender, instance, signal, *args, **kwargs): 
    58     [...] 
     47A good example of this is found in Django's bundled "contenttypes" application, which creates and maintains a registry of all the installed models in your database. In order to do this, the contenttypes app defines a `ContentType` model, and it needs to know any time a new model is installed so it can create the appropriate `ContentType` object for that model. To do this, it includes a file called `mnagement.py`; whenever `manage.py syncdb` is run, it loops through ''every'' application in the `INSTALLED_APPS` setting, and looks to see if any apps contain a module called `management`; if they do, `manage.py` imports them before installing any models, which means that any dispatcher connections listed in an app's `management` module will be set up before model installation happens. 
    5948 
    60 dispatcher.connect( increment_tag_summary , signal=signals.pre_save, sender=TagUserObject ) 
    61 dispatcher.connect( decrement_tag_summary , signal=signals.post_delete, sender=TagUserObject ) 
    62 }}} 
     49In its `management.py` file, the contenttypes app defines a function called `create_contenttypes`, which takes three arguments: `app`, `created_models` and `verbosity`. These correspond to the extra arguments `manage.py` will use with `dispatcher.send` when it sends the `post_syncdb` signal after installing each new application, and provide enough information to determine which models need to have new `ContentType` objects created (actually, just `app` and `created_models` would be enough; the extra argument, `verbosity`, is used by `manage.py` to indicate whether any listening functions should be "verbose" and echo output to the console, or be quiet and not echo any output. 
    6350 
    64 === Other examples === 
     51The `management.py` file also imports `django.db.models.signals` and `django.dispatch.dispatcher`; after the `create_contenttypes` function is defined, it sets up that function to listen for the `post_syncdb` signal: 
    6552 
     53{{{ dispatcher.connect(create_contenttypes, signal=signals.post_syncdb) }}} 
     54 
     55The first argument, `create_contenttypes`, is the name of the function to execute when the signal is sent out. The second argument, `signal`, is the signal to listen for. There is another optional argument, `sender`, which is not used in this example; `sender` can be used to narrow down exactly what will be listened for; when you specify `sender`, your function will only be executed when the object which sent the signal is the same as the object you specify as the `sender` argument. 
     56 
     57An example of this can be found in Django's authentication application: `django.contrib.auth.management` defines a function called `create_superuser`, and uses the dispatcher to connect to the `post_syncdb` signal -- but ''only'' when `post_syncdb` is being sent as a result of installing the auth application. To do this, the auth app's `management.py` file imports its own models: 
     58 
     59{{{ from django.contrib.auth import models as auth_app }}} 
     60 
     61And then uses the `sender` argument to `dispatcher.connect`: 
     62 
     63{{{ dispatcher.connect(create_superuser, sender=auth_app, signal=signals.post_syncdb) }}} 
     64 
     65Here's a breakdown of exactly why that works: 
     66 
     67 1. Whenever `manage.py syncdb` finishes installing the models for a particular application, it sends the `post_syncdb` signal. You'll remember that `dispatcher.send` takes an optional argument, `sender`, which is the object that's "sending" the signal. In this case, `manage.py syncdb` sets `sender` to be the `models` module of the app it just installed. 
     68 2. `django.contrib.auth.management` import the auth app's models as `auth_app`, which means that, within that file, the variable `auth_app` ''is'' the module `django.contrib.auth.models`. 
     69 3. So when `manage.py syncdb` send the `post_syncdb` signal with `django.contrib.auth.models` as the `sender` argument, the dispatcher notices that this is the same as the `sender` specified in the `dispatcher.connect` call in `django.contrib.auth.management`, and so the `create_superuser` function is executed. 
     70 
     71In case you've ever wondered, that's how Django knows to prompt you to create a superuser whenever you install the auth app for the first time. The auth app also sets up another function -- `create_permissions` -- which doesn't specify `sender` in its call to `dispatcher.connect`, so it runs any time `post_syncdb` is sent. That's how the auth app creates the add, change and delete `Permission` objects for each application you install. 
     72 
     73== List of signals built in to Django == 
     74 
     75Django defines several sets of signals which are used internally, and which you can listen for in order to run your own custom code at specific moments. 
     76 
     77`django.db.models.signals` defines the following signals: 
     78 
     79'''class_prepared''' 
     80 
     81This is sent whenever a model class has been "prepared"; in other words, once most of the metaprogramming which makes models work has been completed. Django uses this signal internally to know when to generate and add the automatic `AddManipulator` and `ChangeManipulator` to a model class (see the DevModelCreation page for details). 
     82 
     83Arguments that are sent with this signal: 
     84 
     85 * `sender` -- the model class which was just prepared. 
     86 
     87'''pre_init''' 
     88 
     89Whenever you create a new instance of a Django model (for example, in [http://www.djangoproject.com/documentation/tutorial1/ the first part of the Django tutorial] when you do `p = Poll(question="What's up?", pub_date=datetime.now())`) , this signal is sent at the beginning of the execution of the model's `__init__` method. 
     90 
     91Arguments sent with this signal: 
     92 
     93 * `sender` -- the model class you're creating an instance of. 
     94 * `args` -- a list of positional arguments passed to the model's `__init__` method. 
     95 * `kwargs` -- a dictionary of keyword arguments passed to the model's `__init__` method. For example, in the tutorial when you do `p = Poll(question="What's up?", pub_date=datetime.now())`, the `kwargs` argument to the `pre_init` signal would be the dictionary `{'question': "What's up?", 'pub_date': datetime.now()}`. 
     96 
     97'''post_init''' 
     98 
     99Like `pre_init`, but this one is sent when the model's `__init__` method is done executing. 
     100 
     101Arguments sent with this signal: 
     102 
     103 * `sender` -- the model class you've just created an instance of. 
     104 * `instance` -- the instance of the model you just created. For example, in the tutorial when you do `p = Poll(question="What's up?", pub_date=datetime.now())`, the `instance` argument to the `post_init` signal would be the `Poll` object you just created. 
     105 
     106''pre_save''' 
     107 
     108This is sent at the beginning of a model's `save` method. 
     109 
     110Arguments sent with this signal: 
     111 
     112 * `sender` -- the model class of the object being saved. 
     113 * `instance` -- the actual object being saved. 
     114 
     115'''post_save''' 
     116 
     117This is sent at the end of a model's `save` method. 
     118 
     119Arguments sent with this signal: 
     120 
     121 * `sender` -- the model class of the object which was just saved. 
     122 * `instance` -- the actual object which was just saved. 
     123 
     124'''pre_delete''' 
     125 
     126This is sent at the beginning of a model's `delete` method. 
     127 
     128Arguments sent with this signal: 
     129 
     130 * `sender` -- the model class of the object which is about to be deleted. 
     131 * `instance` -- the actual object which is about to be deleted. 
     132 
     133'''post_delete''' 
     134 
     135This is sent at the end of a model's `delete` method. 
     136 
     137Arguments sent with this signal: 
     138 
     139 * `sender` -- the model class of the object which was just deleted. 
     140 * `instance` -- the actual object which was just deleted (the object will no longer be in the database, but will stick around in memory for a little while after that). 
     141 
     142'''post_syncdb'' 
     143 
     144Sent by `manage.py syncdb` after it installs an application. 
     145 
     146Arguments sent with this signal: 
     147 
     148 * `sender` -- the `models` module of the application which was just installed. 
     149 * `app` -- same as `sender`. 
     150 * `created_models` -- a list of the model classes which were just installed. 
     151 * `verbosity` -- indicates how much information `manage.py` is printing on screen. There are three possible values: 0 means no information, 1 means some information and 2 means all possible information. Functions which listen for this signal should adjust what they output to the screen based on the value of this argument. 
     152 * `interactive` -- whether `manage.py` is running in "interactive" mode; this is a boolean and so is either `True` or `False`. If `interactive` is `True`, it's safe to prompt the user to input things on the command line (for example, the auth app only prompts to create a superuser when `interactive` is `True`); if `interactive` is `False`, functions which listen for this signal should not try to prompt for anything. 
     153 
     154`django.core.signals` defines the following signals: 
     155 
     156'''request_started''' 
     157 
     158This signal is sent whenever Django begins processing an incoming HTTP request. 
     159 
     160This signal doesn't provide any arguments. 
     161 
     162'''request_finished''' 
     163 
     164This signal is sent whenever Django finishes processing an incoming HTTP request. 
     165 
     166This signal doesn't provide any arguments. 
     167 
     168'''got_request_exception''' 
     169 
     170This signal is sent whenever Django encounters an exception while processing an incoming HTTP request. 
     171 
     172This signal doesn't provide any arguments. 
     173 
     174`django.test.signals` defines the following signals: 
     175 
     176'''template_rendered''' 
     177 
     178This signal is sent by Django's testing framework whenever the test system renders a template; it's used by the test system to verify that the template rendered as expected. 
     179 
     180Arguments sent with this signal: 
     181 
     182 * `sender` -- the `Template` object which was rendered. 
     183 * `template` -- same as `sender`. 
     184 * `context` -- the `Context` with which the template was rendered. 
     185 
     186== Other documentation == 
     187 
     188 * [http://feh.holsman.net/articles/2006/06/13/django-signals Django signals] -- blog entry by Ian Holsman which discusses signals in general. 
     189 * [http://www.bright-green.com/blog/2006_07_12/initialising_application_data_.html Initializing application data in Django] -- blog entry by Alan Green which discusses use of the `post_syncdb` signal to provide initial application data. 
     190 * [http://www.b-list.org/weblog/2006/09/10/django-tips-laying-out-application Django tips: laying out an application] -- blog entry by James Bennett which mentions the use of the `post_syncdb` signal to execute custom functions when an application is installed. 
     191 
     192== Applications not bundled with Django which use signals == 
     193 
     194 * [http://zyons.com/ Zyons] 
    66195 * http://trac.studioquattro.biz/djangoutils/browser/trunk/nesh/thumbnail/field.py 
    67  * http://www.bright-green.com/blog/2006_07_12/initialising_application_data_.html