适配django-tenant-schemas:Django URL后缀式租户切换的URL解析问题
我之前也碰到过一模一样的问题!核心原因是Django默认会把整个请求路径(包括/tenant1/)拿去匹配你的URLconf,但你的URL规则里并没有定义带租户前缀的条目,所以才会返回404。要解决这个问题,我们需要做两件关键的事:在中间件里剥离租户前缀再让Django处理URL,以及生成URL时自动加上租户前缀。
一、修改中间件:剥离租户前缀并调整请求路径
你的中间件已经能正确识别租户了,现在需要在匹配URL之前,把请求路径里的租户部分去掉,让Django只处理剩下的路径。
举个完整的中间件示例:
from django.http import Http404 from tenant_schemas.middleware import BaseTenantMiddleware from tenant_schemas.models import Tenant from django.db import connection class TenantPrefixMiddleware(BaseTenantMiddleware): def process_request(self, request): # 拆分路径,提取租户名 path_components = request.path.strip('/').split('/') # 跳过静态文件、媒体文件路径,让Django默认处理 if path_components and path_components[0] in ['static', 'media']: return None if not path_components or path_components[0] == '': # 根路径,可跳转到租户选择页面或返回默认租户,这里示例返回404 raise Http404("Please specify a tenant in the URL path") tenant_name = path_components[0] try: tenant = Tenant.objects.get(schema_name=tenant_name) except Tenant.DoesNotExist: raise Http404(f"Tenant {tenant_name} does not exist") # 设置当前租户 connection.set_schema(tenant.schema_name) request.tenant = tenant # 关键步骤:修改请求路径,剥离租户前缀 # 把 /tenant1/some-page/ 改成 /some-page/ new_path = '/' + '/'.join(path_components[1:]) if path_components[1:] else '/' request.path_info = new_path request.path = new_path
注意中间件顺序
把这个中间件放在django.middleware.common.CommonMiddleware之前,因为CommonMiddleware会处理URL的斜杠重定向,我们需要先剥离租户前缀再让它处理。在settings.py里的MIDDLEWARE配置:
MIDDLEWARE = [ 'yourapp.middleware.TenantPrefixMiddleware', # 放在最前面 'django.middleware.common.CommonMiddleware', # 其他中间件... ]
二、处理URL反向生成(reverse)
现在Django能正确解析URL了,但用reverse()或者模板里的{% url %}标签生成URL时,不会自动加上租户前缀,所以需要自定义反向逻辑。
方法1:自定义reverse函数
写一个全局的reverse函数,自动拼接当前租户的前缀:
from django.urls import reverse as django_reverse from django.utils.functional import lazy def tenant_reverse(viewname, args=None, kwargs=None, request=None): url = django_reverse(viewname, args=args, kwargs=kwargs) if request and hasattr(request, 'tenant'): return f"/{request.tenant.schema_name}{url}" return url # 支持懒加载,用于模板场景 tenant_reverse_lazy = lazy(tenant_reverse, str)
在模板里使用的话,可以注册一个自定义模板标签:
# yourapp/templatetags/tenant_tags.py from django import template from yourapp.utils import tenant_reverse register = template.Library() @register.simple_tag(takes_context=True) def tenant_url(context, viewname, *args, **kwargs): request = context['request'] return tenant_reverse(viewname, args=args, kwargs=kwargs, request=request)
然后在模板里先加载标签{% load tenant_tags %},再用{% tenant_url 'your-view-name' %}来生成带租户前缀的URL。
方法2:重写Django的reverse逻辑(进阶)
如果不想每次都用自定义函数,可以通过修改django.urls.reverse的行为,但这种方式要小心维护,适合对项目结构有把握的场景。
三、测试验证
- 访问
https://mydomain.com/tenant1/some-page/,中间件会把路径改成/some-page/,匹配你的URLconf,正常返回页面。 - 用自定义的
tenant_reverse生成URL,应该得到/tenant1/some-page/。 - 测试不存在的租户路径,应该返回404提示。
- 测试静态文件
/static/css/style.css,应该能正常访问。
这样调整之后,Django就能正确处理带租户前缀的URL了,不会再出现404问题。
内容的提问来源于stack exchange,提问作者klautern

