高级视图和URL配置
在第2章中,我们解释了Django的视图函数和URLconf的基础知识。 本章将更详细地介绍这两个框架中的高级功能。
URLconf提示和技巧
没有什么关于URLconf的“特殊” - 就像Django中的其他任何东西一样,它们只是Python代码。 您可以通过几种方式利用这一点,如下面的部分所述。
简化功能导入
考虑这个URLconf,它建立在第2章的例子上:
from django.conf.urls import include, url
from django.contrib import admin
from mysite.views import hello, current_datetime, hours_ahead
urlpatterns = [
url(r'^admin/', include(admin.site.urls)),
url(r'^hello/$', hello),
url(r'^time/$', current_datetime),
url(r'^time/plus/(\d{1,2})/$', hours_ahead),
]
如第2章所述,URLconf中的每个条目都包含与其相关的视图函数,直接作为函数对象传递。 这意味着有必要在模块的顶部导入视图功能。
但是随着Django应用程序的复杂性增长,它的URLconf也会增长,而保留这些导入可能是非常繁琐的。 (对于每个新的视图函数,都必须记住导入它,如果使用这种方法,导入语句会过长。)
通过导入视图模块本身,可以避免这种乏味。 这个URLconf示例等同于前一个:
from django.conf.urls import include, url
from . import views
urlpatterns = [
url(r'^hello/$', views.hello),
url(r'^time/$', views.current_datetime),
url(r'^time/plus/(d{1,2})/$', views.hours_ahead),
]
调试模式下的特殊外壳URL
说到动态构建urlpattern,你可能想利用这个技术来改变你的URLconf在Django调试模式下的行为。 为此,只需在运行时检查DEBUG设置的值,如下所示:
from django.conf import settings
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.homepage),
url(r'^(\d{4})/([a-z]{3})/$', views.archive_month),
]
if settings.DEBUG:
urlpatterns += [url(r'^debuginfo/$', views.debug),]
在这个例子中,URL / debuginfo /只有在DEBUG时才可用
设置设置为True。
命名组
上面的例子使用简单的,非命名的正则表达式组(通过圆括号)来捕获URL的位,并将它们作为位置参数传递给视图。
在更高级的用法中,可以使用命名正则表达式组来捕获URL位,并将它们作为关键字参数传递给视图。
在Python正则表达式中,命名正则表达式组的语法是(?P <name> pattern),其中name是组的名称,pattern是可以匹配的模式。
例如,假设我们的图书网站上有书评的列表,我们希望检索特定日期或日期范围的评论。
以下是一个URLconf示例:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^reviews/2003/$', views.special_case_2003),
url(r'^reviews/([0-9]{4})/$', views.year_archive),
url(r'^reviews/([0-9]{4})/([0-9]{2})/$', views.month_archive),
url(r'^reviews/([0-9]{4})/([0-9]{2})/([0-9]+)/$', views.review_detail),
]
注意:
- 要从URL中捕获值,只需在其周围放置括号即可。
- 没有必要添加一个前导斜杠,因为每个URL都有。 例如,它是^ reviews,而不是^/reviews。
- 每个正则表达式字符串前面的“r”是可选的,但建议使用。 它告诉Python一个字符串是“原始的” - 字符串中的任何内容都不应该被转义。
示例请求:
-
请求/reviews/ 2005/03 /将与列表中的第三项匹配。 Django会调用函数views.month_archive(request,'2005','03')。
-
/ reviews / 2005/3 /不会匹配任何URL模式,因为列表中的第三项需要两位数。
-
/ reviews / 2003 /会匹配列表中的第一个模式,而不是第二个模式,因为模式是按顺序测试的,而第一个模式是第一个要传递的测试。 随意利用排序来插入这样的特殊情况。
-
/ reviews / 2003将不匹配任何这些模式,因为每个模式都要求URL以斜杠结尾。
-
/ reviews / 2003/03/03 /将匹配最终模式。 Django会调用函数views.review_detail(request,'2003','03',
'03')。
这里是上面的例子URLconf,重写为使用命名组:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^reviews/2003/$', views.special_case_2003),
url(r'^reviews/(?P<year>[0-9]{4})/$', views.year_archive),
url(r'^reviews/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$',
views.month_archive),
url(r'^reviews/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})/$',
views.review_detail),
]
这和前面的例子完全一样,只有一个细微的差别:捕获的值被传递给查看函数而不是位置参数。
例如:
-
请求/ reviews / 2005/03 /会调用函数views.month_archive(request,year ='2005',month = '03'),而不是views.month_archive(request,'2005','03')。
-
请求/ reviews / 2003/03/03 /会调用函数views.review_detail(request,year ='2003',month = '03',day = '03')。
在实践中,这意味着你的URLconf稍微更明确一点,不太容易出现参数错误 - 你可以重新排列视图函数定义中的参数。 当然,这些好处是以简洁为代价的。 一些开发者发现命名组语法丑陋而且太冗长。
匹配/分组算法
下面是URLconf解析器遵循的关于正则表达式中的命名组与非命名组的算法:
- 如果有任何命名参数,它将使用这些参数,忽略非命名参数。
- 否则,它将传递所有非命名参数作为位置参数。
在这两种情况下,任何额外的关键字参数也将被传递给视图。
什么是URLconf搜索
URLconf按照正常的Python字符串搜索请求的URL。 这不包括GET或POST参数或域名。 例如,在对http://www.example.com/myapp/的请求中,URLconf将查找myapp /。 在对http://www.example.com/myapp/?page=3的请求中,URLconf将查找myapp /。 URLconf不查看请求方法。 换句话说,所有请求方法(POST,GET,HEAD等)都将被路由到相同URL的同一个函数。
捕获的参数始终是字符串
无论正则表达式匹配什么类型,每个捕获的参数都会以纯Python字符串的形式发送到视图。 例如,在这个URLconf行中:
url(r'^reviews/(?P<year>[0-9]{4})/$', views.year_archive),
...年份参数views.year_archive()将是一个字符串,而不是一个整数,即使[0-9] {4}只会匹配整数字符串。
指定查看参数的默认值
一个方便的技巧是为你的视图的参数指定默认参数。 这是一个URLconf的例子:
# URLconf
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^reviews/$', views.page),
url(r'^reviews/page(?P<num>[0-9]+)/$', views.page),
]
# View (in reviews/views.py)
def page(request, num="1"):
# Output the appropriate page of review entries, according to num.
在上面的例子中,这两个URL模式指向相同的view - views.page - 但是第一个模式并没有从URL中捕获任何东西。 如果第一个模式匹配,page()函数将使用其默认参数num,“1”。 如果第二个模式匹配,则page()将使用正则表达式捕获的任何num值。
性能
urlpatterns中的每个正则表达式在第一次被访问时被编译。 这使得系统非常快速。
关键字参数与位置参数
Python函数可以使用关键字参数或位置参数来调用 - 在某些情况下,可以同时使用。 在关键字参数调用中,您可以指定参数的名称以及传递的值。 在一个位置参数调用中,只需传递参数而不显式指定哪个参数匹配哪个值; 该协会隐含在论点的顺序中。
例如,考虑这个简单的功能:
def sell(item, price, quantity):
print "Selling %s unit(s) of %s at %s" % (quantity, item, price)
要用位置参数调用它,可以按照它们在函数定义中列出的顺序来指定参数:
sell('Socks', '$2.50', 6)
要使用关键字参数调用它,可以指定参数的名称和值。 以下声明是等同的:
sell(item='Socks', price='$2.50', quantity=6)
sell(item='Socks', quantity=6, price='$2.50')
sell(price='$2.50', item='Socks', quantity=6)
sell(price='$2.50', quantity=6, item='Socks')
sell(quantity=6, item='Socks', price='$2.50')
sell(quantity=6, price='$2.50', item='Socks')
最后,只要在关键字参数之前列出所有位置参数,就可以混合使用关键字和位置参数。 以下语句与前面的例子相同:
sell('Socks', '$2.50', quantity=6)
sell('Socks', price='$2.50', quantity=6)
sell('Socks', quantity=6, price='$2.50')
错误处理
当Django找不到与请求的URL匹配的正则表达式,或者引发异常时,Django将调用错误处理视图。 用于这些情况的视图由四个变量指定。 变量是:
- handler404
- handler500
- handler403
- handler400
对于大多数项目来说,默认值是可以满足的了,但是为他们设计一些自定义的值是可能的。 这些值可以在您的根URLconf中设置。 在其他URLconf中设置这些变量将不起作用。 值必须是可调用的,或者是代表完整的Python导入路径的字符串,该路径应该被调用来处理当前的错误情况。
包括其他URLconf
在任何时候,你的urlpatterns都可以“include”其他的URLconf模块。 这本质上是“roots”其他URL下的一组URL。 例如,下面是Django网站本身的URLconf的摘录。 它包括一些其他的URLconf:
from django.conf.urls import include, url
urlpatterns = [
# ...
url(r'^community/', include('django_website.aggregator.urls')),
url(r'^contact/', include('django_website.contact.urls')),
# ...
]
请注意,此示例中的正则表达式没有$(字符串尾匹配字符),但包含一个尾部斜线。 每当Django遇到include()时,它会截断与该点匹配的URL的任何部分,并将剩余的字符串发送到包含的URLconf以供进一步处理。 另一种可能是通过使用url()实例列表来包含额外的URL模式。 例如,考虑这个URLconf:
from django.conf.urls import include, url
from apps.main import views as main_views
from 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 url
from . 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, url
from . 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),
])),
]
捕获的参数
An included URLconf receives any captured parameters from parent URLconfs, so the fol\
lowing example is valid:
# In settings/urls/main.py
from django.conf.urls import include, url
urlpatterns = [
url(r'^(?P<username>\w+)/reviews/', include('foo.urls.reviews')),
]
# In foo/urls/reviews.py
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.reviews.index),
url(r'^archive/$', views.reviews.archive),
]
在上面的例子中,如预期的那样,捕获的“username”变量被传递给包含的URLconf。
传递额外的选项来查看函数
URLconf有一个钩子,可以让你传递额外的参数给你的视图函数,如Python字典。 django.conf.urls.url()函数可以接受一个可选的第三个参数,它应该是一个额外的关键字参数的字典传递给视图函数。 例如:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^reviews/(?P<year>[0-9]{4})/$',
views.year_archive,
{'foo': 'bar'}),
]
在这个例子中,对于/ reviews / 2005 /的请求,Django将调用views.year_archive(request,year ='2005',foo ='bar')。 在联合框架中使用这种技术将元数据和选项传递给视图(见第14章)。
处理冲突可能有一个URL模式捕获命名的关键字参数,并且还在其额外参数的字典中传递具有相同名称的参数。 发生这种情况时,将使用字典中的参数,而不是在URL中捕获的参数。
传递额外选项以包include()
同样,你可以传递额外的选项来include()。 当你向include()传递额外的选项时,包含的URLconf中的每一行都会被传递给额外的选项。 例如,这两个URLconf集在功能上是相同的:
设置1:
# main.py
from django.conf.urls import include, url
urlpatterns = [
url(r'^reviews/', include('inner'), {'reviewid': 3}),
]
# inner.py
from django.conf.urls import url
from mysite import views
urlpatterns = [
url(r'^archive/$', views.archive),
url(r'^about/$', views.about),
]
设置2:
# main.py
from django.conf.urls import include, url
from mysite import views
urlpatterns = [
url(r'^reviews/', include('inner')),
]
# inner.py
from django.conf.urls import url
urlpatterns = [
url(r'^archive/$', views.archive, {'reviewid': 3}),
url(r'^about/$', views.about, {'reviewid': 3}),
]
请注意,无论该行的视图是否实际接受这些选项都是有效的,总是会将其他选项传递给包含的URLconf中的每一行。出于这个原因,只有在确定包含的URLconf中的每个视图都接受了传递的额外选项时,该技术才有用。
反向解析URL
在Django项目上工作的一个共同需求是可以获得最终形式的URL,或者嵌入到生成的内容中(视图和资产URL,显示给用户的URL等),或者用于处理服务器上的导航流方(重定向等)
强烈希望避免对这些URL进行硬编码(一种费力,不可扩展且容易出错的策略),或者必须设计用于生成与由URLconf描述的设计并行的URL的特定机制,并且因此处于危险之中在某些时候生成陈旧的URL。换句话说,需要的是一个DRY机制。
除了其他优势之外,它还允许进行URL设计,而无需遍历项目源代码来搜索和替换过时的URL。我们可以获得的信息作为获取URL的起点是负责处理它的视图的标识(例如名称),必须参与查找正确URL的其他信息是类型(位置,关键字)和视图参数的值。
Django提供了一个解决方案,使得URL映射器是URL设计的唯一存储库。你用你的URLconf来提供它,然后可以在两个方向上使用它:
- 从user/broswer请求的URL开始,它调用正确的Django视图,提供它可能需要的任何参数以及从URL中提取的值。
- 从相应的Django视图的标识以及传递给它的参数的值开始,获取关联的URL。
第一个是我们前面讨论的用法。 第二个是所谓的URL反向解析,反向URL匹配,反向URL查询或者简单的URL反转。
Django提供了用于执行URL反转的工具,以匹配需要URL的不同图层:
- 在模板中:使用url模板标签。
- 在Python代码中:使用django.core.urlresolvers.reverse()函数。
- 在与处理Django模型实例的URL相关的更高级代码中:get_absolute_url()方法。
例子
再考虑一下这个URLconf条目:
from django.conf.urls import url
from . import views
urlpatterns = [
#...
url(r'^reviews/([0-9]{4})/$', views.year_archive,
name='reviews-year-archive'),
#...
]
根据这种设计,对应于年份nnnn的档案的URL是/ reviews / nnnn /。 您可以通过使用以下代码获取这些代码:
<a href="{% url 'reviews-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 'reviews-year-archive' yearvar %}">{{
yearvar }} Archive</a></li>
{% endfor %}
</ul>
或者在Python代码中:
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
def redirect_to_year(request):
# ...
year = 2012
# ...
return HttpResponseRedirect(reverse('reviews-year-archive', args=(year,)))
如果出于某种原因,决定应该更改发布年度审阅档案的内容的URL,那么您只需要更改URLconf中的条目。在某些情况下,视图具有一般性,URL和视图之间可能存在多对一的关系。对于这些情况,视图名称不是一个足够好的标识符。阅读下一节了解Django为此提供的解决方案。
命名URL模式
为了执行网址反转,您需要使用上述示例中的命名网址格式。用于URL名称的字符串可以包含您喜欢的任何字符。您不限于有效的Python名称。当您为URL模式命名时,请确保使用不太可能与其他应用程序的名称选择冲突的名称。如果您调用您的URL模式注释,而另一个应用程序执行相同的操作,则不能保证使用此名称时哪个URL将被插入到您的模板中。在您的URL名称上加上一个前缀(可能是从应用程序名称中派生出来的),将会减少碰撞的可能性。我们推荐像myapp-comments而不是comments。
URL命名空间
即使不同的应用程序使用相同的URL名称,URL名称空间也允许您唯一地反转命名的URL模式。 第三方应用程序总是使用名称空间的URL是一个好习惯。 同样,如果部署应用程序的多个实例,它也允许您反向URL。 换句话说,由于单个应用程序的多个实例将共享命名的URL,命名空间提供了一种方法来区分这些命名的URL。
正确使用URL命名空间的Django应用程序可以为特定的站点多次部署。 例如,
django.contrib.admin有一个AdminSite类,允许您轻松部署多个管理员实例。 一个URL命名空间有两部分,都是字符串:
- 应用程序名称空间 这描述了正在部署的应用程序的名称。 每个应用程序的每个实例将具有相同的应用程序名称空间。 例如,Django的管理应用程序具有可预测的应用程序名称空间admin。
- 实例名称空间 这标识了应用程序的特定实例。 实例名称空间在整个项目中应该是唯一的。 但是,实例名称空间可以与应用程序名称空间相同。 这用于指定应用程序的默认实例。 例如,默认的Django管理员实例有一个名为admin的实例。
命名空间的URL使用“:”运算符指定。 例如,管理应用程序的主索引页面使用“admin:index”引用。 这表示“admin”的命名空间,以及“index”的命名URL。
命名空间也可以嵌套。 指定的URL成员:reviews:index会在名称空间“reviews”中查找名为“index”的模式,它本身在顶级名称空间“members”中定义。
逆向命名空间的URL
当给定命名空间URL(例如“reviews:index”)来解决时,Django将完全限定的名称分割成部分,然后尝试以下查找:
-
首先,Django查找匹配的应用程序名称空间(在本例中为“reviews”)。这将产生该应用程序的实例列表。
-
如果定义了当前的应用程序,Django将查找并返回该实例的URL解析器。当前应用程序可以被指定为请求中的一个属性。期望具有多个部署的应用程序应该在正在处理的请求上设置current_app属性。
-
当前的应用程序也可以手动指定为reverse()函数的参数。
-
如果没有当前的应用程序。 Django寻找一个默认的应用程序实例。默认应用程序实例是具有与应用程序名称空间相匹配的实例名称空间的实例(在本例中是名为“reviews”的评论实例)。
-
如果没有默认的应用程序实例,Django将选择应用程序的最后部署的实例,无论它的实例名称是什么。
-
如果提供的名称空间与步骤1中的应用程序名称空间不匹配,则Django将尝试直接查找名称空间作为实例名称空间。
如果存在嵌套命名空间,则对命名空间的每个部分重复这些步骤,直到只有视图名称未解析。 视图名称将被解析成已找到的命名空间中的URL。
URL命名空间和包含的URLconf
包含的URLconf的URL命名空间可以用两种方式指定。 首先,当你构造你的URL模式时,你可以提供应用程序和实例命名空间作为include()的参数。 例如:
url(r'^reviews/', include('reviews.urls', namespace='author-reviews', app_name='revie\
ws')),
这将把reviews.urls中定义的URL包含到应用程序命名空间的“reviews”中,其中实例命名空间为“author-reviews”。 其次,你可以包含一个包含嵌入式命名空间数据的对象。 如果include()一个url()实例列表,则包含在该对象中的URL将被添加到全局名称空间中。 但是,您也可以include()一个3元组,其中包含:
(<list of url() instances>, <application namespace>, <instance namespace>)
例如:
from django.conf.urls import include, url
from . import views
reviews_patterns = [
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^(?P<pk>\d+)/$', views.DetailView.as_view(), name='detail'),
]
url(r'^reviews/', include((reviews_patterns, 'reviews', 'author-reviews'))),
这将包括提名的URL模式到给定的应用程序和实例名称空间中。 例如,Django管理员作为AdminSite的实例进行部署。 AdminSite对象具有urls属性:一个3元组,包含相应管理站点中的所有模式,以及应用程序名称空间“admin”和admin实例的名称。 当你部署一个管理员实例时,这个urls属性是你include()到你的项目的urlpatterns。
一定要传递一个元组来include()。 如果你只传递三个参数:include(reviews_patterns,'reviews','author-reviews'),Django不会抛出错误,但是由于include()的签名,“reviews”将是实例名称空间和作者 'reviews'将是应用程序名称空间,而不是反之。
下一步是什么?
本章为视图和URL配置提供了许多高级技巧和窍门。 接下来,在第8章中,我们将对Django的模板系统给予进一步的处理。