django URL反向解析和命名空间
一、反向解析URL
在实际的Django项目中,经常需要获取某条URL,为生成的内容配置URL链接。
比如,我要在页面上展示一列文章列表,每个条目都是个超级链接,点击就进入该文章的详细页面。
现在我们的urlconf是这么配置的:^post/(?P<id>\d+)
。
在前端中,这就需要为HTML的<a>
标签的href属性提供一个诸如http://www.xxx.com/post/3
的值。其中的域名部分,Django会帮你自动添加无须关心,我们关注的是post/3
。
此时,一定不能硬编码URL为post/3
,那样费时、不可伸缩,而且容易出错。试想,如果哪天,因为某种原因,需要将urlconf中的正则改成^entry/(?P<id>\d+)
,为了让链接正常工作,必须修改对应的herf属性值,于是你去项目里将所有的post/3
都改成entry/3
吗?显然这是不行的!
我们需要一种安全、可靠、自适应的机制,当修改URLconf中的代码后,无需在项目源码中大范围搜索、替换失效的硬编码URL。
为了解决这个问题,Django提供了一种解决方案,只需在URL中提供一个name参数,并赋值一个你自定义的、好记的、直观的字符串。
通过这个name参数,可以反向解析URL、反向URL匹配、反向URL查询或者简单的URL反查。
在需要解析URL的地方,对于不同层级,Django提供了不同的工具用于URL反查:
-
在模板语言中:使用
url
模板标签。(也就是写前端网页时) -
在Python代码中:使用
reverse()
函数。(也就是写视图函数等情况时) -
在更高层的与处理Django模型实例相关的代码中:使用
get_absolute_url()
方法。(也就是在模型model中)
范例:
考虑下面的URLconf:
from django.conf.urls import url
from . import views
urlpatterns = [
#...
url(r'^articles/([0-9]{4})/$', views.year_archive, name='news-year-archive'),
#...
]
某一年nnnn对应的归档的URL是/articles/nnnn/
。
可以在模板的代码中使用下面的方法获得它们:
<a href="{% url 'news-year-archive' 2012 %}">2012 Archive</a> # 注意模版语言的用法,注意参数的传递方法
{# Or with the year in a template context variable: #}
<ul>
{% for yearvar in year_list %}
<li><a href="{% url 'news-year-archive' yearvar %}">{{ yearvar }} Archive</a></li>
{% endfor %}
</ul>
在Python代码中,这样使用:
from django.urls import reverse
from django.http import HttpResponseRedirect
def redirect_to_year(request):
# ...
year = 2006
# ...注意参数的传递方法
return HttpResponseRedirect(reverse('news-year-archive', args=(year,)))
其中,起到核心作用的是我们通过name='news-year-archive'
为那条url起了一个可以被引用的名称。
URL名称name使用的字符串可以包含任何你喜欢的字符,但是过度的放纵有可能带来重名的冲突,比如两个不同的app,在各自的urlconf中为某一条url取了相同的name,这就会带来麻烦。为了解决这个问题,又引出了下面的命名空间。
二、URL命名空间
URL命名空间可以保证反查到唯一的URL,即使不同的app使用相同的URL名称。
第三方应用始终使用带命名空间的URL是一个很好的做法。
类似地,它还允许你在一个应用有多个实例部署的情况下反查URL。 换句话讲,因为一个应用的多个实例共享相同的命名URL,命名空间提供了一种区分这些命名URL 的方法。
实现命名空间的做法很简单,在urlconf文件中添加app_name = 'polls'
和namespace='author-polls'
这种类似的定义。
范例:
以前面的polls应用的两个实例为例子:'publisher-polls' 和'author-polls'。
假设我们已经在创建和显示投票时考虑了实例命名空间的问题,代码如下:
urls.py
from django.conf.urls import include, url
urlpatterns = [
url(r'^author-polls/', include('polls.urls', namespace='author-polls')),
url(r'^publisher-polls/', include('polls.urls', namespace='publisher-polls')),
]
polls/urls.py
from django.conf.urls import url
from . import views
app_name = 'polls'
urlpatterns = [
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^(?P<pk>\d+)/$', views.DetailView.as_view(), name='detail'),
...
]
如果当前的app实例是其中的一个,例如我们正在渲染实例'author-polls'中的detail视图,'polls:index'将解析到'author-polls'实例的index视图。
根据以上设置,可以使用下面的查询:
在基于类的视图的方法中:
reverse('polls:index', current_app=self.request.resolver_match.namespace)
和在模板中:
{% url 'polls:index' %}
如果没有当前app实例,例如如果我们在站点的其它地方渲染一个页面,'polls:index'将解析到polls注册的最后一个app实例空间。 因为没有默认的实例(命名空间为'polls'的实例),将使用注册的polls 的最后一个实例。 这将是'publisher-polls',因为它是在urlpatterns中最后一个声明的。
三、URL命名空间和include的URLconf
可以通过两种方式指定include的URLconf的应用名称空间。
第一种
在include的URLconf模块中设置与urlpatterns属性相同级别的app_name
属性。必须将实际模块或模块的字符串引用传递到include(),而不是urlpatterns本身的列表。
polls/urls.py
from django.conf.urls import url
from . import views
app_name = 'polls'
urlpatterns = [
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^(?P<pk>\d+)/$', views.DetailView.as_view(), name='detail'),
...
]
urls.py
from django.conf.urls import include, url
urlpatterns = [
url(r'^polls/', include('polls.urls')),
]
此时,polls.urls中定义的URL将具有应用名称空间polls。
第二种
include一个包含嵌套命名空间数据的对象。如果你include()一个url()实例的列表,那么该对象中包含的URL将添加到全局命名空间。 但是,你也可以include()一个2元组,其中包含:
(<list of url() instances>, <application namespace>)
例如:
from django.conf.urls import include, url
from . import views
polls_patterns = ([
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^(?P<pk>\d+)/$', views.DetailView.as_view(), name='detail'),
], 'polls')
urlpatterns = [
url(r'^polls/', include(polls_patterns)),
]
这将include指定的URL模式到给定的app命名空间。
可以使用include()的namespace参数指定app实例命名空间。如果未指定,则app实例命名空间默认为URLconf的app命名空间。