Django application import and missed class_prepared signals
Due to opaque application loading semantics within the Django i18n code, several model classes may be imported before a
class_prepared signal listener connects, resulting in missing the signal entirely. Specifically, connecting to this signal from an application which appears first in
INSTALLED_APPS, does not guarantee that it will execute when a class is first imported by any of the subsequent applications.
class_prepared signal is dispatched when the model class is instantiated for the first time; this happens inside class
Model’s metaclass at
Therefore, we would expect that connecting to this signal from the
__init__.py of an app which appears first in the
INSTALLED_APPS list, would ensure that our listener receives the signal for all Models loaded in the remainder apps (following is the beginning of a typical such list):
However, this is not (always) the case. Tracing, for example, shell execution, we find that
from django.db.models.loading import get_models loaded_models = get_models()
requests all models from the app cache which first calls
_populate to initialize itself at
which loads all apps into the cache the first time it’s called and does nothing for subsequent calls at
The apps are loaded in
_populate in the order they’re listed in
INSTALLED_APPS; some apps can be postponed though if they can’t be imported at the time of traversal at
A print statement in
_populate indicates that no apps are postponed and that all apps are, in fact, loaded in sequence. Furthermore the signal gets connected before any other apps are loaded. The output of
manage.py shell with several
Signal connected. class_prepared_listener_app django.contrib.auth django.contrib.contenttypes django.contrib.sessions django.contrib.sites django.contrib.messages django.contrib.admin ...
Model’s metaclass indicates that some classes are instantiated before their owner apps are loaded:
Preparing class 'django.contrib.contenttypes.models.ContentType' Preparing class 'django.contrib.auth.models.Permission' Preparing class 'django.contrib.auth.models.Group_permissions' Preparing class 'django.contrib.auth.models.Group' Preparing class 'django.contrib.auth.models.User_user_permissions' Preparing class 'django.contrib.auth.models.User_groups' Preparing class 'django.contrib.auth.models.User' Preparing class 'django.contrib.auth.models.Message' Preparing class 'django.contrib.sites.models.Site' Signal connected. Preparing class 'django.contrib.sessions.models.Session' Preparing class 'django.contrib.admin.models.LogEntry' ...
Further snooping shows that these classes are instantiated by translation-related preparation code, before the shell command is properly handled, at
Models are instantiated inside the last call to
activate() which eventually calls
From the above, it is straightforward to see that by removing the
auth apps, or by placing our app after the
auth apps, our signal connects before any
Model is loaded. Neither of these two “solutions” however appear robust or welcome.
It should be noted that, because the call to
translation.activate() is being made inside the execute method of the base class from which all management commands derive, this behaviour is to be expected when Django starts up through commands other than shell.
Admittedly, the official Django documentation for this signal states “Django uses this signal internally; it’s not generally used in third-party applications.” however, I strongly feel that Django would benefit from a formal and deterministic load order for