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 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 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.