Django多数据库环境创建用户时默认数据库配置报错问题
问题场景
我有一个配置了多数据库的Django项目,数据库配置如下:
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']
db1对应的路由Router1定义:
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
项目除创建新用户外其他功能正常,创建用户时抛出错误:
django.core.exceptions.ImproperlyConfigured: settings.DATABASES is improperly configured. Please supply the ENGINE value. Check settings documentation for more details.
将db1改为default后,用户创建功能恢复正常,完整错误栈如下:
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.
问题原因
从错误栈可以看出,问题出在transaction.atomic()调用上。Django的transaction.atomic()默认会使用default数据库,但当前配置里default是空字典,未指定ENGINE参数,因此触发配置错误。
尽管路由Router1已经将auth(用户模型所属的app)指向db1,但transaction.atomic()在未指定数据库的情况下,仍会尝试使用default数据库连接,而该连接是未配置的dummy后端,最终导致报错。
解决方案
方案1:给default数据库配置实际连接(推荐)
直接将default指向已配置的db1,让默认事务使用正确的数据库:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': BASE_DIR / DB1_NAME, }, 'db1': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': BASE_DIR / DB1_NAME, }, 'db2': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': BASE_DIR / DB2_NAME, } }
也可以通过复用配置避免重复代码:
DB1_CONFIG = { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': BASE_DIR / DB1_NAME, } DATABASES = { 'default': DB1_CONFIG, 'db1': DB1_CONFIG, 'db2': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': BASE_DIR / DB2_NAME, } }
方案2:修改事务代码,指定使用db1
如果不想修改default配置,可以自定义Djoser的序列化器,在perform_create中指定事务使用的数据库:
from djoser.serializers import UserCreateSerializer from django.db import transaction class CustomUserCreateSerializer(UserCreateSerializer): def perform_create(self, validated_data): # 指定使用db1的事务 with transaction.atomic(using='db1'): return super().perform_create(validated_data)
然后在Djoser配置中替换默认序列化器:
# settings.py DJOSER = { 'SERIALIZERS': { 'user_create': 'your_app.serializers.CustomUserCreateSerializer', }, }
方案3:完善数据库路由的事务处理
可以通过修改路由逻辑,让Django处理auth模型事务时自动选择db1,但该方案逻辑相对复杂,不如前两种直观,因此不优先推荐。
内容的提问来源于stack exchange,提问作者michael

