你如何使用Django URL命名空间?

30

我正在尝试掌握Django URL命名空间。但是我找不到任何示例或文档。

以下是我尝试过的内容。

urls.py:

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^foo/', include('sub_urls', namespace='foo', app_name='foo')),
    (r'^bar/', include('sub_urls', namespace='bar', app_name='bar')),            
)

sub_urls.py:

from django.conf.urls.defaults import patterns, url
from views import view1

urlpatterns = patterns('views',
    url(r'^(?P<view_id>\d+)/$', view1, name='view1')
)

views.py:

from django.shortcuts import render_to_response

def view1(request, view_id):
    return render_to_response('view1.html', locals())

在view1.html中,{% url foo:view1 3 %} 输出 /foo/3,{% url bar:view1 3 %} 输出 /bar/3。无论我浏览到/foo/X还是/bar/X都是如此。
我想要的是能够浏览到/foo/X或/bar/X,并且使 {% url view1 3 %} 分别输出 /foo/3 或 /bar/3。

我这样做的原因是为了让这两个命名空间使用不同的CSS文件。也就是说,在/foo下浏览将包括foo.css,在/bar下浏览将包括bar.css。 - Chase Seibert
我想提醒您,如果网站上有不同的URL但内容相同,那么搜索引擎肯定会对其进行惩罚。我不知道这是否会让您感到担忧,但如果是的话,我建议您通过其他方式解决这个问题。也许您可以在客户端设置一个cookie,并让他们以其他方式选择CSS样式。 - Clueless
最终我根据不同的子域名设置了CSS。但是在本地测试时需要添加host文件条目,这真是一件麻烦事。 - Chase Seibert
1
请注意,命名空间是用于消除歧义的:即使视图名称冲突,也可以引用特定的视图对象(通过命名空间前缀实现)。您上面所要求的可能被认为是“歧义”:有意使用单个视图名称来引用两个不同的视图对象,具体取决于上下文。将这两个目标混合在一起对我来说并不符合Pythonic风格。 - Lutz Prechelt
4个回答

6

似乎没有直接的方法来实现它。我将使用你介绍的模板标签的类似解决方案,但我找到了一种更通用的方法。我利用了在url配置中可以传递可选参数的事实,因此您可以跟踪命名空间:

#urls.py
from django.conf.urls import defaults

urlpatterns = defaults.patterns('',
    defaults.url(r'^foo/', include('sub_urls', namespace='foo', app_name='myapp'), 
    kwargs={'namespace':'foo'}),
    defaults.url(r'^bar/', include('sub_urls', namespace='bar', app_name='myapp'),
    kwargs={'namespace':'bar'}),      
)

那也违反了DRY原则,但并不是很严重 :)

然后在您的视图中获取命名空间变量(sub_urls.py将是相同的):

#views.py
from django import shortcuts

def myvew(request, namespace):
    context = dict(namespace=namespace)
    return shortcuts.render_to_response('mytemplate.html', context)

稍后,您只需要使用一个简单的标签,将您的命名空间变量和视图名称传递给它即可:
#tags.py
from django import template
from django.core import urlresolvers

register = template.Library()

def namespace_url(namespace, view_name):
   return urlresolvers.reverse('%s:%s' % (namespace, view_name, args=args, kwargs=kwargs)))
register.simple_tag(namespace_url)

然后在模板中使用它(确保将您的视图名称作为字符串传递,而不是作为模板变量):

<!-- mytemplate.html -->
{% load tags %}
{% namespace_url namespace "view1"%}

顺便感谢你的提示,我一直在寻找这样的东西。


我看到这是一篇非常老的帖子,但是,dict(namespace=namespace)的计算代价比{'namespace': namespace}更高。使用“dis”查找原因! - explodes
对 namespace_url 函数进行微小更改:def namespace_url(namespace, view_name, *args, **kwargs): return urlresolvers.reverse('%s:%s' % (namespace, view_name), args=args, kwargs=kwargs)这将允许您支持 URL 参数。 - georgerw
谢谢。很好的补充。已添加到答案中。 - Torsten Engelbrecht
我认为在添加了 kwargs 后,可以并且应该从给定情况下的 include() 调用中删除 namespaceapp_name 参数。这样就可以再次合理地保持DRY(Don't Repeat Yourself)原则。 - Lutz Prechelt

3
我认为django现在无法实现这一点。请参考此帖子引用的Ticket 11559。我认为你试图做同样的事情——有效地将隐含参数传递给URL标记。
另外,假设sub_urls两次都来自同一个应用程序,您应该确保app_name在两种情况下相同。您只需要更改命名空间。

0

我意识到以下解决方案违反了DRY原则,因为您必须为foo和bar创建基本重复的URL配置文件,但是我认为它应该可以工作。

urls.py:

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^foo/', include('sub_urls_foo')),
    (r'^bar/', include('sub_urls_bar')),            
)

sub_urls_foo.py:

from django.conf.urls.defaults import patterns, url
from views import view1

urlpatterns = patterns('views',
    url(r'^(?P<view_id>\d+)/$', view1, 'view1_foo', {'namespace': 'view1_foo'})
)

sub_urls_bar.py:

from django.conf.urls.defaults import patterns, url
from views import view1

urlpatterns = patterns('views',
    url(r'^(?P<view_id>\d+)/$', view1, 'view1_bar', {'namespace': 'view1_bar'})
)

views.py:

from django.shortcuts import render_to_response

def view1(request, view_id, namespace):
    return render_to_response('view1.html', locals())

然后,对于模板,请使用这个:

{% url namespace 3 %}

我还没有测试在{% url %}标签的名称部分中使用变量的想法,但我认为应该可以工作。


那么你需要使用 {% url view1_bar 3 %} 或者 {% url view1_foo 3 %}?我真正想要的是能够使用 {% url view1 3 %},并且根据与当前URL匹配的命名空间来选择foo或bar。 - Chase Seibert
你说得对,我的例子行不通。我建立它的时候是在假定能够将模板变量传递给 {% url %} 标签的情况下进行的。但现在我已经读到这并不可行。所以这留给你两个选择。要么在视图中使用 reverse() 处理 URL 并将其通过上下文传递到模板中,要么创建一个自定义模板标签。 - Steven Potter

-1

这是我想出来的一个解决方案。

views.py:

from django.shortcuts import render_to_response
from django.template import RequestContext

def render_response_context(view, locals):
    request = locals["request"]
    app = "bar" if request.META["PATH_INFO"].lower().startswith("/bar") else "foo"
    return render_to_response(view, locals, 
        context_instance=RequestContext(request, current_app=app))

def view1(request, view_id):    
    return render_response_context('view1.html', locals())

view1.html:

{% load extras %}
{% namespace_url view1 3 %}

extras.py:

from django import template
from django.core.urlresolvers import reverse

register = template.Library()

@register.tag
def namespace_url(parser, token):
    tag_name, view_string, arg1 = token.split_contents()
    return NamespaceUrlNode(view_string, arg1)

class NamespaceUrlNode(template.Node):
    def __init__(self, view_string, arg1):
        self.view_string = view_string
        self.arg1 = arg1
    def render(self, context):
        return reverse("%s:%s" % (context.current_app, self.view_string), args=[self.arg1])

基本上,我确保始终将当前应用程序上下文作为“foo”或“bar”传递,这是通过手动查看请求URL来计算的。然后,我使用一个自定义标记,根据当前应用程序解析URL。

它不是非常通用;“foo”和“bar”是硬编码的,并且该标记只能接受一个参数。即使解决了这些问题,这似乎也是一种hack。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接