0

I've created a cut down scenario from scratch to hopefully find a solution to what I have witnessed in some production code - which has stopped me dead in my tracks.

The code is here:

https://github.com/andez2000/issue-django-multi-db

The problem is that I have a Django project - which will support multiple databases. So for this scenario:

  1. acmeapp targets acmeapp_db.
  2. auth targets auth_db.
  3. There is no default connection - all connections are aliased.

This is not a direct result from watching the following:
Django Multiple Database Setup Ex1
Django Testing - Model Testing Introduction

Routers have been setup and configured to direct the models to connections.

When running py manage.py test the following errors are evident.

AssertionError: Database queries to 'acmeapp_db' are not allowed in this test. Add 'acmeapp_db' to acmeapp.tests.model.TestModels.databases to ensure proper test isolation and silence this failure.

Fixing this as suggested yields an additional error. This is done by adding databases = {'acmeapp_db'} to TestModels. This produces:

ImproperlyConfigured("Circular dependency in TEST[DEPENDENCIES]") django.core.exceptions.ImproperlyConfigured: Circular dependency in TEST[DEPENDENCIES]

core/settings.py

DATABASES = {
    'default': {},
    'auth_db': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'auth_db.sqlite3',
    },
    'acmeapp_db': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'acmeapp_db.sqlite3',
    }
}

DATABASE_ROUTERS = [
    'core.db_routers.AuthRouter',
    'core.db_routers.AcmeAppRouter'
]

tests/model.py

from django.test import TestCase
from acmeapp.models import Post


class TestModels(TestCase):

    def setUp(self):
        Post.objects.create(title="This is a test")

    def test_Post_str(self):
        post = Post.objects.first()

        self.assertEqual(str(post), "This is a test")

core/db_routers.py

class AuthRouter:
    route_app_labels = ['auth', 'contenttypes', 'sessions', 'admin']

    def db_for_read(self, model, **hints):
        if model._meta.app_label in self.route_app_labels:
            return 'auth_db'

        return None

    def db_for_write(self, model, **hints):
        if model._meta.app_label in self.route_app_labels:
            return 'auth_db'

        return None

    def allow_relation(self, obj1, obj2, **hints):
        if (
                obj1._meta.app_label in self.route_app_labels or
                obj2._meta.app_label in self.route_app_labels
        ):
            return True

        return None

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        if app_label in self.route_app_labels:
            return db == 'auth_db'
        return None
    
    
class AcmeAppRouter:
    route_app_labels = ['acmeapp']

    def db_for_read(self, model, **hints):
        if model._meta.app_label in self.route_app_labels:
            return 'acmeapp_db'

        return None

    def db_for_write(self, model, **hints):
        if model._meta.app_label in self.route_app_labels:
            return 'acmeapp_db'

        return None

    def allow_relation(self, obj1, obj2, **hints):
        if (
                obj1._meta.app_label in self.route_app_labels or
                obj2._meta.app_label in self.route_app_labels
        ):
            return True

        return None

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        if app_label in self.route_app_labels:
            return db == 'acmeapp_db'
        return None

Having debugged the routers - the database alias in the parameter db is always 'default'.

Is this setup even possible? Am I missing something?

For this scenario the mechanism for applying migrations has somewhat changed from a single migration call py manage.py migrate to:

py manage.py migrate --database=auth_db
py manage.py migrate --database=acmeapp_db

A little bit more debugging, it would appear that core.settings.DATABASES has:

{
    "default": {
        "ENGINE": "django.db.backends.dummy",
        "ATOMIC_REQUESTS": False,
        "AUTOCOMMIT": True,
        "CONN_MAX_AGE": 0,
        "CONN_HEALTH_CHECKS": False,
        "OPTIONS": {},
        "TIME_ZONE": None,
        "NAME": "",
        "USER": "",
        "PASSWORD": "",
        "HOST": "",
        "PORT": "",
        "TEST": {
            "CHARSET": None,
            "COLLATION": None,
            "MIGRATE": True,
            "MIRROR": None,
            "NAME": None,
        },
    },
    "auth_db": {
        "ENGINE": "django.db.backends.sqlite3",
        "NAME": WindowsPath(
            "SOMEFOLDER\auth_db.sqlite3"
        ),
        "ATOMIC_REQUESTS": False,
        "AUTOCOMMIT": True,
        "CONN_MAX_AGE": 0,
        "CONN_HEALTH_CHECKS": False,
        "OPTIONS": {},
        "TIME_ZONE": None,
        "USER": "",
        "PASSWORD": "",
        "HOST": "",
        "PORT": "",
        "TEST": {
            "CHARSET": None,
            "COLLATION": None,
            "MIGRATE": True,
            "MIRROR": None,
            "NAME": None,
        },
    },
    "acmeapp_db": {
        "ENGINE": "django.db.backends.sqlite3",
        "NAME": WindowsPath(
            "SOMEFOLDER\acmeapp_db.sqlite3"
        ),
        "ATOMIC_REQUESTS": False,
        "AUTOCOMMIT": True,
        "CONN_MAX_AGE": 0,
        "CONN_HEALTH_CHECKS": False,
        "OPTIONS": {},
        "TIME_ZONE": None,
        "USER": "",
        "PASSWORD": "",
        "HOST": "",
        "PORT": "",
        "TEST": {
            "CHARSET": None,
            "COLLATION": None,
            "MIGRATE": True,
            "MIRROR": None,
            "NAME": None,
        },
    },
}

Andez
  • 5,588
  • 20
  • 75
  • 116
  • @AbdulAzizBarkat - yes... yes it does... thanks . I was suspecting default was the culprit - and had tried the other bits around it - but didnt add the DEPENDENCIES under the test in the db configuration. – Andez Aug 10 '22 at 14:03
  • Leaving a comment here to help others find the solution for ```ImproperlyConfigured("Circular dependency in TEST[DEPENDENCIES]") django.core.exceptions.ImproperlyConfigured: Circular dependency in TEST[DEPENDENCIES]```. This error can be solved by adding dependencies to your DB connections. https://stackoverflow.com/questions/56899418/unit-tests-project-with-multiple-application-and-databases-circular-dependency – Shashank Yadav Aug 12 '22 at 07:00

0 Answers0