Django是如何处理一个请求
1、Django确定要使用的根URLconf模块。通常,这是ROOT_URLCONF
设置的值,但是如果传入 HttpRequest
对象具有urlconf
属性(由中间件设置),则将使用其值代替 ROOT_URLCONF
设置。
2、Django加载该Python模块并查找变量 urlpatterns
。这是django.urls.path()
和/或django.urls.re_path()
实例的Python列表。
3、Django按顺序运行每个URL模式,并在与请求的URL匹配的第一个停止。
4、一旦其中一个URL模式匹配,Django就会导入并调用给定的视图,该视图是一个简单的Python函数(或基于类的视图)。该视图将传递以下参数:
- 的实例
HttpRequest
。 - 如果匹配的URL模式未返回命名组,则来自正则表达式的匹配项将作为位置参数提供。
- 关键字参数由与路径表达式匹配的任何命名部分组成,并由或 的可选
kwargs
参数中指定的任何参数覆盖 。django.urls.path()
django.urls.re_path()
5、如果没有URL模式匹配,或者在此过程中的任何时候引发异常,Django都会调用一个适当的错误处理视图。请参阅下面的错误处理。
例子:
1 from django.urls import path 2 3 from . import views 4 5 urlpatterns = [ 6 path('articles/2003/', views.special_case_2003), 7 path('articles/<int:year>/', views.year_archive), 8 path('articles/<int:year>/<int:month>/', views.month_archive), 9 path('articles/<int:year>/<int:month>/<slug:slug>/', views.article_detail), 10 ]
- 捕获的值可以选择包括转换器类型。例如,用于
<int:name>
捕获整数参数。如果不包括转换器/
,则匹配除字符以外的任何字符串。 - 无需添加斜杠,因为每个URL都有该斜杠。例如
articles
,不是/articles
。 - 要从URL捕获值,请使用尖括号。
注意:在使用如<int:year>传递参数的时候,传递的参数一定要被对应的函数接收。
请求示例:
- 请求
/articles/2005/03/
匹配列表中的第三个条目。Django将调用该函数 。views.month_archive(request, year=2005, month=3)
/articles/2003/
会匹配列表中的第一个模式,而不是第二个,因为这些模式是按顺序测试的,而第一个是第一个通过的测试。随意利用命令来插入类似这样的特殊情况。在这里,Django将调用该函数views.special_case_2003(request)
/articles/2003
不会与任何这些模式匹配,因为每种模式都要求URL以斜杠结尾。/articles/2003/03/building-a-django-site/
将匹配最终模式。Django将调用该函数 。views.article_detail(request, year=2003, month=3, slug="building-a-django-site")
路径转换器
默认情况下,以下路径转换器可用:
str
-匹配任何非空字符串,但路径分隔符除外'/'
。如果表达式中不包含转换器,则为默认设置。int
-匹配零或任何正整数。返回一个int。slug
-匹配由ASCII字母或数字以及连字符和下划线字符组成的任何条形字符串。例如,building-your-1st-django-site
。uuid
-匹配格式化的UUID。为防止多个URL映射到同一页面,必须包含破折号,并且字母必须小写。例如,075194d3-6885-417e-a8a8-6c931e272f00
。返回一个UUID
实例。path
-匹配任何非空字符串,包括路径分隔符'/'
。这样,您就可以与完整的URL路径进行匹配,而不仅仅是与URL路径的一部分进行匹配str
。
使用正则表达式
如果路径和转换器语法不足以定义URL模式,则还可以使用正则表达式。为此,请使用 re_path()
代替path()
。
在Python正则表达式中,命名正则表达式组的语法为(?P<name>pattern)
,其中name
是组的名称,并且 pattern
是匹配的某种模式。
这是前面的示例URLconf,使用正则表达式重写:
1 from django.urls import path, re_path 2 3 urlpatterns = [ 4 path('articles/2003/', views.special_case_2003), 5 re_path(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive), 6 re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive), 7 re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<slug>[w-]+)/$', views.article_detail), 8 ]
这可以完成与上一个示例大致相同的操作,除了:
- 将要匹配的确切URL受到更多限制。例如,年份10000将不再匹配,因为年份整数被限制为正好是四位数长。
- 无论正则表达式进行哪种匹配,每个捕获的参数都将作为字符串发送到视图。
当从使用path()切换为使用 re_path()
时,特别重要的是要注意视图参数的类型可能会更改,因此您可能需要调整视图。
嵌套参数
考虑以下URL模式,这些URL模式可以选择采用page参数:
urls.py中:
1 from django.urls import path, re_path 2 from app01 import views 3 4 urlpatterns = [ 5 path('admin/', admin.site.urls), 6 re_path(r'^blog/(page-(d+)/)?$', views.test), 7 re_path(r'^comments/(?:page-(?P<page_number>d+)/)?$', views.test2), 8 ]
views.py中:
1 def test(request, arg1, arg2): 2 print(request) 3 print(arg1) 4 print(arg2) 5 return HttpResponse("空页面") 6 7 8 def test2(request, page_number): 9 print(request) 10 print(page_number) 11 return HttpResponse("空页面2")
例如:
blog/page-2/
将导致与匹配blog_articles
两个位置参数:page-2/
和2
。在视图函数中要接收这两个位置参数。
comments/page-2/
与关键字参数 page_number
设置为2 匹配。在视图函数中只需要接收page_number一个参数即可。
分组命名匹配
在更高级的用法中,可以使用分组命名匹配的正则表达式组来捕获URL中的值并以关键字参数形式传递给视图。
在Python的正则表达式中,分组命名正则表达式组的语法是(?P<name>pattern)
,其中name
是组的名称,pattern
是要匹配的模式。
注意:分组命名匹配中视图函数在接收参数中必须写入分组关键字参数名。
根据参数类型分组匹配:
1 urlpatterns = [ 2 path('articles/2003/', views.special_case_2003), 3 path('articles/<int:year>/', views.year_archive), 4 path('articles/<int:year>/<int:month>/', views.month_archive), 5 path('articles/<int:year>/<int:month>/<slug:slug>/', views.article_detail), 6 ]
正则分组命名匹配:
正则分组命名匹配获取的全是字符串类型
1 urlpatterns = [ 2 path('articles/2003/', views.special_case_2003), 3 re_path(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive), 4 re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive), 5 re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<slug>[w-]+)/$', views.article_detail), 6 ]
include其他的URLconfs
当包含的url太多,或者对url进行分不同功能app分类管理时,就需要用到包含其他URLconfs的方法。
path('app01/', include(('app01.urls',"app01"), namespace="app01")), path('app02/', include(('app02.urls', "app02"), namespace="app02")),
传递额外的参数给视图函数(了解)
该path()
函数可以使用可选的第三个参数,该参数应该是传递给view函数的额外关键字参数的字典。
urlpatterns = [ path('blog/<int:year>/', views.year_archive, {'foo': 'bar'}), ]
将额外的选项传递给include()
同样,您可以将额外选项传递给include()
,所包含的URLconf中的每一行都将传递额外选项。
例如,这两个URLconf集在功能上是相同的:
设置一:
1 from django.urls import include, path 2 3 urlpatterns = [ 4 path('blog/', include('inner'), {'blog_id': 3}), 5 ] 6 7 # inner.py 8 from django.urls import path 9 from mysite import views 10 11 urlpatterns = [ 12 path('archive/', views.archive), 13 path('about/', views.about), 14 ]
设置二:
1 # main.py 2 from django.urls import include, path 3 from mysite import views 4 5 urlpatterns = [ 6 path('blog/', include('inner')), 7 ] 8 9 # inner.py 10 from django.urls import path 11 12 urlpatterns = [ 13 path('archive/', views.archive, {'blog_id': 3}), 14 path('about/', views.about, {'blog_id': 3}), 15 ]
URL的反向解析
Django 提供一个办法是让URL 映射是URL 设计唯一的地方。你填充你的URLconf,然后可以双向使用它:
- 根据用户/浏览器发起的URL 请求,它调用正确的Django 视图,并从URL 中提取它的参数需要的值。
- 根据Django 视图的标识和将要传递给它的参数的值,获取与之关联的URL。
第一种方式是我们在前面的章节中一直讨论的用法。第二种方式叫做反向解析URL、反向URL 匹配、反向URL 查询或者简单的URL 反查。
在需要URL 的地方,对于不同层级,Django 提供不同的工具用于URL 反查:
- 在模板中:使用url模板标签。
- 在Python 代码中:使用django.core.urlresolvers.reverse() 函数。
- 在更高层的与处理Django 模型实例相关的代码中:使用get_absolute_url() 方法。
考虑下面的URLconf:
1 from django.conf.urls import url 2 3 from . import views 4 5 urlpatterns = [ 6 # ... 7 url(r'^articles/([0-9]{4})/$', views.year_archive, name='news-year-archive'), 8 # ... 9 ]
根据这里的设计,某一年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>
在Python 代码中,这样使用:
1 from django.http import HttpResponseRedirect 2 from django.urls import reverse 3 4 def redirect_to_year(request): 5 # ... 6 year = 2006 7 # ... 8 return HttpResponseRedirect(reverse('news-year-archive', args=(year,)))
如果出于某种原因决定更改发布年度文章存档内容的URL,则只需要更改URLconf中的条目即可。
在视图具有一般性质的某些情况下,URL和视图之间可能存在多对一关系。对于这些情况,在反向URL时,视图名称并不是一个足够好的标识符。阅读下一节以了解Django为此提供的解决方案。
命名空间模式
即使不同的APP使用相同的URL名称,URL的命名空间模式也可以让你唯一反转命名的URL。
举个例子:
project中的urls.py
from django.conf.urls import url, include urlpatterns = [ url(r'^app01/', include('app01.urls', namespace='app01')), url(r'^app02/', include('app02.urls', namespace='app02')), ]
app01中的urls.py
from django.conf.urls import url from app01 import views app_name = 'app01' urlpatterns = [ url(r'^(?P<pk>d+)/$', views.detail, name='detail') ]
app02中的urls.py
from django.conf.urls import url from app02 import views app_name = 'app02' urlpatterns = [ url(r'^(?P<pk>d+)/$', views.detail, name='detail') ]
现在,我的两个app中 url名称重复了,我反转URL的时候就可以通过命名空间的名称得到我当前的URL。
语法:
'命名空间名称:URL名称'
模板中使用:
{% url 'app01:detail' pk=12 %}
views中的函数中使用:
v = reverse('app01:detail', kwargs={'pk':11})
这样即使app中URL的命名相同,我也可以反转得到正确的URL了。
遗留的问题:
自定义路径转换器还没能成功运行。