skip to content

In Django, what does adding 'django.contrib.postgres' to INSTALLED_APPS do, and which of its features also need a PostgreSQL extension created in a migration?

level: middleimportance: should knowfreq 30%

answer

  1. an app config's ready() hook
  2. lookups registered on text fields
  3. type handlers on each connection
  4. hstore, pg_trgm, unaccent, btree_gist
  5. an operation before first use

basics

~20 s

The app registers the search, trigram and unaccent lookups on CharField and TextField and hstore handling on each connection. HStoreField, trigram lookups, unaccent and GiST equality in ExclusionConstraint also need extensions, added with HStoreExtension, TrigramExtension, UnaccentExtension or BtreeGistExtension.

solid answer

~40 s

`PostgresConfig.ready()` registers the `search`, `trigram_similar`, `trigram_word_similar`, `trigram_strict_word_similar` and `unaccent` lookups on `CharField` and `TextField`, and connects a `connection_created` handler that registers the `hstore` and `citext` types when the database has them; since Django 6.0 a system check (`postgres.E005`) also flags contrib.postgres fields, indexes and constraints used without the app. Some features additionally need a PostgreSQL **extension**: `HStoreField` needs `hstore`, the trigram lookups and functions need `pg_trgm`, `unaccent` needs `unaccent`, and an `ExclusionConstraint` or `GistIndex` using equality on plain columns needs `btree_gist`. Add them with `HStoreExtension()`, `TrigramExtension()`, `UnaccentExtension()` or `BtreeGistExtension()` in a migration before the first operation that uses them. `ArrayField`, full-text search and range fields need no extension.

code

python · 22 lines
python
# settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.postgres',
    'recipes',
]


# recipes/migrations/0002_extensions.py
from django.contrib.postgres.operations import (
    BtreeGistExtension,
    HStoreExtension,
    TrigramExtension,
)
from django.db import migrations


class Migration(migrations.Migration):
    dependencies = [('recipes', '0001_initial')]
    operations = [HStoreExtension(), TrigramExtension(), BtreeGistExtension()]

go deeper

for a junior

Remember that contrib.postgres must be in INSTALLED_APPS and that hstore and trigram features need an extension created by a migration.

for a middle

Explain what ready() registers, which features need which extension, and why the extension operation must precede the first use of the type.

for a senior

Plan for roles without superuser rights, test databases and reversible migrations that drop an extension other apps may still use.

for a principal

Decide how far to lean on PostgreSQL-only features, knowing each extension becomes a provisioning step for every environment.

