我想出了几种可以做到这一点的方法(第四种是使用子域的奖励)。所有这些都假设有一个中间件可以检测区域并根据请求对其进行设置。
按照@RemcoGerlich 的提示,模仿 Django 如何处理 URL 的国际化。 LocaleMiddleware 检测语言并在该请求上设置活动语言(使用线程局部变量)。然后,使用该活动语言通过 i18n_patterns() 形成 URL,它实际上返回 LocaleRegexURLResolver(它是普通解析器的子类)而不是 url。我相信可以做类似的事情来支持其他类型的前缀。
更暴力的方法是再次将区域存储在请求中,而且再次存储在线程局部变量中,就像 Django 为活动语言所做的那样。更新 URL 以具有区域前缀的命名参数并添加到视图参数。实现自定义反向以添加区域参数。如果倾向于做坏事,可以对其进行猴子补丁以避免触及每个 reverse 和 url 模板引用。
使用基于区域的中间件set the request.urlconf,覆盖ROOT_URLCONF。这仅为此请求提供了一组完全不同的 URL。每个区域创建一个新的 URLconf,添加它们的前缀,然后包含基本 URLconf。无需捕获路径的区域部分或弄乱视图参数。反转 URL “正常工作”。
如果您想使用子域,而我没有使用,有一个名为 django-hosts 的 Django 应用程序,在此问题中引用:Django: Overwrite ROOT_URLCONF with request.urlconf in middleware。
对于我的应用程序,使用中间件覆盖 request.urlconf 是最简单、最优雅的解决方案。这是中间件的一个片段:
# ... detect region first based on path, then session, and and maybe later IP address...
# Then force the URLconf:
if request.region == Region.EU:
request.urlconf = "mysite.regional_urls.eu_urls"
else:
request.urlconf = "mysite.regional_urls.us_urls"
我为每个区域创建了一个新的 URLconf,但它们是 DRY 单行:
urlpatterns = create_patterns_for_region(Region.EU)
这些引用了一个模板,该模板将我想成为区域性的 URL 与我想留下“裸露”的 URL 结合起来:
from django.conf.urls import patterns, include, url
def create_patterns_for_region(region):
return patterns(
'',
# First match regional.
url(r'^{}/'.format(region.short), include('mysite.regional_urls.regional_base_urls')),
# Non-regional pages.
url(r'', include('mysite.regional_urls.nonregional_base_urls')),
# Any regional URL is missing.
url(r'^{}/.*'.format(Region.REGION_PREFIX), error_views.Custom404.as_error_view()),
# Attempt to map any non-regional URL to region for backward compatibility.
url(r'.*', RegionRedirect.as_view()),
)
最后是向后兼容的重定向视图:
class RegionRedirect(RedirectView):
""" Map paths without region to regional versions for backward compatibility.
"""
permanent = True
query_string = True
def get_redirect_url(self, *args, **kwargs):
self.url = "/" + self.request.region.short + self.request.path
return super(RegionRedirect, self).get_redirect_url(*args, **kwargs)
确保更新缓存以包含区域。 ;)