django-url调度器-初级篇

时间:2021-07-13 16:23:39

  Django 遵从 MVC 模型,并将其特色化为 MTV 模型。模型的核心是通过用户访问的 url 来指向处理的函数,而函数处理后返回相应的结果。所以url决定了用户访问的入口,另外表单处理的提交地址也需要指定的url。url是所有功能的入口,所以url的编写就变得非常重要。

  Django 的 url 编写涉及了 python 的 re 模块,也就是正则表达式,这部分内容需要提前掌握。

  本篇的内容将结合官方的1.8.2的文档进行说明,如果有说明不清的地方可以相应参照官方的文档,我这里提供一个进行中文翻译后的:戳这里

  在进行编写的说明前,我先介绍一下django是如何处理一个请求的:

  1.用户发来一个请求。Django将用户的请求报文等信息封装成HttpRequest对象,然后经过各种中间件处理后,抵达 url 模块进行处理函数的选择。

  2.Django要决定使用哪个模块用作匹配,这个通常由 settings.py 中的 ROOT_URLCONF 的值决定,例如:ROOT_URLCONF = 'test_web.urls' 。就表示将使用 test_web(项目同名app) 这个 app 下的 urls 这个模块文件( 这个模块也称为url的根模块,可修改 )。但如果进来的HttpRequest对象具有一个 urlconf  属性(通过中间件 request processing  设置),则使用这个值来替换 ROOT_URLCONF  设置。(可能会存在版本差异)

  3.Django 加载该 Python 模块并寻找可用的 urlpatterns 变量。它是一个 Python 列表,里面的元素是 django.conf.urls.url() 的实例。

  4.Django 按照正则匹配的方式,依次匹配每个URL 模式,在第一个与请求的URL 匹配的地方停下来(下面也符合的会被忽视)。

  5.一旦其中的一个正则表达式匹配上,Django 将导入并调用给出的视图,它是一个简单的Python 函数(或者一个基于类的视图)。视图将获得如下参数:

    a)  一个HttpRequest 实例(这也是为什么 view 函数的第一个参数要是 request,该实例封装了所有的 http 请求报文的信息)

    b) 如果正则匹配的 url 中使用了括号分组,但却没有为分组进行命名,则使用位置参数的模式为view函数传参。

    c) 如果是命名的分组,则使用关键字传参的方式。但是可以被django.conf.urls.url()的可选参数kwargs覆盖。

  6.如果没有匹配到正则表达式,或者如果过程中抛出一个异常,Django 将调用一个适当的错误处理视图。

  7.函数处理完毕,返回处理后的结果。

 


 

官方手册的例子:

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),
]

分析:

  1.若要从 URL 中捕获一个值,只需要在它周围放置一对圆括号。(即正则中的分组匹配)

  2.不需要添加一个前导的反斜杠,因为每个URL 都有。例如,应该是^articles 而不是 ^/articles。(django自动在域名后添加了/,这个默认行为可以进行改写)

  3.每个正则表达式前面的'r' 是可选的但是建议加上。它告诉Python 这个字符串是“原始的” —— 字符串中任何字符都不应该转义。参见Dive Into Python 中的解释。

 

一些请求的例子:

  /articles/2005/03/ 请求将匹配列表中的第三个模式。Django 将调用函数views.month_archive(request, '2005', '03')。

     /articles/2005/3/ 不匹配任何URL 模式,因为列表中的第三个模式要求月份应该是两个数字。

     /articles/2003/ 将匹配列表中的第一个模式不是第二个,因为模式按顺序匹配,第一个会首先测试是否匹配。请像这样*插入一些特殊的情况来探测匹配的次序。

     /articles/2003 不匹配任何一个模式,因为每个模式要求URL 以一个斜线结尾。

   /articles/2003/03/03/ 将匹配最后一个模式。Django 将调用函数views.article_detail(request, '2003', '03', '03')。

 


 

命名组

  上面的例子中,虽然实现了参数的传递,但是并没有我们熟知的关键字传参。要使用关键字传参,只有为分组命名就行了。例如:(?P<name>\d+),这个分组表示匹配一个或多个任意的数字,并以 name = 匹配到的数字,如 name = '123' 的方式传给view 函数。

  但是,要注意一点,url 捕获的所以参数都是字符串类型,虽然 \d 在正则中表示匹配数字,但传参的时候,传的是字符串。例如,正则捕获的 123 ,但传参是 '123'。

 

官方例子

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 解析器使用的算法,针对正则表达式中的命名组和非命名组:

  1.如果有命名参数,则使用这些命名参数,忽略非命名参数。

  2.否则,它将以位置参数传递所有的非命名参数。

  根据传递额外的选项给视图函数(下文),这两种情况下,多余的关键字参数也将传递给视图。

 

