Migration operations (code only — run at migrate time)

Django ORM PostgreSQL May 22, 2026 python

These run during migrate, not in queries, so there's no result row — only the Django code and the SQL it emits.

python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# ======================================================================
# These run during `migrate`, not in queries, so there's no result row — only
# the Django code and the SQL it emits. They cover zero-downtime schema changes:
# concurrent index builds, validating constraints in two steps, managing
# collations, and enabling extensions.
# ======================================================================

# ----------------------------------------------------------------------
# AddIndexConcurrently / RemoveIndexConcurrently
# ----------------------------------------------------------------------

# Django:
from django.contrib.postgres.operations import AddIndexConcurrently
from django.contrib.postgres.indexes import GinIndex

class Migration(migrations.Migration):
    atomic = False
    operations = [
        AddIndexConcurrently(
            "book", GinIndex(fields=["tags"], name="book_tags_gin2")),
    ]

# SQL:
#   CREATE INDEX CONCURRENTLY "book_tags_gin2" ON "examples_book" USING gin ("tags");

# Result:
#   Builds (or drops) an index WITHOUT locking the table for writes.
#   Must run in a non-atomic migration (`atomic = False`).

# ----------------------------------------------------------------------
# AddConstraintNotValid + ValidateConstraint
# ----------------------------------------------------------------------

# Django:
from django.contrib.postgres.operations import (
    AddConstraintNotValid, ValidateConstraint)
from django.db.models import CheckConstraint, Q

operations = [
    AddConstraintNotValid(
        "book", CheckConstraint(condition=Q(price__gte=0), name="price_ok")),
    ValidateConstraint("book", "price_ok"),
]

# SQL:
#   ALTER TABLE "examples_book" ADD CONSTRAINT "price_ok" CHECK ("price" >= 0) NOT VALID;
#
#   ALTER TABLE "examples_book" VALIDATE CONSTRAINT "price_ok";

# Result:
#   Add a CHECK constraint without scanning existing rows (instant),
#   then validate it separately — avoids a long write lock on big tables.

# ----------------------------------------------------------------------
# CreateCollation / RemoveCollation
# ----------------------------------------------------------------------

# Django:
from django.contrib.postgres.operations import CreateCollation

operations = [
    CreateCollation("case_insensitive", provider="icu",
                    locale="und-u-ks-level2", deterministic=False),
]

# SQL:
#   CREATE COLLATION "case_insensitive" (provider = icu, locale = 'und-u-ks-level2', deterministic = false);

# Result:
#   Manage database collations from migrations.
#   This project uses CreateCollation('case_insensitive', …) in migration 0001.

# ----------------------------------------------------------------------
# Extension operations (BloomExtension, CreateExtension, …)
# ----------------------------------------------------------------------

# Django:
from django.contrib.postgres.operations import (
    BloomExtension, CreateExtension)

operations = [BloomExtension(), CreateExtension("cube")]

# SQL:
#   CREATE EXTENSION IF NOT EXISTS "bloom";
#
#   CREATE EXTENSION IF NOT EXISTS "cube";

# Result:
#   Enable a contrib module / extension from a migration.
#   Already used here: HStore, Trigram, BtreeGist, Unaccent, Crypto.
#   BloomExtension enables space-efficient bloom indexes.