运行Django测试触发MigrationSchemaMissing异常求助
Django-tenants 迁移与测试报错解决方案
问题描述
使用django-tenants的Django项目中遭遇以下问题:
- 无法运行任何测试用例
- 执行迁移时触发错误:
django.db.migrations.exceptions.MigrationSchemaMissing: Unable to create the django_migrations table (no schema has been selected to create in LINE 1: CREATE TABLE ‘django_migrations’ (‘id’ bigint NOT NULL PRIMA...
从django-tenant-schemas迁移到django-tenants的过程不顺畅,空数据库执行迁移也报错,租户应用的迁移无法正常执行,需解决上述问题并寻求项目改进建议。
项目详情
依赖配置
[tool.poetry.dependencies] python = "^3.13" django = "5.1.8" # The newest version is not compatible with django-tenants yet django-tenants = "^3.7.0" dj-database-url = "^2.3.0" django-bootstrap5 = "^25.1" django-bootstrap-icons = "^0.9.0" uvicorn = "^0.34.0" uvicorn-worker = "^0.3.0" gunicorn = "^23.0.0" whitenoise = "^6.8.2" encrypt-decrypt-fields = "^1.3.6" django-bootstrap-modal-forms = "^3.0.5" django-model-utils = "^5.0.0" werkzeug = "^3.1.3" tzdata = "^2025.2" pytz = "^2025.2" psycopg = {extras = ["binary", "pool"], version = "^3.2.4"} django-colorfield = "^0.13.0" sentry-sdk = {extras = ["django"], version = "^2.25.1"}
Settings.py配置
import os from pathlib import Path from uuid import uuid4 # External Dependencies import dj_database_url from django.contrib.messages import constants as messages from django.utils.translation import gettext_lazy as _ BASE_DIR = Path(__file__).resolve().parent.parent PROJECT_DIR = os.path.join(BASE_DIR, os.pardir) TENANT_APPS_DIR = BASE_DIR / "tenant" DEBUG = os.environ.get("DEBUG", "False").lower() in ["true", "1", "yes"] TEMPLATE_DEBUG = DEBUG SECRET_KEY = os.environ.get("SECRET_KEY", str(uuid4())) if DEBUG else os.environ["SECRET_KEY"] VERSION = os.environ.get("VERSION", "develop") SECURE_SSL_REDIRECT = os.environ.get("SECURE_SSL_REDIRECT", "False").lower() in ["true", "1", "yes"] try: ALLOWED_HOSTS = os.environ["ALLOWED_HOSTS"].split(";") except KeyError: if not DEBUG: raise ALLOWED_HOSTS = ["localhost", ".localhost"] DEFAULT_DOMAIN = os.environ.get("DEFAULT_DOMAIN", ALLOWED_HOSTS[0]) DEFAULT_FILE_STORAGE = "django_tenants.files.storage.TenantFileSystemStorage" SHARED_APPS = [ "django_tenants", "sfe.common.apps.CommonConfig", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", ] TENANT_APPS = [ "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", "django_bootstrap5", "django_bootstrap_icons", "bootstrap_modal_forms", "sfe.tenant.apps.TenantConfig", "sfe.tenant.email_controller.apps.EmailControllerConfig", ] INSTALLED_APPS = list(SHARED_APPS) + [app for app in TENANT_APPS if app not in SHARED_APPS] MIDDLEWARE = [ "sfe.common.middleware.HealthCheckMiddleware", "django.middleware.security.SecurityMiddleware", "whitenoise.middleware.WhiteNoiseMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.locale.LocaleMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", ] if DEBUG: INTERNAL_IPS = ["localhost", ".localhost"] ROOT_URLCONF = "sfe.urls_tenant" PUBLIC_SCHEMA_NAME = "public" TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", "DIRS": [], "APP_DIRS": True, "OPTIONS": { "context_processors": [ "django.template.context_processors.debug", "django.template.context_processors.request", "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", "sfe.common.context_processor.version", "sfe.common.context_processor.default_domain", ], }, }, ] WSGI_APPLICATION = "sfe.wsgi.application" default_db = dj_database_url.config(engine="django_tenants.postgresql_backend") DATABASES = { "default": { "OPTIONS": {"pool": True}, **default_db, } } DATABASE_ROUTERS = ("django_tenants.routers.TenantSyncRouter",) TEST_RUNNER = "django.test.runner.DiscoverRunner" AUTH_PASSWORD_VALIDATORS = [ {"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"}, {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"}, {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"}, {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"}, ] TENANT_MODEL = "common.SystemTenant" TENANT_DOMAIN_MODEL = "common.Domain" PUBLIC_SCHEMA_NAME = "public" LANGUAGE_CODE = "pl" LANGUAGES = [("pl", "Polski"), ("en", "English")] LOCALE_PATHS = (os.path.join(BASE_DIR, "locale"),) TIME_ZONE = "UTC" USE_TZ = True USE_I18N = True PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__)) STATIC_URL = "/static/" STATIC_ROOT = os.path.join(PROJECT_ROOT, "static") LOGIN_URL = _("/login/") LOGOUT_REDIRECT_URL = "/" LOGIN_REDIRECT_URL = "/" SESSION_COOKIE_AGE = 86400 DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" IMAP_TIMEOUT = 60 ADMINS = [("Damian Giebas", "damian.giebas@gmail.com")] MANAGERS = ADMINS FIRST_DAY_OF_WEEK = 1
项目结构

测试代码示例
# External Dependencies from django.utils.timezone import now from django_tenants.test.cases import TenantTestCase from django_tenants.utils import schema_context # Current App from sfe.tenant.models.due_date import DueDate class DueDateModelTests(TenantTestCase): def setUp(self): super().setUp() def test_create_due_date(self): with schema_context(self.tenant.schema_name): DueDate.objects.create(date=now().date(), name="TestDueDate") assert DueDate.objects.all().count() == 1
核心解决方案
1. 修正测试运行器与数据库配置
- 将默认测试运行器替换为django-tenants专用版本,确保测试时自动处理schema切换:
TEST_RUNNER = "django_tenants.test.runner.TenantTestSuiteRunner" - 确保数据库引擎正确加载,可显式指定默认数据库URL避免环境变量缺失问题:
default_db = dj_database_url.config( default="postgres://username:password@localhost/your_db_name", engine="django_tenants.postgresql_backend" )
2. 规范迁移执行流程
django-tenants要求迁移分两步执行,不能直接用默认的migrate命令:
- 先执行共享应用迁移(针对public schema):
python manage.py migrate_schemas --shared - 创建租户实例后,再执行租户应用迁移:
空数据库初始化时必须先运行python manage.py migrate_schemasmigrate_schemas --shared,否则会出现"无schema可创建"的错误。
3. 优化测试代码
TenantTestCase会自动切换到测试租户的schema,无需手动使用schema_context,简化测试逻辑:
class DueDateModelTests(TenantTestCase): def test_create_due_date(self): DueDate.objects.create(date=now().date(), name="TestDueDate") assert DueDate.objects.count() == 1
若需要测试public schema中的模型,使用PublicSchemaTestCase:
from django_tenants.test.cases import PublicSchemaTestCase class PublicDomainTests(PublicSchemaTestCase): def test_domain_creation(self): # 直接操作public schema中的Domain模型 pass
4. 排查连接池冲突
psycopg的连接池配置可能与django-tenants的schema切换机制冲突,先临时移除连接池配置测试:
DATABASES = { "default": { # 注释掉连接池配置 # "OPTIONS": {"pool": True}, **default_db, } }
确认问题解决后,再参考django-tenants官方文档适配连接池(需确保连接池支持schema切换)。
项目改进建议
- 版本兼容性验证:确认django-tenants 3.7.0与Django 5.1.8的官方兼容性,避免使用未验证的版本组合导致隐性问题。
- 迁移文件清理:从django-tenant-schemas迁移后,检查所有迁移文件,移除旧库相关依赖,确保迁移逻辑仅依赖django-tenants。
- 配置分层管理:将租户配置、公共配置按环境(开发/生产)分离,使用
.env文件管理敏感配置,避免硬编码。 - 测试结构优化:按共享应用、租户应用、public schema三类组织测试用例,明确测试范围,提升可维护性。
- 日志增强:添加schema切换的日志记录,便于排查迁移和测试中的数据库上下文问题。
内容的提问来源于stack exchange,提问作者Draqun
相关产品推荐
相关产品推荐

