Django PG Migration Tools
django-pg-migration-tools is a Python library that provides a set of specialized Django migration operations designed to make database schema changes safer and more scalable on PostgreSQL. It includes tools for idempotent model creation, concurrent index and constraint management, function migration, and concurrent column alterations, aiming to minimize downtime and avoid common locking issues during deployments. The current version is 0.1.26, and it typically sees releases for new features or bug fixes rather than a fixed cadence.
Common errors
-
psycopg2.errors.DuplicateTable: relation "appname_modelname" already exists
cause A previous migration failed after creating the table but before marking the migration as applied, or the table was created manually. When `CreateModel` runs again, it errors.fixUse `CreateModelIfNotExists` from `django_pg_migration_tools.operations` instead of `migrations.CreateModel`. This operation creates the table only if it doesn't already exist, making the migration idempotent. -
django.db.utils.OperationalError: cannot CREATE INDEX CONCURRENTLY in a transaction (or similar for other CONCURRENTLY operations)
cause PostgreSQL's `CREATE INDEX CONCURRENTLY` (and other concurrent DDL operations) cannot run inside a transaction block. Django migrations, by default, run inside a transaction.fixFor any migration file containing `Concurrently` operations, set `atomic = False` as a class attribute within the `Migration` class: `class Migration(migrations.Migration): atomic = False`. -
django.db.utils.ProgrammingError: constraint "your_app_tablename_field_check" does not exist
cause Attempting to remove a constraint using `migrations.RemoveConstraint` that might already be removed, or attempting to rename it with `RenameConstraintConcurrently` when the old name is gone.fixConsider using `RemoveConstraintConcurrently` or `RenameConstraintConcurrently` from `django_pg_migration_tools.operations`. These operations often include `if_exists=True` or handle the existence check internally, making them more robust for idempotent migrations. Ensure you are using the correct constraint name.
Warnings
- gotcha Operations ending in 'Concurrently' (e.g., `AddIndexConcurrently`) require careful consideration. They leverage specific PostgreSQL features that prevent them from running within a transaction block. You must set `atomic = False` in your `Migration` class for these to work, which means the entire migration is not atomic.
- gotcha Ignoring the `PostgreSQLCheck` can lead to issues. The library provides `django_pg_migration_tools.checks.PostgreSQLCheck` to identify and warn about common footguns when using its operations, especially regarding concurrent operations being blocked by active transactions. It is recommended to include this check.
- breaking As a pre-1.0 library (currently 0.1.x), the API surface can change between minor versions (e.g., 0.1.25 to 0.1.26) without explicit deprecation warnings or major announcements for less commonly used features. Always review the GitHub changelog or release notes before upgrading to avoid unexpected breaks.
Install
-
pip install django-pg-migration-tools
Imports
- CreateModelIfNotExists
from django_pg_migration_tools.operations import CreateModelIfNotExists
- AddIndexConcurrently
from django_pg_migration_tools.operations import AddIndexConcurrently
- PostgreSQLCheck
from django_pg_migration_tools.checks import PostgreSQLCheck
- MigrateFunction
from django_pg_migration_tools.operations import MigrateFunction
- RenameConstraintConcurrently
from django_pg_migration_tools.operations import RenameConstraintConcurrently
Quickstart
import os
from django.db import migrations, models
from django_pg_migration_tools.operations import AddIndexConcurrently
# Example of a Django migration using AddIndexConcurrently
# This migration file would typically be in app_name/migrations/000X_...
class Migration(migrations.Migration):
atomic = False # Required for CONCURRENTLY operations
dependencies = [
('your_app_name', '0001_initial'), # Replace with your actual dependency
]
operations = [
# Add a B-tree index concurrently on 'field_name' of 'model_name'
AddIndexConcurrently(
model_name='yourmodelname',
name='yourmodelname_field_name_idx',
fields=['field_name'],
db_tablespace='', # Optional: specify tablespace
opclasses=[], # Optional: specify opclasses for the index
condition=None # Optional: specify a partial index condition
),
# Other operations could follow
]