準備
首先新建一個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!
本文純屬個人見解,如有不對,敬請指教。