Keeping on top of Django's New Migrations
How to avoid being caught out by a missing Migration
For managing database schemas, as great and well-loved as South was, Django's new migrations framework is nicer, particularly thanks to better support for multi-developer, multi-branch projects like YJ.
However, the greater attention now paid to a model's changes has a potential gotcha, especially in a multi-dev environment: even changing a help_text
attribute on a field is now considered migration-worthy, because Django wants to keep as accurate a snapshot as possible of the model's fields. This means that it's actually easier to end up with subtle model changes that you forget to capture in a migration, particularly if you're more used to South's 'lower-resolution' view of model changes.
So, what's the problem here? A developer generating a new migration as part of their work may find it actually includes more than their intended changes, because someone gently amended the model previously but didn't create a migration. This can also lead to Django complaining about a mismatch between models and migrations when running manage.py migrate
when deploying, etc.
To help avoid such annoyances, we've added a check to the YJ test suite. Here's more or less our test, based heavily on the code Django runs to spot out-of-sync models. Feel free to add it to your own project.
from django.apps import apps
from django.db import connections, DEFAULT_DB_ALIAS
from django.db.migrations.autodetector import MigrationAutodetector
from django.db.migrations.executor import MigrationExecutor
from django.db.migrations.state import ProjectState
from django.test import TestCase
class MigrationHealthCheck(TestCase):
"""
Try to pre-empt migration woes.
"""
def migration_progress_callback(*args, **kwargs):
# This is a no-op to keep the MigrationExecutor's
# constructor happy
pass
def test_for_uncreated_migrations(self):
connection = connections[DEFAULT_DB_ALIAS]
# Work out which apps have migrations and which do not
executor = MigrationExecutor(
connection,
self.migration_progress_callback
)
autodetector = MigrationAutodetector(
executor.loader.project_state(),
ProjectState.from_apps(apps),
)
changes = autodetector.changes(graph=executor.loader.graph)
if changes:
self.fail(
"Your models have changes that are not yet reflected "
"in a migration. You should add them now. "
"Relevant app(s): %s" % changes.keys()
)
Tech Lead, Django Dev
Posted in: django