## Two separate requirements `django.contrib.postgres` features can depend on two unrelated things, and interviews probe whether you keep them apart: - **The Django app** in `INSTALLED_APPS`, which wires Python-side behaviour into the ORM when Django starts. - **A PostgreSQL extension** inside the database, which provides the SQL types, operators or functions the feature compiles to. Forgetting the first gives a Python error; forgetting the second gives a database error. ## What the app does when it is installed `PostgresConfig.ready()` runs at startup and: 1. Registers the `unaccent`, `search`, `trigram_similar`, `trigram_word_similar` and `trigram_strict_word_similar` lookups on `CharField` and `TextField`. Without the app, `Recipe.objects.filter(title__search='curry')` fails with an unsupported-lookup `FieldError`. 2. Connects `register_type_handlers` to the `connection_created` signal, so each new connection registers the `hstore` and `citext` types when they exist in the database. Without it, saving an `HStoreField` fails because the driver cannot adapt a `dict`. 3. Registers introspection mappings (for `inspectdb`) and a migration serializer for range values. Since **Django 6.0**, contrib.postgres model fields, indexes and constraints also run a system check, `postgres.E005`, which reports *'django.contrib.postgres' must be in INSTALLED_APPS in order to use ...*. ## Which features need an extension | Feature | Needs the app | PostgreSQL extension | Migration operation | |---|---|---|---| | `ArrayField`, range fields | yes (system check) | none | none | | `SearchVector`, `SearchQuery`, `SearchRank`, `search` lookup | yes | none; full-text search is built in | none | | `HStoreField` | yes | `hstore` | `HStoreExtension()` | | `trigram_similar`, `TrigramSimilarity` and friends | yes | `pg_trgm` | `TrigramExtension()` | | `unaccent` lookup | yes | `unaccent` | `UnaccentExtension()` | | `ExclusionConstraint` or `GistIndex` with equality on plain columns | yes | `btree_gist` | `BtreeGistExtension()` | | `GinIndex` on plain scalar columns | yes | `btree_gin` | `BtreeGinExtension()` | There are also `CITextExtension()`, `CryptoExtension()` and `BloomExtension()`, and the generic `CreateExtension(name)` for anything else. ## How the extension operations behave ```python from django.contrib.postgres.operations import HStoreExtension, TrigramExtension from django.db import migrations class Migration(migrations.Migration): dependencies = [('recipes', '0001_initial')] operations = [HStoreExtension(), TrigramExtension()] ``` - The operation checks `pg_extension` and runs `CREATE EXTENSION IF NOT EXISTS` only when the extension is missing, so re-running it is harmless. - It must come **before** the first `CreateModel` or `AddField` that uses the type, usually in its own early migration that later migrations depend on. - After creating the extension it re-registers type handlers on the migration's connection, so a later data migration in the same run can use `HStoreField` values. - On a non-PostgreSQL connection it does nothing, and reversing the migration **drops** the extension. - Since Django 6.0 each extension operation accepts `hints`, passed to database routers' `allow_migrate()`. ## Privileges Creating most extensions requires a PostgreSQL role with superuser privileges. Application roles in production often lack them, so the migration fails with a permission error. The usual fix is to have an administrator run `CREATE EXTENSION IF NOT EXISTS hstore;` once per database, test databases included, and keep the migration operation for environments where the role can create it; because the operation skips existing extensions, both approaches coexist. ## Reading the failure messages Most setup mistakes announce themselves, and each message points at one of the two requirements: | Symptom | Missing piece | |---|---| | `postgres.E005` from `manage.py check` | the app in `INSTALLED_APPS` (Django 6.0+) | | `FieldError` for an unsupported `search`, `trigram_similar` or `unaccent` lookup | the app in `INSTALLED_APPS` | | the driver cannot adapt a `dict` when saving an `HStoreField` | the app, which registers the hstore type handler | | PostgreSQL says type `hstore` does not exist | the `hstore` extension | | PostgreSQL says operator class `gin_trgm_ops` does not exist | the `pg_trgm` extension | | permission denied while creating an extension | a role with the privilege to create it | ## Where the extension migration belongs Put each extension operation in the app that first needs it, as an early migration that the model migrations depend on. When several apps need the same extension, a small shared app whose first migration creates all of them keeps the dependency explicit. Because the operation skips an extension that already exists, the same operation appearing in two apps is harmless on the way forward; on the way back, though, reversing either one drops the extension for both. ## HStoreField in brief `HStoreField` stores a `dict` of string keys to string-or-`None` values. It adds lookups such as `has_key`, `has_keys`, `has_any_keys`, `keys`, `values`, `contains` and `contained_by`, plus key transforms like `attributes__cuisine='thai'`. `KeysValidator` can require or restrict keys. It is the only field in this list that needs both the app and an extension to store a single value.

  • The production database role cannot create extensions; how do you ship a migration that uses HStoreField?
    Have a privileged role run `CREATE EXTENSION IF NOT EXISTS hstore;` on the database before deploying. Keep `HStoreExtension()` in the migration: it checks `pg_extension` first and skips existing extensions, so it passes where the extension already exists and still creates it in environments where the role is allowed to.
  • Why does filter(title__search='curry') raise FieldError on a project that uses PostgreSQL?
    The `search` lookup is not built into `CharField`; `django.contrib.postgres` registers it in its `ready()` hook. With the app missing from `INSTALLED_APPS`, Django reports an unsupported lookup. The database side is fine: full-text search needs no extension.

saying these in an interview costs you the question

  • Every contrib.postgres feature needs its own PostgreSQL extension.
  • Adding 'django.contrib.postgres' to INSTALLED_APPS installs the extensions automatically.
  • Full-text search needs TrigramExtension to be created first.
  • HStoreExtension can run after the AddField that creates the hstore column.
  • Extension operations always succeed, whatever the database role's privileges.