URL是Web服务的入口,用户通过浏览器发送过来的任何请求,都是发送到一个指定的URL地址,然后被响应。
在Django项目中编写路由,就是向外暴露我们接收哪些URL的请求,除此之外的任何URL都不被处理,也没有返回。通俗地理解,不恰当的形容,URL路由是你的Web服务对外暴露的API。
Django奉行DRY主义,提倡使用简洁、优雅的URL,没有.php
或.cgi
这种后缀,更不会单独使用0、2097、1-1-1928、00这样无意义的东西,让你随心所欲设计你的URL,不受框架束缚。
一、概述
URL路由在Django项目中的体现就是urls.py
文件,这个文件可以有很多个,但绝对不会在同一目录下。实际上Django提倡项目有个根urls.py
,各app下分别有自己的一个urls.py
,既集中又分治,是一种解耦的模式。
随便新建一个Django项目,默认会自动为我们创建一个/project_name/urls.py
文件,并且自动包含下面的内容,这就是项目的根URL:
"""mysite URL Configuration The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/1.11/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.conf.urls import url, include
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
"""
from django.conf.urls import url
from django.contrib import admin urlpatterns = [
url(r'^admin/', admin.site.urls),
]
前面一堆帮助性的文字,我们不用管,关键是默认导入了url和admin,然后有一条指向admin后台的url路径。
我们自己要编写的url路由,基本也是这个套路。
二、Django如何处理请求
当用户请求一个页面时,Django根据下面的逻辑执行操作:
-
决定要使用的根URLconf模块。通常,这是
ROOT_URLCONF
设置的值,但是如果传入的HttpRequest对象具有urlconf属性(由中间件设置),则其值将被用于代替ROOT_URLCONF
设置。通俗的讲,就是你可以自定义项目入口url是哪个文件! -
加载该模块并寻找可用的urlpatterns。 它是
django.conf.urls.url()
实例的一个列表。 - 依次匹配每个URL模式,在与请求的URL相匹配的第一个模式停下来。也就是说,url匹配是从上往下的短路操作,所以url在列表中的位置非常关键。
- 导入并调用匹配行中给定的视图,该视图是一个简单的Python函数(被称为视图函数),或基于类的视图。 视图将获得如下参数:
- 一个HttpRequest 实例。
- 如果匹配的正则表达式返回了没有命名的组,那么正则表达式匹配的内容将作为位置参数提供给视图。
- 关键字参数由正则表达式匹配的命名组组成,但是可以被
django.conf.urls.url()
的可选参数kwargs覆盖。
- 如果没有匹配到正则表达式,或者过程中抛出异常,将调用一个适当的错误处理视图。
三、简单示例
下面是一个简单的 URLconf:
from django.conf.urls import url from . import views urlpatterns = [
url(r'^articles/2003/$', views.special_case_2003),
url(r'^articles/([0-9]{4})/$', views.year_archive),
url(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive),
url(r'^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$', views.article_detail),
]
我们要编写的就是上面urlpatterns列表中的一条条url,每条url,都是urlpatterns列表的一个元素。先后顺序有重要关系,不能随意摆放。最后一条的末尾建议添加一个逗号。
urlpatterns中的每条正则表达式在第一次访问时被自动编译,因此其匹配速度是非常快的。
注意:
- 若要从URL中捕获一个值,只需要在它周围放置一对圆括号。
- 不需要添加前导的反斜杠,因为每个URL都有。 例如,应该是
^articles
而不是^/articles
。 - 每个正则表达式前面的'r'是可选的但是建议加上。它告诉Python这个字符串是“原始的” —— 字符串中任何字符都不应该转义。
根据上面的urlconf,下面是一些请求的例子,以及它们将匹配到的url:
-
/articles/2005/03/
将匹配列表中的第三个模式。Django将调用函数views.month_archive(request, '2005', '03')
。 -
/articles/2005/3/
不匹配任何URL模式,因为列表中的第三个模式要求月份是两个数字。 -
/articles/2003/
将匹配列表中的第一个模式不是第二个,因为模式按顺序从上往下匹配,第一个会首先被匹配。Django会调用函数views.special_case_2003(request)
-
/articles/2003
不匹配任何一个模式,因为每个模式都要求URL以一个斜杠结尾。 -
/articles/2003/03/03/
将匹配最后一个模式。Django将调用函数views.article_detail(request, '2003', '03', '03')
。
四、命名组
很多时候,我们需要获取URL中的一些片段,作为参数,传递给处理请求的视图。
上面的示例使用简单的、没有命名的正则表达式组(通过圆括号)来捕获URL中的值并以位置参数的形式传递给视图。
可以使用命名的正则表达式组来捕获URL中的值并以关键字参数传递给视图。
在Python的正则表达式中,命名组的语法是(?P<name>pattern)
,其中name是组的名称,pattern是要匹配的模式。
下面是以上URLconf使用命名组的重写:
from django.conf.urls import url from . import views urlpatterns = [
url(r'^articles/2003/$', views.special_case_2003),
url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive),
url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})/$', views.article_detail),
]
这个实现与前面的示例完全相同,只有一个细微的差别:捕获的值作为关键字参数而不是位置参数传递给视图函数。 像这样:
-
/articles/2005/03/
请求将调用views.month_archive(request, year='2005', month='03')
函数,而不是views.month_archive(request, '2005', '03')
。 -
/articles/2003/03/03/
请求将调用函数views.article_detail(request, year='2003', month='03', day='03')
。
在实际应用中,这让你的URLconf更加明晰且不容易产生参数顺序问题的错误。当然,这不是强制性的,也牺牲了一些简洁性。
针对命名组和非命名组:
- 如果有命名参数,则使用这些命名参数,忽略非命名参数。
- 否则,它将以位置参数传递所有的非命名参数。
五、URLconf匹配请求URL中的哪些部分
请求的URL被看做是一个普通的Python字符串,URLconf在其上查找并匹配。进行匹配时将不包括GET或POST请求方式的参数以及域名。
例如,在https://www.example.com/myapp/
的请求中,URLconf将查找myapp/
。
在https://www.example.com/myapp/?page=3
的请求中,URLconf也将查找myapp/
。
URLconf不检查使用何种HTTP请求方法,所有请求方法POST、GET、HEAD等都将路由到同一个URL的同一个视图。在视图中,才根据具体请求方法的不同,进行不同的处理。
六、URL中捕获的参数为字符串类型
每个捕获的参数都作为一个普通的Python字符串传递给视图,即便被捕获的‘100’看起来像个整数,但实际上是个字符串‘100’。 例如,下面这行URLconf中:
url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
传递给views.year_archive()
的year参数将是一个字符串,不是整数,即使[0-9]{4}
只匹配整数字符串。
七、指定视图参数的默认值
有一个小技巧,我们可以指定视图参数的默认值。 下面是一个URLconf和视图的示例:
# URLconf
from django.conf.urls import url from . import views urlpatterns = [
url(r'^blog/$', views.page),
url(r'^blog/page(?P<num>[0-9]+)/$', views.page),
] # View (in blog/views.py)
def page(request, num="1"):
# Output the appropriate page of blog entries, according to num.
...
在上面的例子中,两个URL模式指向同一个视图views.page
。但是第一个模式不会从URL中捕获任何值。 如果第一个模式匹配,page()函数将使用num参数的默认值"1"。 如果第二个模式匹配,page()将使用捕获的num值。
八、自定义错误页面
当Django找不到与请求匹配的URL时,或者当抛出一个异常时,将调用一个错误处理视图。错误视图包括400、403、404和500,分别表示请求错误、拒绝服务、页面不存在和服务器错误。它们分别位于:
- handler400 —— django.conf.urls.handler400。
- handler403 —— django.conf.urls.handler403。
- handler404 —— django.conf.urls.handler404。
- handler500 —— django.conf.urls.handler500。
这些值可以在根URLconf中设置。在其它app中的二级URLconf中设置这些变量无效。
Django有内置的HTML模版,用于返回错误页面给用户,但是这些403,404页面实在丑陋,通常我们都自定义错误页面。
首先,在根URLconf中额外增加下面的条目:
# URLconf
from django.conf.urls import url from . import views urlpatterns = [
url(r'^blog/$', views.page),
url(r'^blog/page(?P<num>[0-9]+)/$', views.page),
] # 增加的条目
handler400 = views.bad_request
handler403 = views.permission_denied
handler404 = views.page_not_found
handler500 = views.page_error
然后在,views.py文件中增加四个处理视图:
def page_not_found(request):
return render(request, '404.html') def page_error(request):
return render(request, '500.html') def permission_denied(request):
return render(request, '403.html') def bad_request(request):
return render(request, '400.html')
再根据自己的需求,创建404.html、400.html等四个页面文件,就可以了。