注意:

  1.一条 url 中,分组不能同名,否则报错。(初步测试)

  2.所谓的有命名分组就使用命名参数,忽略非命名参数是下面这样的:

url(r'add/(\d+)/(?P<num1>\d+)/(?P<num2>\d+)/', add, name='add'),

 

def add(request, num1, num2):
    num1 = int(num1)
    num2 = int(num2)
    return HttpResponse(num1 + num2)

 

  在这里,request 是 Httprequest,是 django 自动传递的,我们只要看 num1 和 num2 两个参数就可以的。这里我的处理函数除了 request 之外还需要 2 个参数,但是我在 url 中使用了三个分组,理论上应该会捕获 3 个参数。

  但是,当我访问 http://127.0.0.1:8000/add/666/321/123/  时,我得到的结果是:

  django-url调度器-初级篇

  另外,因为捕获的参数是字符串类型, 所以我使用了 int() 函数进行类型转换,如果不这样的话:

def add(request, num1, num2):

    return HttpResponse(num1 + num2)

 

得到的结果就是:

django-url调度器-初级篇

  字符串的拼接,所以说,捕获的参数都是字符串

  好了,回到正题。我的函数需要除了 request 之外的 2 个参数,但是我在 url 中捕获了三个,得到的结果是后面的两个命名组的参数,也就是多出来的被忽略了

  这就是所谓的当命名组存在的时候,非命名组会被忽略。

  但是,我的 url 是这样写的:

 url(r'add/(\d+)/(?P<num2>\d+)/', add, name='add'),

 

  我希望前面的未命名组按位置传给 num1 而 num2 使用关键字参数。

  现在,我访问: http://127.0.0.1:8000/add/321/123/ 

django-url调度器-初级篇

  报错的信息是,函数需要 3 个参数,而我们给了 2 个,其中,去除自动传递的 request,也就是我们只捕获到了 1 个参数。

  此时,再看看这句活:当命名组存在时,会忽略非命名组

  因为非命名组捕获的参数被忽略了,所以才导致我们少传了一个参数。

  这个时候,你应该知道这里的忽略是什么意思了吧。

 

 


 

URLconf 在什么上查找

  请求的URL被看做是一个普通的Python 字符串, URLconf在其上查找并匹配。进行匹配时将不包括GET或POST请求方式的参数以及域名

  例如,http://www.example.com/myapp/请求中,URLconf 将查找myapp/。

    在http://www.example.com/myapp/?page=3 请求中,URLconf 仍将查找myapp/。

  URLconf 不检查使用了哪种请求方法。换句话讲,所有的请求方法 —— 即,对同一个URL的无论是POST请求、GET请求、或HEAD请求方法等等 —— 都将匹配到相同的函数。

  但是,我们能限制某个函数只能由某种方式访问,但这里不是 url 这里的内容了,以后再讲。

 


 

捕获的参数永远是字符串

  关于这句话我在刚才的 num1 和 num2 相加的示例中已经演示过了,这里记住这句话就行了。

 


 

指定视图参数的默认值

  这并不是 django 特有的用法,这是 python中 的函数默认参数的内容。但是django这里能处理一些特殊的情况:

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),
]

 

def page(request, num="1"):
    ......
    return .....

 

  在上面的例子中,两个URL模式指向同一个视图views.page —— 但是第一个模式不会从URL 中捕获任何值。如果第一个模式匹配,page() 函数将使用num参数的默认值"1"。如果第二个模式匹配,page() 将使用正则表达式捕获的 num 值。

 


性能

  urlpatterns 中的每个正则表达式在第一次访问它们时被编译。这使得系统相当快。

 


urlpatterns 变量的语法

  urlpatterns 应该是一个 python 列表,列表中存放的都是 url() 的实例。

 


 

错误处理

  当Django 找不到一个匹配请求的 URL 的正则表达式时,或者当抛出一个异常时,Django 将调用一个错误处理视图。

    这些情况发生时使用的视图通过4个变量指定。它们的默认值应该满足大部分项目,但是通过赋值给它们以进一步的自定义也是可以的。

  完整的细节请参见自定义错误视图。

  这些值可以在你的根URLconf 中设置。在其它URLconf 中设置这些变量将不会生效果。

  它们的值必须是可调用的或者是表示视图的Python 完整导入路径的字符串,可以方便地调用它们来处理错误情况。

这些值是:

    handler404 —— 参见django.conf.urls.handler404。

    handler500 —— 参见django.conf.urls.handler500。

    handler403 —— 参见django.conf.urls.handler403。

    handler400 —— 参见django.conf.urls.handler400。

 

 关于这部分我目前也在研究中,以后研究出结果再分享出来。