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