Skip to content

Instantly share code, notes, and snippets.

Last active November 1, 2023 00:04
Show Gist options
  • Save raprasad/f292f94657728de45d1614a741928308 to your computer and use it in GitHub Desktop.
Save raprasad/f292f94657728de45d1614a741928308 to your computer and use it in GitHub Desktop.
Ignoring migrations during Django testing (unmanaged databases, legacy databases, etc)


  • Django 1.9 application with two databases:
    • Legacy database with readonly access via unmanaged models. Both Django models ( and related migrations have "managed" set to False
      • 'managed': False
    • Default database holding django specific tables (e.g. auth_user, django_content_type, etc)

Testing Woes

Combining the blog post and gist above gave the following which is working!

Thanks to Scot Hacker and @NotSqrt who wrote this code:

from project.local_settings import *
from django.test.runner import DiscoverRunner

class DisableMigrations(object):
    def __contains__(self, item):
        return True

    def __getitem__(self, item):
        return "notmigrations"

class UnManagedModelTestRunner(DiscoverRunner):
    Test runner that automatically makes all unmanaged models in your Django
    project managed for the duration of the test run.
    Many thanks to the Caktus Group:

    def setup_test_environment(self, *args, **kwargs):
        from django.db.models.loading import get_models
        self.unmanaged_models = [m for m in get_models() if not m._meta.managed]
        for m in self.unmanaged_models:
            m._meta.managed = True
        super(UnManagedModelTestRunner, self).setup_test_environment(*args, **kwargs)

    def teardown_test_environment(self, *args, **kwargs):
        super(UnManagedModelTestRunner, self).teardown_test_environment(*args, **kwargs)
        # reset unmanaged models
        for m in self.unmanaged_models:
            m._meta.managed = False

# Since we can't create a test db on the read-only host, and we
# want our test dbs created with postgres rather than the default, override
# some of the global db settings, only to be in effect when "test" is present
# in the command line arguments:

if 'test' in sys.argv or 'test_coverage' in sys.argv:  # Covers regular testing and django-coverage

    DATABASES['default']['ENGINE'] = 'django.db.backends.postgresql_psycopg2'
    DATABASES['default']['HOST'] = ''
    DATABASES['default']['USER'] = 'username'
    DATABASES['default']['PASSWORD'] = 'secret'

    DATABASES['tmi']['ENGINE'] = 'django.db.backends.postgresql_psycopg2'
    DATABASES['tmi']['HOST'] = ''
    DATABASES['tmi']['USER'] = 'username'
    DATABASES['tmi']['PASSWORD'] = 'secret'

# The custom routers we're using to route certain ORM queries
# to the remote host conflict with our overridden db settings.
# Set DATABASE_ROUTERS to an empty list to return to the defaults
# during the test run.

# Skip the migrations by setting "MIGRATION_MODULES"
# to the DisableMigrations class defined above
MIGRATION_MODULES = DisableMigrations()

# Set Django's test runner to the custom class defined above
TEST_RUNNER = 'project.test_settings.UnManagedModelTestRunner'
Copy link

dimtion commented Nov 28, 2016

Thanks for the Gist, but in Django 1.10 it doesn't work anymore. The module django.db.models.loading doesn't exist.

The problem can be overcome by using:

from django.apps import apps
self.unmanaged_models = [m for m in apps.get_models() if not m._meta.managed]

Copy link

Stumbled across this in my need for testing in an environment with a mix of managed and unmanaged models in legacy databases. This worked fantastic to enable standard Django testing! Using this technique in combination with FactoryBoy gives you a ton of power for setting up test data.

Worth noting: with this can also be achieved with Pytest-Django - but will just a touch more setup and familiarity with Pytest

Copy link

Thanks! This is super helpful. I think there is one more modification to make this work with newer versions of Django (using 2.2).

class DisableMigrations(object):
    def __contains__(self, item):
        return True

    def __getitem__(self, item):
        return None

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment