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