准备
首先新建一个Django 项目
django-admin startproject urlTest
进入manage.py所在目录后
./manage.py startapp app1 ./manage.py startpap app2
此时我们新建了一个名为urlTest的项目,其中有两个模块的名称分别为app1和app2。(树目录结构如下)
. ├── app1 │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ ├── urls.py │ └── views.py ├── app2 │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ └── views.py ├── db.sqlite3 ├── manage.py └── urlTest ├── __init__.py ├── settings.py ├── urls.py └── wsgi.py
在settings.py中我们可以看到:
ROOT_URLCONF = 'urlTest.urls'
#浏览器访问的所有的url都将在urlTest目录下的urls.py中配置,
urls.py默认加入了admin模块的url:
# urlTest.urls.py from django.conf.urls import url, include from django.contrib import admin urlpatterns = [ url(r'^admin/', admin.site.urls), ]
也即是说,每个url都映射到了一个指定的view函数,其中views中定义的函数接受一个request,并返回一个response。
如对view的工作原理不清楚,可参考这里request-response。
这里写代码片
正则表达式与命名组
首先在app1模块中通过正则表达式分别动态的匹配年,年月,年月日类型的URL。
默认的情况下app1模块中是没有urls.py文件,在我们新建了之后,还需要在urlTest的urls.py加上:
url(r'^app1/', include('app1.urls')) #这样就包括了app1模块的urls.py文件 接下来在新建的app1模块下的urls.py中写动态正则表达式: # app1.urls.py from django.conf.urls import url from . import views urlpatterns = [ url(r'^$', views.index), url(r'^([0-9]{4})/$', views.pattern1), url(r'^([0-9]{4})/(0?[1-9]|1[0-2])/$', views.pattern2), url(r'^([0-9]{4})/(0?[1-9]|1[0-2])/(0?[1-9]|[1-2][0-9]|3[0-1])/$', views.pattern3), #记得加上^和$否则年月,年月日的匹配都会被年的匹配 ]
我用斜杆“/”作为分割年月日的符号,但是为什么斜杆之前要加上圆括号呢?因为当加上圆括号的时候,django就能从URL中捕获这一个值并传递给相对应的views函数,当然使用的是位置传参。
根据URL匹配到指定的views函数后,我分别返回了HttpResponse:
# app1.views from django.shortcuts import render from django.http import HttpResponse # Create your views here. def index(request): return HttpResponse('index', 'text/plain') def pattern1(request, year): return HttpResponse(year, 'text/plain') def pattern2(request, year, month): return HttpResponse(year + month, 'text/plain') def pattern3(request, year, month, date): return HttpResponse(year + month + date, 'text/plain')
刚才我们在使用圆括号进行传参的时候是位置传参,那么如果我们希望使用关键字传参的时候该怎么办呢?
这时候我们就使用到了命名组,命名组的正则表达式语法是(?P<name>pattern),其中name是指传递参数的名字,pattern是指匹配模式。
因此,下面的代码与之前的正则表达式+圆括号是完全等效地:
# app1.urls.py from django.conf.urls import url from . import views urlpatterns = [ url(r'^$', views.index), url(r'^(?P<year>[0-9]{4})/$', views.pattern1), url(r'^(?P<year>[0-9]{4})/(?P<month>0?[1-9]|1[0-2])/$', views.pattern2), url(r'^(?P<year>[0-9]{4})/(?P<month>0?[1-9]|1[0-2])/(?P<date>0?[1-9]|[1-2][0-9]|3[0-1])/$', views.pattern3), ]
最后值得注意的是在views函数中的参数是可以使用默认参数的,以及可以使用正则表达式进行不捕获参数的设置(可在嵌套参数中使用),如语法(?:….)。
除了捕获URL参数以外,我们还可以直接通过url函数传输额外的数据给view函数。
# urls.py # 替换index的url url(r'^$', views.index, {'string': 'Hello World!'}) # views.py def index(request, string): return HttpResponse(string, 'text/plain')
如果在包含include函数的url函数里面传输额外的数据,那么额外的数据将传输给被包含的urls.py的每一行url函数上。
URL模式与命名空间
在url patterns上里面的每一个url函数就是一个URL模式,在django中使用类django.core.urlresolvers.RegexURLPattern来表示。
而url patterns就代表着一个URL分解器(url resolver),使用include函数包含其他的url配置模块也是作为一个URL分解器来解析,在django中使用类django.core.urlresolvers.RegexURLResolver来表示。
命名空间主要分为两种,分别是实例命名空间(instance namespace)以及应用命名空间(application namespace)。
为什么需要命名空间呢?
在之前如果我们通过URL反查的话是通过URL模式中的name属性来进行反查标记的,但是name属性容易重复并且不利于复用,当我们要多次部署一个URL配置模块的时候,就无法通过简单的name属性来进行标记了。
如何设置实例命名空间以及应用命名空间?
# include函数的API include(arg, namespace=None, app_name=None) # namespace设置实例命名空间,app_name设置应用命名空间 # 不能只设置app_name,否则会报错,以下是报错的源码 if app_name and not namespace: raise ValueError('Must specify a namespace if specifying app_name.')
一般来说,同一应用下的不同实例应该具有相同的应用命名空间,但是,这并不意味着不同应用可以使用相同的实例命名空间,因为实例命名空间在你所有项目中都是唯一的。
URL反向解析
URL反向解析一般是通过reverse函数以及模板中的url标记实现。
我们首先看看在django官方文档中URL反向解析的机制:
Reversing namespaced URLs When given a namespaced URL (e.g. ‘polls:index’) to resolve, Django splits the fully qualified name into parts and then tries the following lookup: First, Django looks for a matching application namespace (in this example, ‘polls’). This will yield a list of instances of that application. If there is a current application defined, Django finds and returns the URL resolver for that instance. The current application can be specified with the current_app argument to the reverse() function. The url template tag uses the namespace of the currently resolved view as the current application in a RequestContext. You can override this default by setting the current application on the request.current_app attribute. If there is no current application. Django looks for a default application instance. The default application instance is the instance that has an instance namespace matching the application namespace (in this example, an instance of polls called ‘polls’). If there is no default application instance, Django will pick the last deployed instance of the application, whatever its instance name may be. If the provided namespace doesn’t match an application namespace in step 1, Django will attempt a direct lookup of the namespace as an instance namespace.
除了最后一个视图名作为name标记来识别,之前的每一个名称首先是作为应用命名空间来识别的(第一条),如果找不到符合的应用命名空间则直接作为实例命名空间来识别(第五条)。
当识别出应用命名空间的时候,再看当前应用有没有定义(即current_app,这里比较容易引起误解,这个当前应用并非应用命名空间,恰恰相反,它是指实例命名空间),如果定义了,直接在之前的已经确认的应用命名空间的所属的实例命名空间列表下寻找current_app的值(第二条)。
如果在实例命名空间列表下找不到current_app的值,那么它会寻找默认的实例命名空间,即名称与应用命名空间相同的实例命名空间。(第三条)
如果连默认的实例命名空间都找不到,那么django会返回最后一个部署的实例命名空间的URL。(第四条)
我们还是通过具体的例子来说明反向解析机制吧。
之前的例子里定义了app1模块和app2模块,再分别生成两个它们的实例。
# urlTest.urls.py from django.conf.urls import url, include from django.contrib import admin urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^app2/first/', include('app2.urls', namespace='first', app_name='app2')), url(r'^app2/second/', include('app2.urls', namespace='second', app_name='app2')), url(r'^app1/third/', include('app1.urls', namespace="third", app_name='app1')), url(r'^app1/fourth/', include('app1.urls', namespace="fourth", app_name='app1')), ] # app1.views.py from django.core.urlresolvers import reverse from django.http import HttpResponse import pdb def index(request, string): pdb.set_trace() return HttpResponse(string, 'text/plain')
在pdb的测试环境下我们使用reverse函数分别确定。
(Pdb) reverse('first:index') u'/app2/first/' (Pdb) reverse('second:index') u'/app2/second/' (Pdb) reverse('third:index') u'/app1/third/' (Pdb) reverse('fourth:index') u'/app1/fourth/'
我们可以看到,实例命名空间能够唯一确定整个项目的URL。
(Pdb) reverse('app1:index') u'/app1/fourth/' (Pdb) reverse('app2:index') u'/app2/second/'
当我们使用应用命名空间的时候,django反向解析机制在没有提供current_app的情况下又找不到默认的实例命名空间,只能返回最后一个部署的实例命名空间。
(Pdb) reverse('app1:index', current_app='third') u'/app1/third/' (Pdb) reverse('app1:index', current_app='fourth') u'/app1/fourth/' (Pdb) reverse('app2:index', current_app='first') u'/app2/first/' (Pdb) reverse('app2:index', current_app='second') u'/app2/second/'
在提供了实例命名空间之后,即使是使用应用命名空间也能唯一确定URL。
最后我们重新添加默认实例命名空间。
# urlTest.urls.py from django.conf.urls import url, include from django.contrib import admin urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^app2/default/', include('app2.urls', namespace='app2', app_name='app2')), url(r'^app2/first/', include('app2.urls', namespace='first', app_name='app2')), url(r'^app2/second/', include('app2.urls', namespace='second', app_name='app2')), url(r'^app1/default/', include('app1.urls', namespace="app1", app_name='app1')), url(r'^app1/third/', include('app1.urls', namespace="third", app_name='app1')), url(r'^app1/fourth/', include('app1.urls', namespace="fourth", app_name='app1')), ] # pdb (Pdb) reverse('app1:index') u'/app1/default/' (Pdb) reverse('app2:index') u'/app2/default/'
最后还要提醒的是,千万不要以为不同应用下可以有相同的实例,namespace必须是unique!
from django.conf.urls import url,include from django.contrib import admin from app import views urlpatterns = [ url(r'^admin/', admin.site.urls), # 静态路由 url(r'^login/',views.login), # 如 http://127.0.0.1:8080/login # 为路由取别名,这样,在地址修改的时候,前端不需要修改地址 url(r'^register/',views.register,name='reg'), # 动态路由,可以用来做分页 # 无名分组,views里面的函数名参数可任意 url(r'^article/(\d{4})$',views.article), # 如 http://127.0.0.1:8080/article/2017 # 有名分组,view里面的函数名必须是year和month url(r'^arcticle/(?P<year>\d{4})/(?P<month>\d{2})',views.arcticle), # 路由分发 url(r'^app/',include('app.urls')), ]