I have a django project with multiple databases:
DATABASES = {
'default': {},
'db1': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / DB1_NAME,
},
'db2': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / DB2_NAME,
}
}
DATABASE_ROUTERS = ['app.router1.Router1', 'app.router2.Router2']
The router for db1
is defined:
class Router1:
route_app_labels = {'app', 'sessions', 'auth', 'admin', 'contenttypes'}
def db_for_read(self, model, **hints):
if model._meta.app_label in self.route_app_labels:
return 'db1'
return None
def db_for_write(self, model, **hints):
if model._meta.app_label in self.route_app_labels:
return 'db1'
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 == 'db1'
return None
Everything seems to work well, except creating new users. When I try to create a new user, I get the following error:
django.core.exceptions.ImproperlyConfigured: settings.DATABASES is improperly configured. Please supply the ENGINE value. Check settings documentation for more details.
When I change db1
to default
, user creation seems to work well.
Full error traceback:
Traceback (most recent call last):
File "/.venv/lib/python3.9/site-packages/django/core/handlers/exception.py", line 55, in inner
response = get_response(request)
File "/.venv/lib/python3.9/site-packages/django/core/handlers/base.py", line 197, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/.venv/lib/python3.9/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
return view_func(*args, **kwargs)
File "/.venv/lib/python3.9/site-packages/rest_framework/viewsets.py", line 125, in view
return self.dispatch(request, *args, **kwargs)
File "/.venv/lib/python3.9/site-packages/rest_framework/views.py", line 509, in dispatch
response = self.handle_exception(exc)
File "/.venv/lib/python3.9/site-packages/rest_framework/views.py", line 469, in handle_exception
self.raise_uncaught_exception(exc)
File "/.venv/lib/python3.9/site-packages/rest_framework/views.py", line 480, in raise_uncaught_exception
raise exc
File "/.venv/lib/python3.9/site-packages/rest_framework/views.py", line 506, in dispatch
response = handler(request, *args, **kwargs)
File "/.venv/lib/python3.9/site-packages/rest_framework/mixins.py", line 19, in create
self.perform_create(serializer)
File "/.venv/lib/python3.9/site-packages/djoser/views.py", line 136, in perform_create
user = serializer.save()
File "/.venv/lib/python3.9/site-packages/rest_framework/serializers.py", line 212, in save
self.instance = self.create(validated_data)
File "/.venv/lib/python3.9/site-packages/djoser/serializers.py", line 65, in create
user = self.perform_create(validated_data)
File "/.venv/lib/python3.9/site-packages/djoser/serializers.py", line 72, in perform_create
with transaction.atomic():
File "/.venv/lib/python3.9/site-packages/django/db/transaction.py", line 197, in __enter__
if not connection.get_autocommit():
File "/.venv/lib/python3.9/site-packages/django/db/backends/base/base.py", line 455, in get_autocommit
self.ensure_connection()
File "/.venv/lib/python3.9/site-packages/django/db/backends/dummy/base.py", line 20, in complain
raise ImproperlyConfigured(
django.core.exceptions.ImproperlyConfigured: settings.DATABASES is improperly configured. Please supply the ENGINE value. Check settings documentation for more details.
I think I found the root cause of this issue. It seems that before the new user is saved, a transaction is being created, but this transaction always tries to access the default database. If default database is not defined, the above error is returned (in case the default database is defined, it means that the transaction is accessing the default, while users are being written to a different database).
In djoser\serializers.py
, the perform_create
method first creates an atomic transaction with no arguments:
def perform_create(self, validated_data):
with transaction.atomic():
user = User.objects.create_user(**validated_data)
if settings.SEND_ACTIVATION_EMAIL:
user.is_active = False
user.save(update_fields=["is_active"])
return user
The atomic
function creates it with using=None
:
def atomic(using=None, savepoint=True, durable=False):
# Bare decorator: @atomic -- although the first argument is called
# `using`, it's actually the function being decorated.
if callable(using):
return Atomic(DEFAULT_DB_ALIAS, savepoint, durable)(using)
# Decorator: @atomic(...) or context manager: with atomic(...): ...
else:
return Atomic(using, savepoint, durable)
Then inside Atomic
class, in __enter__
method the function get_connection
is called. This function returns default
database in case using
is not defined:
def get_connection(using=None):
"""
Get a database connection by name, or the default database connection
if no name is provided. This is a private API.
"""
if using is None:
using = DEFAULT_DB_ALIAS
return connections[using]
The fix for this should probably be to pass the right the database to atomic
function.
As mentioned in the question, renaming db1
to default
bypasses this issue.