Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Django migrations: generate and apply an owned application schema

Last updated: 30 Sept 20264 min read
tutorial
IntermediateBy AITrove Editorial

A Django migration records a schema transition for an installed application so that a database can apply a versioned sequence of changes.

Download Python source kit

Operation contract

The fixture writes its own tiny installed app into a temporary directory, generates its initial migration and applies it to an in-memory SQLite database. It then creates a record through the model and checks the applied migration entry. Unlike the separate schema_editor fixtures, this program exercises Django’s migration loader and recorded history.

Failure and ownership boundary

Generating a migration compares model state with migration state; it does not inspect every production database as a source of truth. A successful initial migration says nothing about a large-table alteration, concurrent traffic or data backfill. Review generated operations and plan releases before changing a live schema. Django models: database constraints survive an unchecked save and Python package layout: owned imports and module entrypoints provide the prerequisites.

Tested environment

Dependency check: this program was executed on CPython 3.14.6 with Django==5.2.17. Install these versions in a separate virtual environment. The download includes the recorded environment snapshot; no third-party package is part of the website runtime.

Working program

python
from pathlib import Path
from io import StringIO
import importlib
import sys
import tempfile

with tempfile.TemporaryDirectory() as directory:
    package = Path(directory) / "receipt_store"
    package.mkdir()
    (package / "__init__.py").write_text("", encoding="utf-8")
    (package / "models.py").write_text(
        'from django.db import models\n'
        'class Receipt(models.Model):\n'
        '    receipt_id = models.CharField(max_length=6, unique=True)\n', encoding="utf-8")
    sys.path.insert(0, directory)
    from django.conf import settings
    settings.configure(INSTALLED_APPS=["receipt_store"],
                       DATABASES={"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"}},
                       DEFAULT_AUTO_FIELD="django.db.models.AutoField")
    import django
    django.setup()
    from django.core.management import call_command
    from django.db import connection
    from django.db.migrations.loader import MigrationLoader
    with StringIO() as command_output:
        call_command("makemigrations", "receipt_store", verbosity=0, stdout=command_output)
        call_command("migrate", verbosity=0, stdout=command_output)
    Receipt = importlib.import_module("receipt_store.models").Receipt
    Receipt.objects.create(receipt_id="R-0041")
    print("initial migration written:", (package / "migrations" / "0001_initial.py").exists())
    print("initial migration applied:", ("receipt_store", "0001_initial") in MigrationLoader(connection).applied_migrations)
    print("stored records:", Receipt.objects.count())
    connection.close()
    sys.path.remove(directory)

Output

Output
initial migration written: True
initial migration applied: True
stored records: 1

Costs and limits

Schema changes can lock tables, rewrite data or rebuild indexes depending on backend and operation. This tiny initial SQLite schema has none of the scale or deployment costs of a production alteration; no zero-downtime claim follows from it.

Common Mistakes

  • Commit reviewed migration files with the model change.
  • Do not treat an initial schema test as evidence that a later data migration is safe.

Connected lessons

Django models: database constraints survive an unchecked save, Python package layout: owned imports and module entrypoints, Django atomic transactions: rollback the batch and defer callbacks.

python
django-migrations
Storage details