Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Ignoring migrations during Django testing (unmanaged databases, legacy databases, etc)

Scenario

  • Django 1.9 application with two databases:
    • Legacy database with readonly access via unmanaged models. Both Django models (models.py) 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: http://bit.ly/1N8TcHW
    '''

    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'] = '127.0.0.1'
    DATABASES['default']['USER'] = 'username'
    DATABASES['default']['PASSWORD'] = 'secret'

    DATABASES['tmi']['ENGINE'] = 'django.db.backends.postgresql_psycopg2'
    DATABASES['tmi']['HOST'] = '127.0.0.1'
    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.
DATABASE_ROUTERS = []

# 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'
@dimtion

This comment has been minimized.

Copy link

@dimtion 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]
@hannylicious

This comment has been minimized.

Copy link

@hannylicious hannylicious commented Jan 10, 2019

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

@jdelamare

This comment has been minimized.

Copy link

@jdelamare jdelamare commented Mar 26, 2021

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