1.2 How Django is extended
Django is extended at its extension points: places where it calls code that is not its own, named in a setting, handed to a registry or found by a convention. Most are a dotted path in a setting, which import_string turns into a class; Django makes an object of it and keeps it for a process, a request, a thread or an asynchronous context, or makes one at each use. Three matter most: the applications a project is made of, the middleware every request passes through, and the backends behind its database, cache, files and mail.
A setting holds a name, not the object. The settings module is imported when a setting is first read, and a module it imported may read the settings in turn: django, Django’s default settings, cannot import the translation module, which “depends on the settings”. A name puts the import off until the object is first wanted. What Django is shows a new project’s names.
The pattern
A dotted path is a module’s path with one more name after the last dot. import_string imports the module and returns that attribute of it (import_string; Importing by name: import_string and autodiscover_modules).
The default CACHES is one extension point whole: its alias "default" names LocMemCache, which is called with the alias’s "LOCATION" and the rest of its dictionary, both empty here. In the figure, look for when the path becomes a class, the base class nothing tests, and what keeps the instance.
Three of the things the figure shows differ from one point to the next. What is done with the import: most points call a class, the middleware calls a factory, a few name a function and WSGI_APPLICATION the application itself, and some import a module with importlib instead, as the database and session engines do.
What the object must be: most points have a base class that Django’s own implementations subclass, but a setting’s loading code tests it only for the installed applications, the static file finders and the default primary key; elsewhere an object need only have what Django uses of it.
And how long it lives, which is whatever holds it: an object made at import, the request, a cached_property or a functools cache on what builds it, a Local with a value for each thread or asynchronous context, or nothing, so that each use makes a new one.
The installed applications
Each entry of INSTALLED_APPS is the path of a package or of an AppConfig subclass. AppConfig.create imports a package with importlib, taking the configuration class from its submodule called apps where it finds one, and anything else with import_string; the class must subclass AppConfig. Apps.populate makes the configurations once a process, keeps them in the app registry, imports each application’s models module, and last calls each AppConfig.ready once, the place for an application’s startup code.
Django also looks inside each installed application by name: for its template tags, commands, migrations and translations with no setting, and for its templates and static files where the settings ask (MigrationLoader; Settings, apps and startup).
The middleware
Each entry of MIDDLEWARE is the dotted path of a factory: BaseHandler imports it and calls it once with the layer inside it, and what it returns is the middleware, called for each request. No base class is asked for, MiddlewareMixin being a convenience: the factory must return something other than None and may raise MiddlewareNotUsed to be left out. A server’s handler runs the factories when it is made, once a process (Building the middleware chain).
The backends
A backend is a pluggable implementation that a setting names, and six kinds share a shape. The setting maps each alias, "default" and any others, to a dictionary whose "ENGINE" (in DATABASES) or "BACKEND" (in CACHES, STORAGES, TASKS, MAILERS and TEMPLATES) names the backend, and an object made at import, such as caches, makes the backend for an alias when code asks for it. The base classes, BaseDatabaseWrapper, BaseCache, Storage, BaseTaskBackend, BaseEmailBackend and BaseEngine, are not tested when a backend is made.
They differ in how long a backend lives. caches, task_backends and connections are each a BaseConnectionHandler, which keeps backends in a Local, one for each thread or asynchronous context; ConnectionHandler’s is never shared between threads, for the reason Django’s neighbours gives. storages and engines keep one for each alias in a dictionary all threads share. TEMPLATES is a list, and a backend’s alias is its "NAME" or else the next-to-last name of its path, "django" for DjangoTemplates; engines makes the backend the first time its alias is asked for, as the system checks and get_template do for every engine, and keeps it for the process. mailers makes a new backend at each access, because mail backends had always been made for one sending, DEP 18 says.
A database’s "ENGINE" names a package, not a class: Django imports its base module and takes the class called DatabaseWrapper from it.
The other settings
Other settings name code, each loaded by import_string unless the loader says otherwise: “by module” is importlib.
django/conf/global_settings.py .| setting: what it names | loaded by | made |
|---|---|---|
AUTH_: dictionaries, a class in each "NAME" (Authentication, sessions and messages) |
loaded by get_ django/ |
made once a process |
AUTHENTICATION_: each a BaseBackend (Authentication, sessions and messages) |
loaded by load_backend django/ |
made once a use |
CSRF_: a view (Security) |
loaded by by get_callable: _get_ django/ |
made once a process |
DATABASE_ROUTERS: paths of router classes, or routers; no base class (Database backends) |
loaded by Connection django/ |
made once a process |
DEFAULT_: a subclass of AutoField, tested (Models and fields) |
loaded by Options django/ |
made once a model without its own primary key |
DEFAULT_: an Exception (Views) |
loaded by get_ django/ |
made once a use |
DEFAULT_: a Safe (Views) |
loaded by get_ django/ |
made once a process |
FILE_: each a File (Requests and responses) |
loaded by Http django/ |
made once a request |
FORM_RENDERER: a BaseRenderer (Forms) |
loaded by get_ django/ |
made once a process |
FORMAT_: packages of format modules by locale (Internationalization and time zones) |
loaded by by module: iter_ django/ |
made once a language |
LOGGING_CONFIG: a function, given LOGGING (Settings, apps and startup) |
loaded by configure_ django/ |
made once, by django.setup; called only if LOGGING is not empty |
MESSAGE_STORAGE: a BaseStorage (Authentication, sessions and messages) |
loaded by default_storage django/ |
made once a request |
MIGRATION_: an application’s migrations package (Migrations) |
loaded by by module: Migration django/ |
made once a loader |
PASSWORD_HASHERS: each a Base (Authentication, sessions and messages) |
loaded by get_hashers django/ |
made once a process |
ROOT_URLCONF, no default: the root URLconf (URL routing) |
loaded by by module: URLResolver django/ |
made once a process |
SERIALIZATION_, no default: a module for each format (Caching, files, mail, signals and tasks) |
loaded by by module: _load_ django/ |
made once a process |
SESSION_ENGINE: a module with a SessionBase store (Authentication, sessions and messages) |
loaded by by module: Session django/ |
made the module once a process; a store once a request |
SESSION_: a class like django (Authentication, sessions and messages) |
loaded by Session django/ |
made once a session store |
SIGNING_BACKEND: a Signer (Security) |
loaded by get_ django/ |
made once a use |
STATICFILES_: each a BaseFinder, tested (Content types, static files and the other contrib apps) |
loaded by get_finder django/ |
made once a process |
TEMPLATES’ "OPTIONS": loaders, each a django; context processors; tag modules (Templates) |
loaded by tag modules by module: Engine django/ |
made once an engine |
TEST_RUNNER: a DiscoverRunner (The test framework) |
loaded by by __import__: get_runner django/ |
made once a test run |
WSGI_APPLICATION: the application itself (Management commands) |
loaded by get_ django/ |
made once, by runserver |
Registries
Some extension points are not settings: an object handed to a registry, or a module Django finds by name.
| registry: what it holds | registered by, or found in | made | chapter |
|---|---|---|---|
template tag libraries: modules with a Library |
registered by, or found in each application’s templatetags package (get_ django/ |
made once an engine | chapter Templates |
lookups and transforms: Lookup and Transform classes, tested on use |
registered by, or found in Register django/ |
made held by the class, or the field, it is registered on; an instance for each filter | chapter QuerySets |
| system checks: functions that return messages | registered by, or found in Check django/ |
made held for the process; called at each run of the checks | chapter Management commands |
the admin: its site, a class Simple names; a ModelAdmin for each model |
registered by, or found in Default django/Admin, called from each application’s admin module |
made once a process | chapter The admin |
management commands: each a BaseCommand |
registered by, or found in each application’s management/commands directory (get_commands django/ |
made the list once a process; a command once a use | chapter Management commands |
Signals: Django calling out
A signal runs the other way. The project’s code connects a function, a receiver, to a Signal that Django made, and Django calls the receivers when it sends the signal, as a request starts and finishes or a model is saved. The signals Django sends lists them.