Chapter 16 · Caching, files, mail, signals and tasks

16.2 The signals Django sends

Every Signal Django defines, the code that sends it, its sender and its keyword arguments: the request signals, the model signals such as pre_save and m2m_changed, post_migrate, setting_changed, connection_created, and the login, autoreloader and task signals.

Django’s signals are module-level objects: the request signals and setting_changed in django/core/signals.py; the model signals, class_prepared, pre_migrate and post_migrate in django/db/models/signals.py; connection_created in django/db/backends/signals.py; template_rendered in django/test/signals.py; the login signals in django/contrib/auth/signals.py; the autoreloader’s in django/utils/autoreload.py; the tasks’ in django/tasks/signals.py. Django listens to several itself: it closes connections and caches on the request signals, django.contrib.postgres registers PostgreSQL’s types on connection_created (Opening and closing a connection), and makes the contrib applications’ rows on post_migrate.

Where each signal is sent

Table 16.2 The signals Django defines, and what a receiver is given besides "signal" and "sender" (Signal.send). The login signals belong to The user of a request: login, logout and AuthenticationMiddleware and Authenticating: authenticate and the backends, the autoreloader’s to The autoreloader, the tasks’ to Background tasks: Task, TaskResult and the task backends.
signal sent by sender keyword arguments
request_started sent by a handler (WSGIHandler.__call__ django/core/handlers/wsgi.py, ASGIHandler.handle django/core/handlers/asgi.py) sender the handler’s class keyword arguments "environ", or "scope" under ASGI
request_finished sent by closing the response (HttpResponseBase.close django/http/response.py); ASGIHandler.handle when there is no response sender the handler’s class; None under the test client keyword arguments none
got_request_exception sent by an exception becoming a 500 (response_for_exception django/core/handlers/exception.py, get_exception_response) sender None keyword arguments "request"
setting_changed sent by override_settings.enable django/test/utils.py, override_settings.disable; a TransactionTestCase with available_apps sender the settings object’s class keyword arguments "setting", "value", "enter"
class_prepared sent by ModelBase._prepare django/db/models/base.py sender the model class keyword arguments none
pre_init sent by Model.__init__ django/db/models/base.py sender the model class keyword arguments "args", "kwargs"
post_init sent by Model.__init__ sender the model class keyword arguments "instance"
pre_save sent by Model.save_base django/db/models/base.py sender the model class keyword arguments "instance", "raw", "using", "update_fields"
post_save sent by Model.save_base sender the model class keyword arguments "instance", "created", "update_fields", "raw", "using"
pre_delete sent by Collector.delete django/db/models/deletion.py sender the model class keyword arguments "instance", "using", "origin"
post_delete sent by Collector.delete sender the model class keyword arguments "instance", "using", "origin"
m2m_changed sent by a many-to-many manager (create_forward_many_to_many_manager django/db/models/fields/related_descriptors.py) sender the through model keyword arguments "action", "instance", "reverse", "model", "pk_set", "using", "raw"
pre_migrate sent by emit_pre_migrate_signal django/core/management/sql.py sender an AppConfig keyword arguments "app_config", "verbosity", "interactive", "using"; from migrate, "stdout", "apps", "plan"
post_migrate sent by emit_post_migrate_signal django/core/management/sql.py sender an AppConfig keyword arguments as pre_migrate
connection_created sent by BaseDatabaseWrapper.connect django/db/backends/base/base.py sender the wrapper’s class keyword arguments "connection"
template_rendered sent by instrumented_test_render django/test/utils.py sender the template keyword arguments "template", "context"
user_logged_in sent by login django/contrib/auth/__init__.py, alogin sender the user’s class keyword arguments "request", "user"
user_login_failed sent by authenticate django/contrib/auth/__init__.py, aauthenticate sender "django.contrib.auth" keyword arguments "credentials", secrets masked; "request"
user_logged_out sent by logout django/contrib/auth/__init__.py, alogout, before the session is flushed sender the user’s class, or the class of None keyword arguments "request", "user"
autoreload_started sent by BaseReloader.run django/utils/autoreload.py sender the reloader keyword arguments none
file_changed sent by BaseReloader.notify_file_changed django/utils/autoreload.py, which restarts unless a receiver returns true sender the reloader keyword arguments "file_path"
task_enqueued sent by ImmediateBackend._execute_task django/tasks/backends/immediate.py, DummyBackend._store_result django/tasks/backends/dummy.py sender the backend’s class keyword arguments "task_result"
task_started sent by ImmediateBackend._execute_task sender the backend’s class keyword arguments "task_result"
task_finished sent by ImmediateBackend._execute_task sender the backend’s class keyword arguments "task_result"

