You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django多租户:如何通过中间件动态更新settings的SITE_HOST_NAME

Django Tenants动态设置SITE_HOST_NAME的正确姿势

首先得提一句:直接在中间件里修改settings.SITE_HOST_NAME是绝对不可取的!Django的settings是全局单例,多请求并发时,一个请求改了这个值会直接影响其他请求,轻则出现域名混乱,重则导致业务bug,这个坑我之前踩过,一定要避开。

回到你的需求:根据当前租户域名动态生成正确的SITEURL,我们换个思路来实现:

1. 重构settings.py的配置逻辑

把原来硬编码的SITE_HOST_NAME和SITEURL改成动态获取的方式,不再依赖全局变量:

# settings.py
import os

SITE_HOST_SCHEMA = os.getenv('SITE_HOST_SCHEMA', 'http')
SITE_HOST_PORT = os.getenv('SITE_HOST_PORT', 8000)
# 设置公共租户域名作为 fallback 场景(比如后台任务、无请求时)的默认值
DEFAULT_SITE_HOST_NAME = os.getenv('DEFAULT_SITE_HOST_NAME', 'public.leap.com')

def get_current_siteurl(request=None):
    """动态获取当前请求对应的站点URL"""
    # 优先从request中取当前租户的域名
    if request and hasattr(request, 'tenant_domain'):
        hostname = request.tenant_domain
    else:
        # 无请求场景用默认域名
        hostname = DEFAULT_SITE_HOST_NAME
    
    # 拼接URL,处理端口逻辑(80/443端口可以省略)
    if SITE_HOST_PORT and SITE_HOST_PORT not in ('80', '443'):
        return f"{SITE_HOST_SCHEMA}://{hostname}:{SITE_HOST_PORT}/"
    else:
        return f"{SITE_HOST_SCHEMA}://{hostname}/"

# 全局SITEURL仅作为默认值,实际业务优先使用上面的动态函数
SITEURL = os.getenv('SITEURL', get_current_siteurl())

2. 修改中间件,把租户域名绑定到request对象

不要碰全局settings,而是把获取到的租户域名存到当前请求的request对象里,这样每个请求都有独立的域名数据:

# my_middleware.py
from django.conf import settings
from django.db import connection
from django.utils.deprecation import MiddlewareMixin as MIDDLEWARE_MIXIN
from django_tenants.utils import remove_www, get_tenant_model, get_public_schema_name
from customers.models import Domain

class CustomTenantMiddleware(MIDDLEWARE_MIXIN):
    def get_tenant(self, model, hostname, request):
        return model.objects.get(domain_url=hostname)
    
    def hostname_from_request(self, request):
        """提取并格式化请求的主机名(去掉www和端口)"""
        return remove_www(request.get_host().split(':')[0]).lower()
    
    def process_request(self, request):
        try:
            hostname = self.hostname_from_request(request)
            # 获取当前租户的域名记录
            domain_obj = Domain.objects.get(domain=hostname)
            # 把域名绑定到request对象,供后续业务逻辑调用
            request.tenant_domain = domain_obj.domain
            
            # 顺便处理Django Tenants的租户切换(确保数据库路由正确指向当前租户)
            tenant_model = get_tenant_model()
            tenant = tenant_model.objects.get(domains__domain=hostname)
            connection.set_tenant(tenant)
        except Domain.DoesNotExist:
            # 找不到对应租户时,切换到公共租户
            connection.set_schema_to_public()
            # 用默认域名作为fallback
            request.tenant_domain = settings.DEFAULT_SITE_HOST_NAME

3. 业务代码中如何使用动态URL

现在不管是视图还是模板,都能轻松拿到当前租户的正确URL:

在视图里使用

from django.conf import settings

def my_tenant_view(request):
    # 获取当前租户的站点URL
    current_site = settings.get_current_siteurl(request)
    # 后续业务逻辑直接使用current_site即可
    return render(request, 'my_template.html', {'site_url': current_site})

在模板里全局使用

如果想在所有模板中直接调用,可添加一个上下文处理器:

  1. 在settings的TEMPLATES配置中添加自定义处理器:
# settings.py
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',
                # 加入自定义上下文处理器
                'your_app_name.context_processors.siteurl_processor',
            ],
        },
    },
]
  1. 在你的app下创建context_processors.py文件:
# your_app_name/context_processors.py
from django.conf import settings

def siteurl_processor(request):
    return {
        'current_siteurl': settings.get_current_siteurl(request)
    }

之后在模板里就可以直接用{{ current_siteurl }}获取当前租户的站点URL了。

为什么不能直接修改settings?

再强调一次:Django的settings是全局共享的,启动后所有请求共用同一套配置。如果在中间件里修改settings.SITE_HOST_NAME,多请求并发时会出现值被覆盖的情况——比如A请求刚把值改成t1.leap.com,B请求马上改成t2.leap.com,A请求后续代码拿到的就变成了t2.leap.com,完全混乱。所以必须用每个请求独立的request对象存储租户相关数据,而非修改全局设置。

内容的提问来源于stack exchange,提问作者python enthusiast

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 19:03:04