程式實現
我們建立第三個檢視來顯示當前時間和加上時間偏差量的時間,設計是這樣的: /time/plus/1/ 顯示當前時間+1個小時的頁面 /time/plus/2/ 顯示當前時間+2個小時的頁面 /time/plus/3/ 顯示當前時間+3個小時的頁面,以此類推。
1. urls.py
1 |
from django.conf.urls.defaults import * |
2 |
from PythonProject.views import hello,
current_datetime, hours_ahead |
4 |
urlpatterns = patterns('', |
6 |
( '^time/$' ,
current_datetime), |
7 |
(r '^time/plus/(\d{1,2})/$' ,
hours_ahead), |
2. views.py
01 |
from django.http import Http404,
HttpResponse |
05 |
return HttpResponse( "Hello
NowaMagic" ) |
07 |
def current_datetime(request): |
08 |
now = datetime.datetime.now() |
09 |
html = "<html><body>It
is now %s.</body></html>" % now |
10 |
return HttpResponse(html) |
12 |
def hours_ahead(request,
offset): |
14 |
dt = datetime.datetime.now() + datetime.timedelta(hours = offset) |
15 |
html = "<html><body>In
%s hour(s), it will be %s.</body></html>" % (offset,
dt) |
16 |
return HttpResponse(html) |
現在訪問 http://127.0.0.1:8000/time/plus/3/,比實際時間多了 3 小時。OK,程式通過。
程式解釋
新手可能會考慮寫不同的檢視函式來處理每個時間偏差量,URL配置看起來就象這樣:
1 |
urlpatterns = patterns('', |
2 |
( '^time/$' ,
current_datetime), |
3 |
( '^time/plus/1/$' ,
one_hour_ahead), |
4 |
( '^time/plus/2/$' ,
two_hours_ahead), |
5 |
( '^time/plus/3/$' ,
three_hours_ahead), |
6 |
( '^time/plus/4/$' ,
four_hours_ahead), |
很明顯,這樣處理是不太妥當的。 不但有很多冗餘的檢視函式,而且整個應用也被限制了只支援 預先定義好的時間段,2小時,3小時,或者4小時。如果哪天我們要實現 5 小時,我們就 不得不再單獨建立新的檢視函式和配置URL,既重複又混亂。 我們需要在這裡做一點抽象,提取一些共同的東西出來。
那麼,我們如何設計程式來處理任意數量的時差? 答案是:使用萬用字元(wildcard URLpatterns)。正如我們之前提到過,一個URL模式就是一個正規表示式。因此,這裡可以使用d+來匹配1個以上的數字。
1 |
urlpatterns = patterns('', |
3 |
(r '^time/plus/(\d+)/$' ,
hours_ahead), |
這個URL模式將匹配類似 /time/plus/2/ , /time/plus/25/ ,甚至 /time/plus/100000000000/ 的任何URL。 更進一步,讓我們把它限制在最大允許99個小時,這樣我們就只允許一個或兩個數字,正規表示式的語法就是 \d{1,2} :
1 |
(r '^time/plus/(\d{1,2})/$' ,
hours_ahead), |
另外一個重點,正規表示式字串的開頭字母“r”。 它告訴python這是個原始字串,不需要處理裡面的反斜槓(轉義字元)。 在普通Python字串中,反斜槓用於特殊字元的轉義。比如n轉義成一個換行符。
當你用r把它標示為一個原始字串後,Python不再視其中的反斜槓為轉義字元。也就是說,“n”是兩個字串:“”和“n”。由於反斜槓在Python程式碼和正規表示式中有衝突,因此建議你在Python定義正規表示式時都使用原始字串。 從現在開始,本文所有URL模式都用原始字串。
現在我們已經設計了一個帶萬用字元的URL,我們需要一個方法把它傳遞到檢視函式裡去,這樣 我們只用一個檢視函式就可以處理所有的時間段了。 我們使用圓括號把引數在URL模式裡標識 出來。 在這個例子中,我們想要把這些數字作為引數,用圓括號把 \d{1,2} 包圍起來:
1 |
(r '^time/plus/(\d{1,2})/$' ,
hours_ahead), |
如果你熟悉正規表示式,那麼你應該已經瞭解,正規表示式也是用圓括號來從文字里 提取 資料的。
讓我們逐行分析一下 views.py 的程式碼:
檢視函式, hours_ahead , 有 兩個 引數: request 和 offset .
request 是一個 HttpRequest 物件, 就像在 current_datetime 中一樣. 再說一次好了: 每一個檢視 總是 以一個 HttpRequest 物件作為 它的第一個引數。
offset 是從匹配的URL裡提取出來的。 例如:如果請求URL是/time/plus/3/,那麼offset將會是3;如果請求URL是/time/plus/21/,那麼offset將會是21。請注意:捕獲值永遠都是字串(string)型別,而不會是整數(integer)型別,即使這個字串全由數字構成(如:“21”)。
在這裡我們命名變數為 offset ,你也可以任意命名它,只要符合Python 的語法。 變數名是無關緊要的,重要的是它的位置,它是這個函式的第二個 引數 (在 request 的後面)。 你還可以使用關鍵字來定義它,而不是用 位置。
我們在這個函式中要做的第一件事情就是在 offset 上呼叫 int() . 這會把這個字串值轉換為整數。
請留意:如果你在一個不能轉換成整數型別的值上呼叫int(),Python將丟擲一個ValueError異常。如:int(‘foo’)。在這個例子中,如果我們遇到ValueError異常,我們將轉為丟擲django.http.Http404異常——正如你想象的那樣:最終顯示404頁面(提示資訊:頁面不存在)。
機靈的讀者可能會問: 我們在URL模式中用正規表示式(d{1,2})約束它,僅接受數字怎麼樣?這樣無論如何,offset都是由數字構成的。 答案是:我們不會這麼做,因為URLpattern提供的是“適度但有用”級別的輸入校驗。萬一這個檢視函式被其它方式呼叫,我們仍需自行檢查ValueError。 實踐證明,在實現檢視函式時,不臆測引數值的做法是比較好的。 鬆散耦合,還記得麼?
下一行,計算當前日期/時間,然後加上適當的小時數。 在current_datetime檢視中,我們已經見過datetime.datetime.now()。這裡新的概念是執行日期/時間的算術操作。我們需要建立一個datetime.timedelta物件和增加一個datetime.datetime物件。 結果儲存在變數dt中。
這一行還說明了,我們為什麼在offset上呼叫int()——datetime.timedelta函式要求hours引數必須為整數型別。
這行和前面的那行的的一個微小差別就是,它使用帶有兩個值的Python的格式化字串功能, 而不僅僅是一個值。 因此,在字串中有兩個 %s 符號和一個以進行插入的值的元組: (offset, dt) 。
最終,返回一個HTML的HttpResponse。
OK,程式解釋完畢。用 Django 實現動態 URL 大概就這樣。
五、Q&A
*先寫URLpattern還是先寫檢視?
根據個人喜好,一般設定主框架時,可以先寫出URLpattern,在開發具體的檢視;細節處,可以先寫好檢視函式,然後在掛到
URLpattern上。
*正規表示式
. (dot) 任意單一字元
\d 任意一位數字
[A-Z] A 到 Z中任意一個字元(大寫)
[a-z] a 到 z中任意一個字元(小寫)
[A-Za-z] a 到 z中任意一個字元(不區分大小寫)
+ 匹配一個或更多 (例如, \d+ 匹配一個或 多個數字字元)
[^/]+ 一個或多個不為‘/’的字元
? 零個或一個之前的表示式(例如:\d? 匹配零個或一個數字)
* 匹配0個或更多 (例如, \d* 匹配0個 或更多數字字元)
{1,3} 介於一個和三個(包含)之前的表示式(例如,\d{1,3}匹配一個或兩個或三個數字)