在初级篇中,我们接触了:
1.url 的简单编写
2.两种传参的方式
3.捕获的参数总是字符串
4.为视图设置默认参数
……
在中级篇中将更进一步。
包含其它的URLconfs
当网站非常大的时候,将所有的url都写在一个url模块中会非常的臃肿,且后期不便于维护。此时,就可以使用包含的方式将部分的url放在另一个url模块中。最常见的就是每个app的url都进行分离。
官方代码示例:
from django.conf.urls import include, url urlpatterns = [ # ... snip ... url(r'^community/', include('django_website.aggregator.urls')), url(r'^contact/', include('django_website.contact.urls')), # ... snip ...]
注意,这个例子中的正则表达式没有包含$(字符串结束匹配符),但是包含一个末尾的斜杠。每当 Django 遇到 include()(django.conf.urls.include())时,它会去掉 URL 中匹配的部分并将剩下的字符串发送给包含的 URLconf 做进一步处理。
另外一种包含其它URL 模式的方式是使用一个url() 实例的列表。例如,请看下面的URLconf:
from django.conf.urls import include, url from apps.main import views as main_viewsfrom credit import views as credit_views extra_patterns = [ url(r'^reports/(?P<id>[0-9]+)/$', credit_views.report), url(r'^charge/$', credit_views.charge),
] urlpatterns = [ url(r'^$', main_views.homepage), url(r'^help/', include('apps.help.urls')), url(r'^credit/', include(extra_patterns)),
]
在这个例子中,/credit/reports/ URL 将被 credit.views.report() 这个Django 视图处理。
这种方法可以用来去除 URLconf 中的冗余,其中某个模式前缀被重复使用。例如,考虑这个 URLconf:
from django.conf.urls import urlfrom . import views urlpatterns = [ url(r'^(?P<page_slug>[w-]+)-(?P<page_id>w+)/history/$', views.history), url(r'^(?P<page_slug>[w-]+)-(?P<page_id>w+)/edit/$', views.edit), url(r'^(?P<page_slug>[w-]+)-(?P<page_id>w+)/discuss/$', views.discuss), url(r'^(?P<page_slug>[w-]+)-(?P<page_id>w+)/permissions/$', views.permissions), ]
我们可以改进它,通过只声明共同的路径前缀一次并将后面的部分分组:
from django.conf.urls import include, urlfrom . import views urlpatterns = [ url(r'^(?P<page_slug>[w-]+)-(?P<page_id>w+)/', include([ url(r'^history/$', views.history), url(r'^edit/$', views.edit), url(r'^discuss/$', views.discuss), url(r'^permissions/$', views.permissions), ])), ]
下面,我们总结一下使用 include 能达到什么效果:
1.分离 url ,例如将 app 相关的 url 都移到对应的 app 目录下,这样逻辑更清晰,也更易维护。
2.去除 url 中的冗余。
当然,还有更高级的用法,例如:命名空间,反向解析等。这些等到高级篇再进行讨论。
包含之后的参数传递
包含的 URLconf 会收到来自父URLconf 捕获的任何参数(也就是说捕获到的参数是向下传递的),所以下面的例子是合法的:
# In settings/urls/main.py from django.conf.urls import include, url urlpatterns = [ url(r'^(?P<username>w+)/blog/', include('foo.urls.blog')), ] # In foo/urls/blog.py from django.conf.urls import url from . import views urlpatterns = [ url(r'^$', views.blog.index), url(r'^archive/$', views.blog.archive), ]
在上面的例子中,捕获的 "username" 变量将被如期传递给 include() 指向的 URLconf。
嵌套的参数
正则表达式允许嵌套参数,Django 将解析它们并传递给视图。当反查时,Django 将尝试填满所有外围捕获的参数,并忽略嵌套捕获的参数。
例如下面的 URL 模式,它带有一个可选的 page 参数:
from django.conf.urls import url urlpatterns = [ url(r'blog/(page-(d+)/)?$', blog_articles), # bad url(r'comments/(?:page-(?P<page_number>d+)/)?$', comments), # good
]
两个模式都使用嵌套的参数,其解析方式是:例如 blog/page-2/ 将匹配 blog_articles 并带有两个位置参数 page-2/ 和 2。(也就是凡是分组都会传递参数,顺序由内层到外层)
而在正在表达式中 (?:....)虽然类似一个分组,但并不是分组。
所以,第二个 comments 的模式将匹配 comments/page-2/ 并带有一个值为 2 的关键字参数 page_number。这个例子中外围参数是一个不捕获的参数(?:...)。
blog_articles 视图需要最外层捕获的参数来反查,在这个例子中是 page-2/ 或者没有参数,而 comments 可以不带参数或者用一个 page_number 值来反查。
嵌套捕获的参数使得视图参数和URL 之间存在强耦合,正如 blog_articles 所示:视图接收URL(page-2/)的一部分,而不只是视图所要的值(通常我只需要知道一个页码,也就是这里的 2 就行了)。这种耦合在反查时更加显著,因为反查视图时我们需要传递 URL 的一个片段而不只是 page 的值。
总结:尽量不要捕获不需要的参数,因为这样不仅在函数中需要额外的处理,而且在进行各种反向查询的时候也会困难些。
传递额外的选项给视图函数
URLconfs 具有一个钩子,让你传递一个Python 字典作为额外的参数传递给视图函数。
django.conf.urls.url() 函数可以接收一个可选的第三个参数,它是一个字典,表示想要传递给视图函数的额外关键字参数。
例如:
from django.conf.urls import urlfrom . import views urlpatterns = [ url(r'^blog/(?P<year>[0-9]{4})/$', views.year_archive, {'foo': 'bar'}), ]
在这个例子中,对于/blog/2005/请求,Django 将调用views.year_archive(request, year='2005', foo='bar')。
但是这样会出现一定的冲突,当 URL 模式捕获的命名关键字参数和在字典中传递的额外参数具有相同的名称时,将使用字典中的参数而不是URL 中捕获的参数。(也就是说字典中的参数优先级更高。)
传递额外的选项给include
类似地,你可以传递额外的选项给include()。当你传递额外的选项给include() 时,被包含的URLconf 的每一行将被传递这些额外的选项。
也就是说在 include 中传递的额外参数将传给所有被包含的 url。
设置一: # main.py from django.conf.urls import include, url urlpatterns = [ url(r'^blog/', include('inner'), {'blogid': 3}),
] # inner.pyfrom django.conf.urls import urlfrom mysite import views urlpatterns = [ url(r'^archive/$', views.archive), url(r'^about/$', views.about),
] 设置二: # main.py from django.conf.urls import include, urlfrom mysite import views urlpatterns = [ url(r'^blog/', include('inner')),
] # inner.pyfrom django.conf.urls import url urlpatterns = [ url(r'^archive/$', views.archive, {'blogid': 3}), url(r'^about/$', views.about, {'blogid': 3})
,]
但是,如果你不确定你的所有的相关视图函数都需要这个额外的参数的话,将会导致参数传递错误,例如多传了参数。而发生这种情况时,毫无疑问会发生报错。所以在使用这个功能的时候要足够的谨慎。你要知道你做了什么。
URL 的反向解析
反向解析是做什么的?反向解析,就是在模板中,或 views 函数中进行 url 的获取或者重定向到指定页面的时候,可以更加灵活的写入目标 url 而不用硬编码。
例如,在没有使用反向解析之前:
<a href="/xxx/xxx/">测试</a> #模板中 def xxx(request): #视图函数需要重定向时 ..... return HttpResponseRedirect('/xxx/xxx/')
如果此时处于业务需要,想要改动 url 时,那么代码中所以用到的地方就都需要改。如果此时用的这个 url 的地方有 N 个,就意味着要改动 N 次。此时,你可能想死的心都有了。为了延长程序员的寿命,反向解析就显得非常重要了。
Django 通过为指定的url命名的方式,来代替硬编码,而为了解决命名重复的问题,又引入了命名空间,下面逐一介绍。
例子:
from django.conf.urls import url from . import views urlpatterns = [ #... url(r'^articles/([0-9]{4})/$', views.year_archive, name='news-year-archive'), #...]
在这里,添加了 name 这个属性,并将其赋值为'news-year-archive'。这就是为这个 url 进行了命名。
在进行命名了以后,就可以这样写:
<a href="{% url ‘new-year-archive’}>测试</a> #模板中 from django.core.urlresolvers import reverse def xxx(request): #视图函数 ...... return HttpResponseRedirect(reverse('news-year-archive',))
这里的reverse()函数的作用是进行反向解析,以直接访问其他视图函数。
下面来分析一下这个函数要怎么用:
reverse(viewname, urlconf=None, args=None, kwargs=None, current_app=None)
这里接收 5 个参数,下面来看看这些参数都是做什么的:
viewname:是一个字符串,可以是一个Python路径视图对象(废弃),一个URL模式名称( reverse('news_archive') ),或可调用视图对象( from news import views /reverse(views.archive) )。
urlconf : reverse 在其内部是这样处理的: if urlconf is None: urlconf = get_urlconf() ,那我们去看看get_urlconf()是做什么的。这时,我们看到这样的一句说明:
def get_urlconf(default=None): """ Returns the root URLconf to use for the current thread if it has been changed from the default one. """ return getattr(_urlconfs, "value", default)
返回根 url 模块的。
也就是说这个属性决定此次反向解析使用哪个 url 模块。默认是当前的根 url 模块
args : 用于传参,也就是说方向解析到指定的 view 函数后,并传递了参数,可以是元组或列表,表示按照顺序进行位置传参。
kwargs:也是用于传参的,不同的是,这里是一个字典,传参时使用关键字传参的方式。
current_app :参数允许您提供一个提示解析器指示应用程序当前执行的视图所属(所在app)。这个current_app参数作为一个提示,根据名称空间URL解决策略,来解决应用程序名称空间URL在特定的应用程序实例。
注意:
若没有找到匹配的对象,则抛出 NoReverseMatch 异常。
一般而言几乎所有的正则都能逆向解析,但是目前并不能匹配含有选择符(|)的正则。除此之外几乎可以很轻松得逆向解析,但这种逆向解析是不可逆的。
反向解析Python路径的能力,如反向(“news.views.archive”),已被弃用。(在1.8中)
官方手册例子:
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> <ul> {% for yearvar in year_list %} <li><a href="{% url 'news-year-archive' yearvar %}">{{ yearvar }} Archive</a></li> {% endfor %} </ul>
传递多个参数时用空格隔开:
{% url 'add' 123 321 %} {% url 'add' num1=123 num2=321 %}
在Python 代码中,这样使用:
rom django.core.urlresolvers import reverse from django.http import HttpResponseRedirect def redirect_to_year(request): # ... year = 2006 # ... return HttpResponseRedirect(reverse('news-year-archive', args=(year,)))
如果出于某种原因决定按年归档文章发布的URL应该调整一下,那么你将只需要修改URLconf 中的内容。