欢聚网

在线小游戏,用 Django ...

↑我也要推荐

Django 子域的实现

发布时间:2011-10-25 00:09:44, 关注:+139168, 赞美:+10, 不爽:+14

原始出处: 九九房博客

Django作为一个典型的MVC三层框架,有着自己一套full-stack的实现。针对开发和运行效率而言,有人想用[SQLAlchemy]来替换Django的ORM;有人会吐槽Django的Template模板渲染系统,转而尝试[Jinjia2];但或许没有人对Django的URL处理机制不满。StackOverflow上说,面对愈发大型的系统,Django的URL Routing可能是唯一剩下的模块。Django使用了强大的正则表达式来Resolve URL,重用了程序员既有的知识;提供了Reverse方法来反向构造URL,使得View与其对应的URL解耦;再加之飞快的处理速度(因为全局的resolver_cache的存在),使得一切看起来是那么美好。然而,美中不足的是,Django官方对subdomain的支持并不完备,譬如URL的Routing只考虑URI,并不考虑host信息(#8896)。

在Django里有两种方式可以实现subdomain:Contrib下的SitesApp&Middleware动态指定request.urlconf。

让我们先来看看第一种方式:Sites,可定义任意的Domain。
我们知道每个项目的配置文件settings.py都一个属性SITE_ID(默认为1), INSTALLED_APPS中也默认包含‘django.contrib.sites’,那么说明了django内部有使用这个APP。
那么,对于我们上层应用而言,Sites适用于什么场合呢?Django文档上说:
“Use it if your single Django installation powers more than one site and you need to differentiate between those sites in some way.”
对于singleDjangoinstallation,我的理解是不同site使用相同的default数据库,即它们使用同一张django_site数据库表。
咋一看,很符合我们的需求嘛:我们本来在同一个数据库里,只要设置不同的site_id即可。但是site_id设置在settings里,这意味着不同的site需要有不同的project。这对于某些不同站点间功能关联不紧密,原有结构划分清晰,有可重用的APP的项目而言可能不是什么大问题,然而,对于subdoamin来言其功能逻辑代码关联紧密,用sites的方式来处理显得过于粗暴了。

来看看第二种方式:middleware指定request.urlconf属性。
Django对于request的转发基于两个部分。1. request.path_info, 请求的URL。2. request.urlconf, 项目处理的全部URL。
在middleware里我们可以指定urlconf到特定的urls.py,如果没有指定的话,默认使用settings里的ROOT_URLCONF。在调用urlresolvers中resolve和reverse方法都可指定request的urlconf属性,但遗憾的是template的url标签并没有将urlconf属性暴露出来。

试想一下,我们可以给不同的subdomain定义自己的一套urlconf(并不麻烦,归结于django灵活的urlpatterns构造),再在middleware里根据domain的信息,将其转发到定义的subdomain上,就可以达到了目的。

那么,这样的实现方式会有什么问题呢?
1. runserver时除ROOT_URLCONF定义的patterns,其他域名下的url不可用。因为我们在runserver时通过IP访问,没有Domain信息。
2. 因为Django的reverse没有domain信息,我们需要手动将reverse的结果指定到对应的subdomain上。

为解决这两个问题,我们使用了一个trick。我们在middleware处理时设法保留了subdomain的信息,并将其反映在path上;在urls.py中构造对应的patterns;最后在reverse时将subdomain的信息取出构造出正确的url。代码如下:

SubdomainMiddleware:

class SubdomainMiddleware(object):
    def process_request(self, request):
        domain_parts = request.get_host().split('.')
        if len(domain_parts) == 3:
            # 将subdomain的信息放到URI的第一层级
            request.path_info = '/%s%s' % (domain_parts[0], request.path)
    return None

将其添加在MIDDLEWARE_CLASSES中,确保一个靠上的位置。在此,我们没有修改request.path的信息,是因为path_info才是Django内部分发的依据,同时我们也可以根据我们的需要灵活引用path或path_info。

Reverse Monkey Patch:

# not in settings.py: will be imported twice
# not in urls.py: too late
# works in models.py
from django.conf import settings
if not settings.DEBUG:
    from django.core import urlresolvers
    def reverse_subdomain(*args, **kwargs):
        path_info = old_reverse(*args, **kwargs)
        parts = path_info[1:].split('/', 1)
        path_info = 'http://%s%s/%s' % (
                parts[0], settings.SESSION_COOKIE_DOMAIN, parts[1])
        return path_info
    old_reverse = urlresolvers.reverse
    urlresolvers.reverse = reverse_subdomain

在此我们有一个假设,假设我们和我们要使用的第三方包并不依赖于reverse的结果,都不会对其做进一步的处理,仅仅是显示在页面上显示或重定向等直接返回的行为。这个假设我们感觉基本上成立。

还有一点需要说明的是,包含re.VERBOSE(\x)或零宽断言的正则表达式在resolver时没有问题,但在reverse时会异常,因为它们是“Non-reversible reg-exp portion”。偶尔,我们可能会为了效率考虑,或许更多的是为了满足自己的好奇心和挑战欲,写出复杂的正则匹配,而往往是没有必要的。

如果你觉得本站对你有帮助,欢迎向本站赞助 :P

使用支付宝捐赠

Copyright© Python4cn(news, jobs) simple-is-better.com, 技术驱动:powered by web.py 空间主机:Webfaction

版权申明:文章转载已注明出处,如有疑问请来信咨询。本站为 python 语言推广公益网站,与 python 官方没有任何关系。

联系/投搞/留言: en.simple.is.better@gmail.com 向本站捐赠