国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 開發(fā) > PHP > 正文

Python探索之URL Dispatcher實例詳解

2024-05-04 21:50:17
字體:
供稿:網(wǎng)友

URL dispatcher簡單點理解就是根據(jù)URL,將請求分發(fā)到相應(yīng)的方法中去處理,它是對URL和View的一個映射,它的實現(xiàn)其實也很簡單,就是一個正則匹配的過程,事先定義好正則表達(dá)式和該正則表達(dá)式對應(yīng)的view方法,如果請求的URL符合這個正則表達(dá)式,那么就分發(fā)這個請求到這個view方法中。

有了這個base,我們先拋出幾個問題,提前思考一下:

這個映射定義在哪里?當(dāng)映射很多時,如果有效的組織?

URL中的參數(shù)怎么獲取,怎么傳給view方法?

如何在view或者是template中反解出URL?

好,先來看一個簡單的例子:

  1. from django.conf.urls import patterns, url, include 
  2. urlpatterns = patterns(''
  3.   url(r'^articles/2003/$''news.views.special_case_2003'), 
  4.   url(r'^articles/(/d{4})/$''news.views.year_archive'), 
  5. urlpatterns += patterns(''
  6.   url(r'^articles/(?P<year>/d{4})/(?P<month>/d{2})/$''news.views.month_archive'), 
  7.   url(r'model/'include('model_test.urls')), 

這段代碼就是一個URL Dispatcher的例子,它在一個單獨的python模塊定義,Django中管這個模塊叫做URLconf,其實,就是通過python代碼方式實現(xiàn)的配置文件,在這個配置中定義了URL路徑和對應(yīng)的處理方法之間的映射。在Django中,是通過“樹”的結(jié)構(gòu)來管理URLconf之間的關(guān)系的,在Django中的主配置文件中,有一個叫做ROOT_URL_CONF的配置項,就是用來指定根URLconf,從根URLconf開始,逐條進行匹配,直到找到匹配項為止,這就是我們上面提到的第一個問題的答案,下面還會再仔細(xì)剖析。

在上面例子中,我們可以看到有3個方法:patterns, url, include。url方法構(gòu)建了一個URL到View方法的映射關(guān)系對象,patterns將這些映射關(guān)系對象組織成為一個python的列表,那include是做什么的呢?它就是我們上面說到的“樹”結(jié)構(gòu)關(guān)系的聯(lián)系者,include會關(guān)聯(lián)其他的URLconf到本URLconf,也就是說include關(guān)聯(lián)的是孩子節(jié)點。整個URL dispatcher體系,就是由這三個方法構(gòu)建起來的,下面我們重點來介紹這三個方法,了解了這三個方法,整個URL映射機制就會非常清楚了。

  1. def patterns(prefix, *args): 
  2.   pass 
  3. def url(regex, view, kwargs=None, name=None, prefix=''): 
  4.   pass 
  5. def include(arg, namespace=None, app_name=None): 
  6.   pass 
  7. url() 

先來看下最重要的url()方法。第一個參數(shù)regex是代表URL的正則表達(dá)式,第二個參數(shù)指定了和該正則表達(dá)式映射的View,此外,還可以通過kwargs參數(shù),給view方法指定默認(rèn)的kwargs參數(shù),還有name參數(shù),用來命名該URL,主要用在URL反解中,至于prefix用處不大,不解釋。

url()方法最終構(gòu)造了一個對象,我們姑且叫它URL映射對象,當(dāng)?shù)谝淮卧L問這個對象去匹配URL時,它會把這個對象中的正則表達(dá)式編譯一次,然后保存在該對象中,所以以后再次匹配時,就會很快,不會重復(fù)編譯該正則表達(dá)式了。在這里正則匹配其實就是用就是python的re模塊,使用過程大致如下:

  1. # 第一次訪問時,編譯,然后保存在url對象中 
  2. regex = re.compile(regex_str, re.UNICODE) 
  3. # 每次URL訪問時,進行正則匹配 
  4. match = regex.search(path) 
  5. kwargs = match.groupdict() 
  6. args = match.groups() 

注意,這里涉及到了上面提到的第二個問題,即URL中的參數(shù)是如何獲取,如何傳遞給view方法的。從URL中獲取參數(shù),其實是通過re模塊中named groups和non-named groups的概念來獲取的,通過match.groupdict()得到的是named groups,其實就是一個字典,字典的key是在URL中指定的,該字典會作為kwargs參數(shù)傳遞給view,而通過match.groups()得到的是non-named groups,是一個元組,即tuple,該元組會作為args參數(shù)傳遞給view。不過,這里的args和kwargs是不能夠同時存在的,當(dāng)有kwargs不為空時,args就會被置空,當(dāng)kwargs為空時,args才會被用到,而傳遞給view的kwargs就只有url()方法中指定的默認(rèn)kwargs。也就是說,如果你在URL中使用了named groups,那么non-named groups就會被忽略,如果只使用了non-named groups,它才會被作為args參數(shù),傳遞給view方法。

好,糾結(jié)了一大堆,還是來解析一下我們上面提到的例子,假如我們有url和view:

urls:

  1. url(r'^articles/(/d{4})/$''news.views.year_archive'
  2. url(r'^articles/(?P<year>/d{4})/(?P<month>/d{2})/$''news.views.month_archive'

views:

  1. def year_archive(request, *args, **kwargs): 
  2.   pass 
  3. def month_archive(request, *args, **kwargs): 
  4.   pass 

當(dāng)我們訪問”articles/2014/”這個路徑的時候,解析的過程如下:

  1. >>> import re 
  2. >>> regex = re.compile(r'^articles/(/d{4})/$', re.UNICODE) 
  3. >>> match = regex.search("articles/2014/"
  4. >>> match.groupdict() 
  5. {} 
  6. >>> match.groups() 
  7. ('2014',) 

所以最終傳遞給year_archive()方法中的參數(shù)應(yīng)該是這樣的:

  1. (Pdb) pp args 
  2. (u'2014',) 
  3. (Pdb) pp kwargs 
  4. {} 

當(dāng)我們訪問”articles/2014/11”這個路徑時,解析的過程如下:

  1. >>> import re 
  2. >>> regex = re.compile(r'^articles/(?P<year>/d{4})/(?P<month>/d{2})/$', re.UNICODE) 
  3. >>> match = regex.search("articles/2014/11/"
  4. >>> match.groupdict() 
  5. {'year''2014''month''11'
  6. >>> match.groups() 
  7. ('2014''11'

所以最終傳遞給month_archive()方法中的參數(shù)應(yīng)該是這樣的:

  1. (Pdb) pp args 
  2. () 
  3. (Pdb) pp kwargs 
  4. {'month': u'11''year': u'2014'

再羅嗦一句,因為url()可以指定一個kwargs參數(shù),它是該url關(guān)聯(lián)的view()方法的默認(rèn)kwargs參數(shù),也就是說如果在url()方法中指定了kwargs,那么會將這個參數(shù)的內(nèi)容,也傳遞到view方法中的kwargs參數(shù)中。

好,至此,url()方法基本上就清楚了,第二個問題也解決了,至于name參數(shù),到下面講到URL反解的時候再詳細(xì)解釋。

patterns()

接下來,我們來看patterns()方法,這個其實比較簡單,它就是返回一個由url()方法構(gòu)造的URL映射對象組成的列表。它有一個必填參數(shù)是prefix,這個prefix是它所包含的view的公共前綴,這么做是為了避免代碼重復(fù),比如:

  1. urlpatterns = patterns(''
  2.   url(r'^articles/(/d{4})/$''news.views.year_archive'), 
  3.   url(r'^articles/(/d{4})/(/d{2})/$''news.views.month_archive'), 
  4.   url(r'^articles/(/d{4})/(/d{2})/(/d+)/$''news.views.article_detail'), 

可以寫成:

  1. urlpatterns = patterns('news.views'
  2.   url(r'^articles/(/d{4})/$''year_archive'), 
  3.   url(r'^articles/(/d{4})/(/d{2})/$''month_archive'), 
  4.   url(r'^articles/(/d{4})/(/d{2})/(/d+)/$''article_detail'), 

注意,由patterns()生成的列表,被賦值給urlpatterns這個變量,這個變量是不能隨便定義的,必須是約定好的,默認(rèn)django會去URLconf中查找這個變量,也許你可以在某個地方設(shè)定一個參數(shù),來換個約定,改變一下這個變量名。

在2.0版本的Django中,會舍棄這個方法,而是直接賦值給urlpatterns一個列表,不做過多討論。

include()

我們來說include(),這其實是個難點,關(guān)鍵在于URL反解那里,Django的文檔也沒有說清楚,而且關(guān)系也比較亂,所以,必須得實際的測試一下,才會明白。

上面說過,include()是“樹”結(jié)構(gòu)關(guān)系的聯(lián)系者,include會關(guān)聯(lián)其他的URLconf到本URLconf,靠include()才能夠讓Django的URL設(shè)計變得非常的靈活和簡潔。include()有三個參數(shù),第一個參數(shù)不必多說,它指定了要包含的其它URLconf的路徑,關(guān)鍵是剩下的兩個參數(shù),一個是namespace, 一個是app_name,有什么用呢?其實,這兩個參數(shù)再加上url()方法中的name參數(shù),共同構(gòu)成了Django中URL的命名空間,而命名空間主要是為了URL反解的,那什么是URL反解呢?我們現(xiàn)在能根據(jù)請求的一個URL路徑,找到對應(yīng)的view處理方法,那么反過來,我們在view方法中,或者是template中,根據(jù)傳遞過來的參數(shù),能夠解析出對應(yīng)的URL,這就是URL反解。為什么需要URL反解呢?主要是為了不要把程序?qū)懰懒耍绻覀冊趆tml中直接把路徑寫死了,那么以后改起來就會非常的麻煩,所以常常會把這些可變的東西放到一個變量中,在程序中引用的是這個變量名,這是寫程序的一個常識吧。所以,我們能從這個“樹”中,從上到下,也得能夠從下到上。

在template中進行反解使用的是{%url%}這個tag,在view中,進行反解,使用的是`django.core.urlresolvers.reverse()這個方法。

好,先來看一個最簡單的例子:

  1. mydjango/urls.py: 
  2.  
  3. urlpatterns = patterns(''
  4.   url(r'model/'include('model_test.urls')), 
  5. model_test/urls.py: 
  6.  
  7. urlpatterns = patterns(''
  8.   url(r'^$', views.index, name='index'), 

mydjango/urls.py是根URLconf,它包含了model_test的URLconf,modul_test中的urlpatterns中有一個命名為index的url映射對象。

如果我們想在template中得到這個view對應(yīng)的url的真實路徑,那么用template的url tag就行了:

{% url 'index' %}

這樣得到的結(jié)果就是: /model/。

同理,如果在view方法中,那么使用reverve()方法:

from django.core.urlresolvers import reverse

reverse("index")

得到的也是: /model/

在這個例子中,我們只使用到了url()方法中的name參數(shù),并沒有用到命名空間,因為這種簡單的情況,沒有產(chǎn)生混淆,還沒有必要用到命名空間,使用命名空間的主要有以下兩種情況:

當(dāng)在一個項目中,有多個應(yīng)用,應(yīng)用中定義的url映射對象的name有可能有重復(fù)的,這樣當(dāng)進行反解時,Django就不能確定到底是哪個應(yīng)用了

當(dāng)在一個項目中,同一個應(yīng)用,被部署多個實例時,這多個實例之間是共享定義的name url的,所以在進行反解時,也不能確定,到底是哪個實例

第一種情況,其實是比較好解決的,在每一個應(yīng)用的include()中,指定不同的namespace參數(shù)就可以了,如:

mydjango/urls.py:

  1. urlpatterns = patterns(''
  2.   url(r'model/'include('model_test.urls', namespace='model')), 

這樣,在template中或者是reverse中,在name前需要加上namepace進行反解:

  1. {% 'model:index' %} 
  2. or 
  3. reverse("model:index"

這樣就可以準(zhǔn)確的反解到model_test這個應(yīng)用中。

第二種情況,什么叫“一個應(yīng)用,被部署多個實例”呢?其實就是這種情況:

  1. urlpatterns = patterns(''
  2.   url(r'model1/'include('model_test.urls')), 
  3.   url(r'model2/'include('model_test.urls')), 

不同的路徑下,引用的是相同的應(yīng)用,同一個應(yīng)用,被實例化了兩次,這種情況,怎么進行區(qū)分呢?我能像第一種情況一樣,在include中指定不同的namespace來解決問題嗎?答案是可行的,但是不推薦。要知道,他們引用的是同一個應(yīng)用,同一個應(yīng)用意味著什么,意味著代碼是一樣的,你在同一份代碼中,通過if/else來判斷該反解到哪個namespace中,這個做法是非常不優(yōu)雅的,嚴(yán)重違背了Django的DRY原則。

那Django通過什么辦法來解決這個問題呢?它通過app_name + namespace + current_app的方式來解決。namespace, app_name分別為include()的第二個和第三個參數(shù),app_name指定這個應(yīng)用的名稱,namespace指定這個應(yīng)用某個實例的url的命名空間,current_app則是根據(jù)請求的路徑,解析出的該url的命名空間,也就是namespace,在進行反解時,動態(tài)的將該current_app傳遞給反解的函數(shù)中,反解的函數(shù)就可以根據(jù)這個namespace,來確定應(yīng)該反解到哪個實例中了。同一個應(yīng)用的多個實例的app_name應(yīng)該是相同的,而namespace應(yīng)該是不同的。可能有點亂了,我們再來舉個例子:

mydjango/urls.py:

  1. urlpatterns = patterns(''
  2.   url(r'model1/'include('model_test.urls', namespace='model_1', app_name="app")), 
  3.   url(r'model2/'include('model_test.urls', namespace='model_2', app_name="app")), 

model_test/urls.py:

  1. urlpatterns = patterns(''
  2.   url(r'^$', views.index, name='index'), 

model_test/views.py:

  1. from django.shortcuts import render 
  2. from django.core.urlresolvers import reverse 
  3.  
  4. def index(request): 
  5.   current_app = request.resolver_match.namespace 
  6.   print reverse("app:index", current_app=current_app) 
  7.   return render(request, 'model/index.html', current_app=current_app) 

model_test/templates/model/index.html:

{% url 'app:index' %}

在view中,首先獲得當(dāng)前的namespace,然后通過current_app傳遞給reverse(),reverse就可以知道應(yīng)該解析到哪個實例中了。同理,在templace中,也需要將current_app傳遞過去。這樣,我們就可以動態(tài)的反解URL了:

當(dāng)我們請求的路徑是/model1/時,current_app就是model_1,再根據(jù)app_name,就可以準(zhǔn)確的反解出該URL為:/model1/,如果請求的路徑是/model2/,那么current_app就是model_2,反解的路徑就是/model2/。

雖然有點復(fù)雜,但是這種情況用的比較少,google了很久,才把這種情況大概弄清楚,也許理解的有不對的地方,待以后實踐去檢驗,現(xiàn)在關(guān)鍵在于理解這種機制思想。

發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 边坝县| 全南县| 南充市| 琼中| 富锦市| 宁安市| 冕宁县| 乳源| 搜索| 海林市| 青冈县| 阜新市| 海淀区| 会同县| 合山市| 武威市| 宜兰县| 清丰县| 高平市| 珠海市| 旬阳县| 宁德市| 南皮县| 蓝山县| 蓬溪县| 灵台县| 澜沧| 额尔古纳市| 哈尔滨市| 丰城市| 六枝特区| 隆昌县| 宁化县| 延吉市| 望谟县| 龙井市| 卫辉市| 太湖县| 洛阳市| 新丰县| 肥东县|