Install & Compatibility
Where this runs
tested against v1.5.0 · pip install
no network on importno background threads
Install × environment matrix
Each cell = how many times install + import succeeded across repeated harness runs. Partial = flaky.
glibc = Debian/Ubuntu slim · musl = Alpine Linux
muslpy 3.10–3.920 runs
installs and imports cleanly · install 0.0s · import 0.000s · 18.4MB
glibcpy 3.10–3.920 runs
installs and imports cleanly · install 1.6s · import 0.000s · 19MB
16MB installed
● package 16MB
Code
Verified usage
Verified import paths — ran on the pinned version, not inferred.
Migrator
✓ from django_test_migrations import Migrator
✗ from django_test_migrations.migrator import Migrator
The quickstart demonstrates using the `Migrator` class with `pytest` to test a specific migration. It outlines how to set up the database to an 'initial' state before the migration, create test data using the 'old' model state, apply the migration, and then assert the results using the 'new' model state. It emphasizes accessing models through `old_state.apps.get_model()` and `new_state.apps.get_model()` to ensure correct historical model versions are used.
import os
import pytest
from django.conf import settings
# Minimal Django settings for testing if not already configured
# In a real project, this would typically be in your settings.py
if not settings.configured:
settings.configure(
INSTALLED_APPS=[
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# Add your app here, e.g., 'myapp'
],
DATABASES={
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ':memory:',
}
},
MIGRATION_MODULES={ # Example for a hypothetical app 'myapp'
# 'myapp': 'myapp.migrations' # Uncomment if your app has migrations
}
)
from django_test_migrations.migrator import Migrator
@pytest.fixture(scope='function')
def migrator(db) -> Migrator:
"""Provides a Migrator instance for testing against the 'default' database."""
return Migrator(database='default')
def test_my_migration(migrator: Migrator):
# Set up the database to a state *before* the migration you want to test
# Example: app_label 'myapp', migration name '0001_initial'
old_state = migrator.apply_initial_migration(('myapp', '0001_initial'))
# Get the model class from the old state
# Replace 'MyModel' with your actual model name
OldMyModel = old_state.apps.get_model('myapp', 'MyModel')
# Create some data that will be affected by the migration
# Example: creating an instance with an old field value
old_instance = OldMyModel.objects.create(some_old_field='value')
# Apply the migration you want to test
# Example: app_label 'myapp', migration name '0002_new_field'
new_state = migrator.apply_tested_migration(('myapp', '0002_new_field'))
# Get the model class from the new state
NewMyModel = new_state.apps.get_model('myapp', 'MyModel')
# Assertions: Check that the data was migrated correctly
new_instance = NewMyModel.objects.get(pk=old_instance.pk)
assert hasattr(new_instance, 'some_new_field') # Check for new field
# assert new_instance.some_new_field == 'expected_new_value'
# Optional: Test rollback
# migrator.reset()
# assert not NewMyModel.objects.filter(pk=old_instance.pk).exists() # Or check rolled back state
Debug
Known issues
breakingIn version 1.0.0, the `Migrator` methods `before` and `after` were renamed to `apply_initial_migration` and `apply_tested_migration` respectively. Code using the old method names will break.fixUpdate calls from `migrator.before()` to `migrator.apply_initial_migration()` and `migrator.after()` to `migrator.apply_tested_migration()`.
affects: >=1.0.0
gotchaDirectly importing models into your migration tests (e.g., `from myapp.models import MyModel`) is a common pitfall. This can lead to unexpected behavior or failures as your tests won't be using the historical model state relevant to the specific migration being tested.fixAlways retrieve models using the provided state objects: `old_state.apps.get_model('myapp', 'MyModel')` or `new_state.apps.get_model('myapp', 'MyModel')`. This ensures you're interacting with the correct model schema at each stage of the migration test. affects: All versions
gotchaWhen testing multiple Django applications that have interdependent migrations, you must provide `before` and `after` as a list of `(app_name, migration_name)` tuples. Additionally, when retrieving models, explicitly specify the app name (e.g., `self.get_model_before('otherapp.OtherModel')`).fixFor multiple apps, define `before = [('app1', '0001'), ('app2', '0001')]` and `after = [('app1', '0002'), ('app2', '0002')]`. Use `self.get_model_before('app_name.MyModel')` to get models. affects: All versions
gotchaIf your tests involve Django's `post_migrate` signals, be aware that `django-test-migrations` clears the receiver list at the start of tests and restores it afterwards. This is to prevent side effects.fixIf you need to test your own `post_migrate` signals, attach and remove them explicitly within your test methods or setup/teardown.
affects: All versions
deprecatedSupport for older Python and Django versions has been dropped in recent releases. Specifically, Python 3.7, 3.8, and 3.9, and Django 2.2 are no longer officially supported.fixUpgrade your project's Python version to 3.10 or newer, and Django version to 3.2, 4.1, 4.2, 5.0, or 5.2.
affects: >=1.3.0 (Python 3.7, Django 2.2), >=1.4.0 (Python 3.8), >=1.5.0 (Python 3.9)
Upgrade
Version history
1.5.0latest on PyPI · released Apr 18, 2025
Audit
Dependencies
DjangorequiredCore framework dependency; supports Django versions 3.2, 4.1, 4.2, 5.0, 5.2.
PythonrequiredRequires Python versions >=3.10, <4.0.
typing-extensionsrequiredProvides backported and experimental type hints.