A Django migration records a schema transition for an installed application so that a database can apply a versioned sequence of changes.
Django migrations: generate and apply an owned application schema
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
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
initial migration written: True
initial migration applied: True
stored records: 1Costs 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.