The request cycle and the test framework

The request signals and the receivers Django connects to them are in the table of The two entry points.

Every send of setting_changed is in the test framework. override_settings sends it for each setting as an override begins, with "enter" true, and again as it ends, by Signal.send_robust, with the restored value; a TransactionTestCase with available_apps sends it for "INSTALLED_APPS". Most of its receivers throw away what was built from the old value (Overriding settings). template_rendered is sent only once setup_test_environment has made instrumented_test_render the templates’ render method.

A model class and its instances

class_prepared is sent at the end of ModelBase._prepare, when the class is complete and not yet registered (ModelBase: a class statement becomes a model). It is sent for every model that is not abstract, migrations’ historical models included: rendering the harbour’s project state sent it once for each model.

Model.__init__ sends pre_init before it sets anything and post_init at its end, each time it runs, whether or not anything listens, and so for each row a query fetches, which Model.from_db makes by calling the class with the row’s values as "args" (An instance and its state). from_db sets ModelState.db and ModelState.adding only after the class returns, so to a receiver of post_init a fetched instance looks new and from no database. An unpickled instance, one read from a cache among them, is made without calling the class and sends neither.

Saving: pre_save and post_save

Model.save_base sends pre_save before it writes any table and post_save after the last, except for a through model Django made (Saving an instance). QuerySet.update, QuerySet.bulk_create and QuerySet.bulk_update send neither.

Django passes "raw" true only from DeserializedObject.save, which calls save_base itself, past the model’s save, so that a fixture’s rows are written as they stand; since 6.1 it passes "raw" to m2m_changed too (Serialization: serialize, deserialize and DeserializedObject). Django’s reference warns such a receiver not to query or change other rows, since the database may not be consistent yet.

"update_fields" is None unless the save was limited, and then a frozenset: of the names the caller gave, or, for an instance with deferred fields, of attnames Model.save picks itself. Given an empty list, save returns before either signal.

The sender is always the instance’s own class, a proxy’s or a child’s. Saving an Officer, a child of Sailor with a table of its own, sent both signals for Officer alone, since save_base writes a parent’s row without a signal; deleting it sent each delete signal for Officer and again for Sailor, whose row the collector holds as an instance of Sailor.

m2m_changed

A many-to-many manager sends m2m_changed in pairs around each change, with an "action" of "pre_add", "post_add", "pre_remove", "post_remove", "pre_clear" or "post_clear"; set sends those of the changes it makes (Many-to-many relations). "pk_set" is, for an addition, the keys that had no row yet, looked up by a query; for a removal, the keys the caller named, whether a row had them or not; for a clearing, None.

Deleting: pre_delete and post_delete

Collector.delete sends pre_delete for every instance it holds before any row goes, and post_delete after each model’s rows, inside its transaction; "origin" is the instance or queryset whose delete was called (Deleting: the Collector). Rows it deletes without holding them are not announced, and a receiver connected for a model, or for every sender, makes Collector.can_fast_delete refuse to delete that model’s rows unseen. Nor are rows of a through model Django made, or rows the database deletes for DB_CASCADE.

pre_migrate and post_migrate

Each is sent once for every application in the registry that has a models module. The migrate command sends both around the executor and adds "stdout", "plan" and "apps", a registry of historical models (The migrate command, showmigrations and sqlmigrate). post_migrate has two more senders that add none of them: the flush command, after emptying a database that had tables unless its caller inhibits it, and a TransactionTestCase with available_apps, for the applications it keeps. A receiver therefore needs a default for "apps", as create_permissions has.