使方法import流化
看看下面的URL配置,基于第3章的例子:
- from django.conf.urls.defaults import *
- from mysite.views import current_datetime, hours_ahead, hours_behind, now_in_chicago, now_in_london
- urlpatterns = patterns('',
- (r'^now/$', current_datetime),
- (r'^now/plus(\d{1,2})hours/$', hours_ahead),
- (r'^now/minus(\d{1,2})hours/$', hours_behind),
- (r'^now/in_chicago/$', now_in_chicago),
- (r'^now/in_london/$', now_in_london),
- )
前面第3章解釋到,URL配置里每行都包含了它相關(guān)的視圖方法,直接作為一個(gè)方法對(duì)象傳遞
這意味著有必要在模塊最上面import視圖方法
但是隨著Django程序越來越復(fù)雜,它的URL配置也隨之增加,維護(hù)這些imports將十分麻煩
對(duì)于每個(gè)新的視圖方法,你都要記得import它,并且使用這個(gè)方法的話import語句會(huì)變得很長(zhǎng)
可以通過import views模塊本身來避免這種復(fù)雜,下面的URL配置的例子和上面的是相等的:
- from django.conf.urls.defaults import *
- from mysite import views
- urlpatterns = patterns('',
- (r'^now/$', views.current_datetime),
- (r'^now/plus(\d{1,2})hours/$', views.hours_ahead),
- (r'^now/minus(\d{1,2})hours/$', views.hours_behind),
- (r'^now/in_chicago/$', views.now_in_chicago),
- (r'^now/in_london/$', views.now_in_london),
- )
Django提供另一種方式來在URL配置中指定視圖方法:你可以傳遞一個(gè)包含模塊名字和方法名字的字符串
而不是方法對(duì)象本身,繼續(xù)上面的例子:
- from django.conf.urls.defaults import *
- urlpatterns = patterns('',
- (r'^now/$', 'mysite.views.current_datetime'),
- (r'^now/plus(\d{1,2})hours/$', 'mysite.views.hours_ahead'),
- (r'^now/minus(\d{1,2})hours/$', 'mysite.views.hours_behind'),
- (r'^now/in_chicago/$', 'mysite.views.now_in_chicago'),
- (r'^now/in_london/$', 'mysite.views.now_in_london'),
- )
使用這種技術(shù),沒有必要再import視圖方法,Django根據(jù)字符串描述的視圖方法的名字和路徑自動(dòng)
在第一次訪問時(shí)import合適的視圖方法
另一種捷徑是當(dāng)使用字符創(chuàng)技術(shù)時(shí)可以把通用的視圖前綴提取出來,我們的例子中,每個(gè)視圖字符串
都以'mysite.views'開始,它們是冗余的,我們可以把它作為第一個(gè)參數(shù)傳遞給patterns():
- from django.conf.urls.defaults import *
- urlpatterns = patterns('mysite.views',
- (r'^now/$', 'current_datetime'),
- (r'^now/plus(\d{1,2})hours/$', 'hours_ahead'),
- (r'^now/minus(\d{1,2})hours/$', 'hours_behind'),
- (r'^now/in_chicago/$', 'now_in_chicago'),
- (r'^now/in_london/$', 'now_in_london'),
- )
注意你不需在前綴末尾加上".",也不需在視圖字符串前面加".",Django會(huì)自動(dòng)加上去
這兩種方式哪種更好?這取決于你的個(gè)人編碼風(fēng)格和需求
使用字符串方式的優(yōu)點(diǎn):
1,更緊湊,因?yàn)椴恍枰猧mport視圖方法
2,如果你的視圖方法分布在幾個(gè)不同的Python模塊,這種方式更可讀和更易管理
使用方法對(duì)象方式的優(yōu)點(diǎn):
1,可以輕松包裝視圖方法,參考本章后面的“包裝視圖方法”
2,更“Pythonic”,更貼近Python傳統(tǒng),如傳遞方法對(duì)象
兩種方式都是合法的,你甚至可以在同一URL配置里混用它們,選擇權(quán)在你手中
多種視圖前綴
實(shí)踐中如果你使用字符串技術(shù),你很可能混合視圖,因?yàn)橐晥D沒有通用的前綴
盡管如此,你可以利用視圖前綴捷徑來減少冗余,只需將多個(gè)patterns()加到一起
舊的:
- from django.conf.urls.defaults import *
- urlpatterns = patterns('',
- (r'^/?$', 'mysite.views.archive_index'),
- (r'^(\d{4})/([a-z]{3})/$', 'mysite.views.archive_month'),
- (r'^tag/(\w+)/$', 'weblog.views.tag'),
- )
新的:
- from django.conf.urls.defaults import *
- urlpatterns = patterns('mysite.views',
- (r'^/?$', 'archive_index'),
- (r'^(\d{4})/([a-z]{3})/$','archive_month'),
- )
- urlpatterns += patterns('weblog.views',
- (r'^tag/(\w+)/$', 'tag'),
- )
Django只關(guān)心是否有一個(gè)模塊級(jí)的變量urlpatterns,而這個(gè)變量可以被動(dòng)態(tài)構(gòu)建,像上面的例子一樣
命名組
到目前為止在我們所有的URL配置的例子中,我們使用了簡(jiǎn)單的,未命名的正則表達(dá)式組
即我們用括號(hào)包括我們想捕獲的部分URL,Django像傳遞位置參數(shù)一樣把這些捕獲的文本傳遞給視圖方法
在更高級(jí)的使用中,可以使用命名的正則表達(dá)式組來捕獲URL并且傳遞關(guān)鍵字參數(shù)給視圖
關(guān)鍵字參數(shù)與位置參數(shù)
一個(gè)Python方法可以使用關(guān)鍵字參數(shù)或者位置參數(shù)來調(diào)用,它們是一樣的
在關(guān)鍵字參數(shù)調(diào)用中,你指定你想傳遞的參數(shù)名和值
在位置參數(shù)調(diào)用中,你簡(jiǎn)單的傳遞參數(shù)而不指定哪個(gè)參數(shù)匹配哪個(gè)值,關(guān)聯(lián)在參數(shù)順序中隱含
看看下面這個(gè)簡(jiǎn)單的方法:
- def sell(item, price, quantity):
- print "Selling %s unit(s) of %s at %s" % (quantity, item, price)
你可以按方法定義的參數(shù)順序傳遞參數(shù)來使用位置參數(shù)調(diào)用:sell('Socks', '$2.50', 6)
你也可以指定參數(shù)名和參數(shù)值來使用關(guān)鍵字參數(shù)調(diào)用,下面的語句是相等的:
- sell(item='Socks', price='$2.50', quantity=6)
- sell(item='Socks', quantity=6, price='$2.50')
- sell(price='$2.50', item='Socks', quantity=6)
- sell(price='$2.50', quantity=6, item='Socks')
- sell(quantity=6, item='Socks', price='$2.50')
- sell(quantity=6, price='$2.50', item='Socks')
在Python正則表達(dá)式中,命名組的語法是(?P<name>pattern),其中name是組的名字,pattern是要匹配的模式
下面是URL配置的使用未命名組的例子:
- from django.conf.urls.defaults import *
- from mysite import views
- urlpatterns = patterns('',
- (r'^articles/(\d{4})/$', views.year_archive),
- (r'^articles/(\d{4})/(\d{2})/$', views.month_archive),
- )
這里我們使用同樣的URL配置,但是使用命名組來重寫:
- from django.conf.urls.defaults import *
- from mysite import views
- urlpatterns = patterns('',
- (r'^articles/(?P<year>\d{4})/$', views.year_archive),
- (r'^articles/(?P<year>\d{4})/(?P<month>\d{2})/$', views.month_archive),
- )
下面的例子和上面的例子達(dá)到的是同樣的目的,但是有一個(gè)微小的差別,它捕獲的值傳遞給視圖方法時(shí)
使用的是關(guān)鍵字參數(shù)而不是位置參數(shù)
例如,使用未命名組,對(duì)/articles/2006/03的訪問將導(dǎo)致下面的方法調(diào)用:
month_archive(request, '2006', '03')
使用命名組,同樣的請(qǐng)求則會(huì)導(dǎo)致下面的方法調(diào)用:
month_archive(request, year='2006', month='03')
實(shí)踐中使用命名組會(huì)讓你的URL配置更清晰和帶來更少的參數(shù)順序bugs,而且你可以重排視圖方法中
定義的參數(shù)的順序
按照上面的例子,如果你想改變URL使month在year的前面,并且我們使用未命名組,我們必須記得去改
month_archive視圖的參數(shù)順序,而如果我們使用命名組,在URL中改變捕獲的參數(shù)的順序不會(huì)對(duì)視圖造成影響
當(dāng)然,命名組的好處也帶來一些簡(jiǎn)潔上的代價(jià),一些開發(fā)人員認(rèn)為命名組的語法丑陋而且冗長(zhǎng)
匹配和組算法
如果你同時(shí)命名組和未命名組使用兩種方式來處理相同的URL模式,你應(yīng)該清楚Django怎樣處理這種特殊情況
下面是URL配置解析器的算法:
1,如果有命名的參數(shù),Django將使用它,并且忽略未命名的參數(shù)
2,否則,Django視所有的未命名參數(shù)為位置參數(shù)傳遞
3,兩種參數(shù)都有的情況下,Django將傳遞一些額外的關(guān)鍵字參數(shù)作為關(guān)鍵字參數(shù)
參考下面的“向視圖方法傳遞額外選項(xiàng)”
向視圖方法傳遞額外選項(xiàng)
有時(shí)候你發(fā)現(xiàn)你些的視圖方法很相似,只有一些很少的差別
例如,你有兩個(gè)視圖,它們的內(nèi)容除了使用的模板不同其它都一樣:
- # urls.py
- from django.conf.urls.defaults import *
- from mysite import views
- urlpatterns = patterns('',
- (r'^foo/$', views.foo_view),
- (r'^bar/$', views.bar_view),
- )
- # views.py
- from django.shortcuts import render_to_response
- from mysite.models import MyModel
- def foo_view(request):
- m_list = MyModel.objects.filter(is_new=True)
- return render_to_response('template1.html', {'m_list': m_list})
- def bar_view(request):
- m_list = MyModel.objects.filter(is_new=True)
- return render_to_response('template2.html', {'m_list': m_list})
我們?cè)谥貜?fù)我們自己,這是不優(yōu)雅的
首先你可能想通過使用同樣的視圖處理兩種URL來減少冗余,用括號(hào)括住URL來捕獲它,并且在視圖里
通過URL檢查來決定模板:
- # urls.py
- from django.conf.urls.defaults import *
- from mysite import views
- urlpatterns = patterns('',
- (r'^(foo)/$', views.foobar_view),
- (r'^(bar)/$', views.foobar_view),
- )
- # views.py
- from django.shortcuts import render_to_response
- from mysite.models import MyModel
- def foobar_view(request, url):
- m_list = MyModel.objects.filter(is_new=True)
- if url == 'foo':
- template_name = 'template1.html'
- elif url == 'bar':
- template_name = 'template2.html'
- return render_to_response(template_name, {'m_list': m_list})
這種方案的問題是它吧URL和你的代碼耦合在了一起,如果你想把/foo/改名為/fooey/,你必須記得去
更改視圖代碼
優(yōu)雅的方式涉及到一個(gè)交額外URL配置選項(xiàng)的特性,URL配置中每個(gè)模式可能包含了另外一項(xiàng):一個(gè)關(guān)鍵字
參數(shù)的字典,它將被傳遞到視圖方法中
我們可以像下面這樣重寫我們的例子:
- # urls.py
- from django.conf.urls.defaults import *
- from mysite import views
- urlpatterns = patterns('',
- (r'^foo/$', views.foobar_view, {'template_name': 'template1.html'}),
- (r'^bar/$', views.foobar_view, {'template_name': 'template2.html'}),
- )
- # views.py
- from django.shortcuts import render_to_response
- from mysite.models import MyModel
- def foobar_view(request, template_name):
- m_list = MyModel.objects.filter(is_new=True)
- return render_to_response(template_name, {'m_list': m_list})
你可以看到,例子中URL配置指定了template_name,視圖方法只是把它當(dāng)作另一個(gè)參數(shù)
額外URL配置選項(xiàng)技術(shù)是向視圖方法傳遞額外的信息的很好的方式,它在Django綁定的一些
程序中用到,尤其是我們將在第9章碰到的generic views系統(tǒng)
下面是關(guān)于怎樣使用額外URL配置選項(xiàng)技術(shù)的一些方法
偽造捕獲的URL配置值
假設(shè)你已經(jīng)有一些匹配模式的視圖,但是還有一個(gè)URL使用同樣的視圖邏輯卻和模式不匹配
這種情況下你可以通過額外URL配置選項(xiàng)偽造捕獲的URL值來處理具有相同視圖的額外的URL
例如,你可能有一個(gè)從特殊日期顯示數(shù)據(jù)的程序,像下面的URL:
- /mydata/jan/01/
- /mydata/jan/02/
- /mydata/jan/03/
- # ...
- /mydata/dec/30/
- /mydata/dec/31/
這很簡(jiǎn)單就可以處理,你可以像下面這樣捕獲URL(使用命名組語法):
- urlpatterns = patterns('',
- (r'^mydata/(?P<month>\w{3})/(?P<day>\d\d)/$', views.my_view),
- )
視圖方法可能是這樣:
- def my_view(request, month, day):
- # ....
這非常直接,沒有我們沒遇到過的,當(dāng)你像增加另一個(gè)使用my_view的URL并且這個(gè)URL不包括month
或day的時(shí)候,技巧就出現(xiàn)了
例如你想增加另一個(gè)URL /mydata/birthday/,而它應(yīng)該等同與/mydata/jan/06,我們可以像下面這樣
利用額外URL配置選項(xiàng):
- urlpatterns = patterns('',
- (r'^mydata/birthday/$', views.my_view, {'month': 'jan', 'day': '06'}),
- (r'^mydata/(?P<month>\w{3})/(?P<day>\d\d)/$', views.my_view),
- )
這里很酷的地方是,我們根本不需要改變我們的視圖方法,視圖方法僅僅關(guān)心它可以得到month和day參數(shù)
它不關(guān)心這些參數(shù)是否來自于URL捕獲本身或者額外參數(shù)
讓視圖一般化
在代碼中提取公共部分是很好的編程實(shí)踐,例如我們有下面兩個(gè)Python方法:
- def say_hello(person_name):
- print 'Hello, %s' % person_name
- def say_goodbye(person_name):
- print 'Goodbye, %s' % person_name
我們可以把問候語提取出來讓它成為一個(gè)參數(shù):
- def greet(person_name, greeting):
- print '%s, %s' % (greeting, person_name)
你可以通過使用額外URL配置參數(shù)把這個(gè)哲學(xué)應(yīng)用到你的Django視圖中去
這樣你就可以創(chuàng)建高級(jí)抽象視圖,例如:
- # urls.py
- from django.conf.urls.defaults import *
- from mysite import views
- urlpatterns = patterns('',
- (r'^events/$', views.event_list),
- (r'^blog/entries/$', views.entry_list),
- )
- # views.py
- from django.shortcuts import render_to_response
- from mysite.models import Event, BlogEntry
- def event_list(request):
- obj_list = Event.objects.all()
- return render_to_response('mysite/event_list.html', {'event_list': obj_list})
- def entry_list(request):
- obj_list = BlogEntry.objects.all()
- return render_to_response('mysite/blogentry_list.html', {'entry_list': obj_list})
兩個(gè)視圖做的是同一件事情,它們都負(fù)責(zé)顯示對(duì)象列表,因此讓我們把要顯示的對(duì)象的類型抽象出來:
- # urls.py
- from django.conf.urls.defaults import *
- from mysite import models, views
- urlpatterns = patterns('',
- (r'^events/$', views.object_list, {'model': models.Event}),
- (r'^blog/entries/$', views.object_list, {'model': models.BlogEntry}),
- )
- # views.py
- from django.shortcuts import render_to_response
- def object_list(request, model):
- obj_list = model.objects.all()
- template_name = 'mysite/%s_list.html' % model.__name__.lower()
- return render_to_response(template_name, {'object_list': obj_list})
通過這些小改動(dòng),我們突然就有了一個(gè)可重用的,模型不可知的視圖!
從現(xiàn)在開始,任何時(shí)候我們需要一個(gè)對(duì)象列表的視圖,我們都可以簡(jiǎn)單的重用object_list視圖
而不是寫視圖代碼,下面是關(guān)于我們做的事情的注意:
1,我們直接傳遞模型類作為model參數(shù),額外URL配置選項(xiàng)字典可以傳遞任何類型的Python對(duì)象
2,model.objects.all()這一行是一個(gè)鴨子類型:“如果它走起來像鴨子,說話像鴨子,我們就認(rèn)為
它是一只鴨子”,注意代碼并不知道m(xù)odel是什么類型,唯一的前提是model有一個(gè)objects屬性
并且objects有一個(gè)all()方法
3,我們使用model.__name__.lower()來決定模板名,每個(gè)Python類都有__name__屬性,它返回類名
這個(gè)特性對(duì)于現(xiàn)在的情形特別有用,我們直到運(yùn)行時(shí)才知道類的類型
4,這個(gè)例子和上一個(gè)例子的一點(diǎn)不同是,我們傳遞通用的變量名object_list到模板中
我們可以很容易改變這個(gè)變量名為blogentry_list或者event_list,我們把這個(gè)工作留給讀者作為練習(xí)
因?yàn)閿?shù)據(jù)庫驅(qū)動(dòng)的Web站點(diǎn)有許多通用的模式,Django帶來了使用額外技術(shù)的“generic views”來為你
節(jié)省時(shí)間,我們將在下一章講到Django內(nèi)建的generic views
給予視圖配置選項(xiàng)
如果你發(fā)布一個(gè)Django程序,你的用戶可能想擁有一定程度上的配置
這種情況下,向你的視圖添加鉤子來應(yīng)對(duì)人們可能需要一些配置選項(xiàng)是個(gè)好注意
你可以使用額外URL配置參數(shù)來達(dá)到這個(gè)目的
程序中一個(gè)常見的配置是模板名:
- def my_view(request, template_name):
- var = do_something()
- return render_to_response(template_name, {'var': var})
捕獲值的優(yōu)先級(jí)與額外選項(xiàng)
當(dāng)有沖突時(shí),額外URL配置參數(shù)要比捕獲的參數(shù)優(yōu)先級(jí)高
換句話說,如果你的URL配置捕獲了一個(gè)命名組變量和一個(gè)額外URL配置參數(shù),而它們的變量名相同
則額外URL配置參數(shù)值將被使用,例如下面的URL配置:
- from django.conf.urls.defaults import *
- urlpatterns = patterns('',
- (r'^mydata/(?P<id>\d+)/$', views.my_view, {'id': 3}),
- )
在這里正則表達(dá)式和額外的字典都包含id參數(shù),此時(shí)硬編碼的id具有更高的優(yōu)先級(jí)
這意味著/mydata/2/或者/mydata/432432/將被當(dāng)成id設(shè)為3看待,而不管URL所捕獲的值
敏銳的讀者可能注意到這種情況下,在正則表達(dá)式里面捕獲id是純粹在浪費(fèi)時(shí)間
因?yàn)樗闹狄恢睍?huì)被字典的值覆蓋
這些敏銳的讀者是正確的,我們講這些內(nèi)容只是想幫助你避免錯(cuò)誤
使用默認(rèn)視圖參數(shù)
另外一個(gè)方便的技巧是指定視圖的默認(rèn)參數(shù),它告訴視圖如果一個(gè)參數(shù)值是none則使用默認(rèn)值,例如:
- # urls.py
- from django.conf.urls.defaults import *
- urlpatterns = patterns('',
- (r'^blog/$', views.page),
- (r'^blog/page(?P<num>\d+)/$', views.page),
- )
- # views.py
- def page(request, num="1"):
- # Output the appropriate page of blog entries, according to num.
- # ...
這里兩個(gè)URL模式指向了同一個(gè)視圖views.page,但是第一個(gè)模式不會(huì)從URL捕獲任何東西
如果第一個(gè)模式匹配了,page()方法講使用num的默認(rèn)參數(shù)“1”,如果第二個(gè)模式匹配了
page()講使用正則表達(dá)式捕獲的num值
和配置選項(xiàng)一起使用這個(gè)技術(shù)很常見,下面的例子對(duì)給予視圖配置選項(xiàng)的例子做了小小改進(jìn):
- def my_view(request, template_name='mysite/my_view.html'):
- var = do_something()
- return render_to_response(template_name, {'var': var})
特殊情況下的視圖
有時(shí)候你在URL配置里有一個(gè)處理很多URL的模式但是你需要特別指出其中一個(gè)
這種情況下,使用URL配置中把特殊情況放在首位的線性處理方式
例如,Django的admin站點(diǎn)中“添加對(duì)象”頁面是如下配置的:
- urlpatterns = patterns('',
- # ...
- ('^([^/]+)/([^/]+)/add/$', 'django.contrib.admin.views.main.add_stage'),
- # ...
- )
這將匹配像/myblog/entries/add/和/auth/groups/add/這樣的URL
盡管如此,對(duì)于用戶對(duì)象的添加頁面/auth/user/add/是個(gè)特殊情況,例如它不會(huì)顯示所有的表單域,
它顯示兩個(gè)密碼域等等,我們可以通過在視圖中特別指出來以解決這個(gè)問題:
- def add_stage(request, app_label, model_name):
- if app_label == 'auth' and model_name == 'user':
- # do special-case code
- else:
- # do normal code
但是它并不優(yōu)雅,因?yàn)樗裊RL邏輯放在視圖中,更優(yōu)雅的方式是我們利用URL配置是從頂向下解析的方案:
- urlpatterns = patterns('',
- # ...
- ('^auth/user/add/$', 'django.contrib.admin.views.auth.user_add_stage'),
- ('^([^/]+)/([^/]+)/add/$', 'django.contrib.admin.views.main.add_stage'),
- # ...
- )
這樣的話對(duì)于/auth/user/add/的請(qǐng)求將會(huì)被user_add_stage視圖處理,盡管URL也匹配第二種模式
它會(huì)先匹配上面的模式(這是短路邏輯)
從URL捕獲文本的注意點(diǎn)
每個(gè)被捕獲的參數(shù)像普通的Python字符串一樣被傳遞給視圖,而不管正則表達(dá)式匹配的類型
例如,下面的URL配置: