在 Django 中,如何向大多数路径添加区域前缀

Joh*_*ann 0 django country routes url-routing region

我正在将一个电子商务网站转换为特定于区域(例如美国、欧盟)的网站,因此根据访问者将看到的内容,它基本上会感觉像是一个不同的网站,即使它实际上是一个网站(对于许多人来说)原因)。我网站上的大多数路径都将成为特定于区域的路径,通过在路径开头添加区域前缀,例如“/us/”(我可以转换所有路径,但如果它使它变得更加容易的话)。

我的计划:

  • 中间件根据 1) 请求路径、2) 会话或 3) 根据 IP 地址的顺序猜测来识别区域,并将其设置在请求对象上。此外,一旦他们使用区域路径,它就会存储为会话值。通过这种方式,区域上下文可以跨非区域特定的 URL 传递。

  • 特定于区域的 URL 模式必须更新以匹配区域,即使我已经在中间件中检测到该区域,因为逻辑比路径更复杂。尽管如此,出于下一个原因(反向),我必须将其作为参数并传递到我的所有视图中。此外,任何变为区域性的路径都将使其先前的模式301重定向到其区域性路径。

  • 为了生成特定于区域的链接,我必须通过添加区域参数来更新对 reverse() 和 {% url %} 的许多调用。我希望这里有一些层可以自定义,以便在了解请求的情况下动态反转 URL。

我的主要问题是处理逆转的最佳方法(最后一个项目符号)。感觉像是做了很多不必要的工作。我愿意接受更好的方法来解决整体问题。

更新:

  • 我排除了子域名,因为众所周知,它们不利于 SEO 和转移权限。另外,我认为子域可能意味着完全不同的设置,而现在我将其作为单个网络应用程序进行管理。
  • 正如 @RemcoGerlich 指出的,基本上我想添加 LocaleMiddleware/i18n_patterns 在 urlconf 和逆向中添加的自动行为。

Joh*_*ann 5

我想出了几种方法可以做到这一点(第四种是使用子域的好处)。所有这些都假设有一个中间件来检测区域并根据请求进行设置。

  1. 按照 @RemcoGerlich 的提示,模仿 Django 如何处理 URL 国际化。LocaleMiddleware检测语言并设置该请求的活动语言(使用线程局部变量)。然后,该活动语言用于通过 i18n_patterns() 形成 URL,它实际上返回 a LocaleRegexURLResolver(它是普通解析器的子类)而不是 url。我相信可以做类似的事情来支持其他类型的前缀。

  2. 更强力的方法是不仅将区域存储在请求中,而且再次存储在线程局部变量中,就像 Django 对活动语言所做的那样。更新 URL 以具有区域前缀的命名参数并添加到视图参数。实现自定义反向以添加区域参数。如果想要作恶,可以对其进行猴子修补,以避免触及每个单个reverseurl模板引用。

  3. 使用中间件根据区域设置,以覆盖request.urlconfROOT_URLCONF. 这仅为该请求提供了一组完全不同的 URL。每个区域创建一个新的 URLconf,添加其前缀,然后包含基本 URLconf。无需捕获路径的区域部分或弄乱视图参数。反转 URL“正常”。

  4. 如果您想使用子域(我没有),则有一个名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"
Run Code Online (Sandbox Code Playgroud)

我为每个区域创建了一个新的 URLconf,但它们都是枯燥的单行:

urlpatterns = create_patterns_for_region(Region.EU)
Run Code Online (Sandbox Code Playgroud)

这些引用了一个模板,该模板将我想要成为区域性的 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()),
        )
Run Code Online (Sandbox Code Playgroud)

最后是用于向后兼容的重定向视图:

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)
Run Code Online (Sandbox Code Playgroud)

确保更新缓存以包含区域。;)