前一章中,我們解釋了如何建立一個 Django 項目并啟動 Django 開發服務器。 在這一章,你將會學到用Django創建動態網頁的基本知識。
正如我們的第一個目標,創建一個網頁,用來輸出這個著名的示例信息:
Hello world.
如果你曾經發布過Hello world頁面,但是沒有使用網頁框架,只是簡單的在hello.html文本文件中輸入Hello World,然后上傳到任意的一個網頁服務器上。 注意,在這個過程中,你已經說明了兩個關于這個網頁的關鍵信息: 它包括(字符串 "Hello world")和它的URL( http://www.example.com/hello.html , 如果你把文件放在子目錄,也可能是 http://www.example.com/files/hello.html)。
使用Django,你會用不同的方法來說明這兩件事 頁面的內容是靠view function(視圖函數) 來產生,URL定義在 URLconf 中。首先,我們先寫一個Hello World視圖函數。
在上一章使用django-admin.py startproject制作的mysite文件夾中,創建一個叫做views.py的空文件。這個Python模塊將包含這一章的視圖。 請留意,Django對于view.py的文件命名沒有特別的要求,它不在乎這個文件叫什么。但是根據約定,把它命名成view.py是個好主意,這樣有利于其他開發者讀懂你的代碼,正如你很容易的往下讀懂本文。
我們的Hello world視圖非常簡單。 這些是完整的函數和導入聲明,你需要輸入到views.py文件:
from django.http import HttpResponse
def hello(request):
return HttpResponse("Hello world")
我們逐行逐句地分析一遍這段代碼:
首先,我們從 django.http 模塊導入(import) HttpResponse 類。參閱附錄 H 了解更多關于 HttpRequest和 HttpResponse 的細節。 我們需要導入這些類,因為我們會在后面用到。
接下來,我們定義一個叫做hello 的視圖函數。
每個視圖函數至少要有一個參數,通常被叫作request。 這是一個觸發這個視圖、包含當前Web請求信息的對象,是類django.http.HttpRequest的一個實例。在這個示例中,我們雖然不用request做任何事情,然而它仍必須是這個視圖的第一個參數。
注意視圖函數的名稱并不重要;并不一定非得以某種特定的方式命名才能讓 Django 識別它。 在這里我們把它命名為:hello,是因為這個名稱清晰的顯示了視圖的用意。同樣地,你可以用諸如:hello_wonderful_beautiful_world,這樣難看的短句來給它命名。 在下一小節(Your First URLconf),將告訴你Django是如何找到這個函數的。
這個函數只有簡單的一行代碼: 它僅僅返回一個HttpResponse對象,這個對象包含了文本“Hello world”。
這里主要講的是: 一個視圖就是Python的一個函數。這個函數第一個參數的類型是HttpRequest;它返回一個HttpResponse實例。為了使一個Python的函數成為一個Django可識別的視圖,它必須滿足這兩個條件。 (也有例外,但是我們稍后才會接觸到。
現在,如果你再運行:python manage.py runserver,你還將看到Django的歡迎頁面,而看不到我們剛才寫的Hello world顯示頁面。 那是因為我們的mysite項目還對hello視圖一無所知。我們需要通過一個詳細描述的URL來顯式的告訴它并且激活這個視圖。 (繼續我們剛才類似發布靜態HTML文件的例子。現在我們已經創建了HTML文件,但還沒有把它上傳至服務器的目錄。)為了綁定視圖函數和URL,我們使用URLconf。
URLconf 就像是 Django 所支撐網站的目錄。 它的本質是 URL 模式以及要為該 URL 模式調用的視圖函數之間的映射表。 你就是以這種方式告訴 Django,對于這個 URL 調用這段代碼,對于那個 URL 調用那段代碼。 例如,當用戶訪問/foo/時,調用視圖函數foo_view(),這個視圖函數存在于Python模塊文件view.py中。
前一章中執行 django-admin.py startproject 時,該腳本會自動為你建了一份 URLconf(即 urls.py 文件)。 默認的urls.py會像下面這個樣子:
from django.conf.urls.defaults import *
# Uncomment the next two lines to enable the admin:
# from django.contrib import admin
# admin.autodiscover()
urlpatterns = patterns('',
# Example:
# (r'^mysite/', include('mysite.foo.urls')),
# Uncomment the admin/doc line below and add 'django.contrib.admindocs'
# to INSTALLED_APPS to enable admin documentation:
# (r'^admin/doc/', include('django.contrib.admindocs.urls')),
# Uncomment the next line to enable the admin:
# (r'^admin/', include(admin.site.urls)),
)
默認的URLconf包含了一些被注釋起來的Django中常用的功能,僅僅只需去掉這些注釋就可以開啟這些功能. 下面是URLconf中忽略被注釋的行后的實際內容
from django.conf.urls.defaults import *
urlpatterns = patterns('',
)
讓我們逐行解釋一下代碼:
當前應該注意是 urlpatterns 變量, Django 期望能從 ROOT_URLCONF 模塊中找到它。 該變量定義了 URL 以及用于處理這些 URL 的代碼之間的映射關系。 默認情況下,URLconf 所有內容都被注釋起來了——Django 應用程序還是白版一塊。 (注:那是上一節中Django怎么知道顯示歡迎頁面的原因。 如果 URLconf 為空,Django 會認定你才創建好新項目,因此也就顯示那種信息。
如果想在URLconf中加入URL和view,只需增加映射URL模式和view功能的Python tuple即可. 這里演示如何添加view中hello功能.
from django.conf.urls.defaults import *
from mysite.views import hello
urlpatterns = patterns('',
('^hello/$', hello),
)
請留意:為了簡潔,我們移除了注釋代碼。 如果你喜歡的話,你可以保留那些行。)
我們做了兩處修改。
簡單來說,我們只是告訴 Django,所有指向 URL /hello/ 的請求都應由 hello 這個視圖函數來處理。
Python 搜索路徑
Python 搜索路徑 就是使用 import 語句時,Python 所查找的系統目錄清單。
舉例來說,假定你將 Python 路徑設置為
['','/usr/lib/python2.4/site-packages','/home/username/djcode/']
如果執行代碼from foo import bar ,Python 將會首先在當前目錄查找 foo.py 模塊( Python 路徑第一項的空字符串表示當前目錄)。 如果文件不存在,Python將查找 /usr/lib/python2.4/site-packages/foo.py
文件。
如果你想看Python搜索路徑的值,運行Python交互解釋器,然后輸入:
>>> import sys
>>> print sys.path
通常,你不必關心 Python 搜索路徑的設置。 Python 和 Django 會在后臺自動幫你處理好。
討論一下URLpattern的語法是值得的,因為它不是顯而易見的。 雖然我們想匹配地址/hello/,但是模式看上去與這有點差別。 這就是為什么:
Django在檢查URL模式前,移除每一個申請的URL開頭的斜杠(/)。 這意味著我們為/hello/寫URL模式不用包含斜杠(/)。(剛開始,這樣可能看起來不直觀,但這樣的要求簡化了許多工作,如URL模式內嵌,我們將在第八章談及。)
模式包含了一個尖號(^)和一個美元符號($)。這些都是正則表達式符號,并且有特定的含義: 上箭頭要求表達式對字符串的頭部進行匹配,美元符號則要求表達式對字符串的尾部進行匹配。
最好還是用范例來說明一下這個概念。 如果我們用尾部不是$的模式’^hello/’,那么任何以/hello/開頭的URL將會匹配,例如:/hello/foo 和/hello/bar,而不僅僅是/hello/。類似地,如果我們忽略了尖號(^),即’hello/$’,那么任何以hello/結尾的URL將會匹配,例如:/foo/bar/hello/。如果我們簡單使用hello/,即沒有^開頭和$結尾,那么任何包含hello/的URL將會匹配,如:/foo/hello/bar。因此,我們使用這兩個符號以確保只有/hello/匹配,不多也不少。
你大多數的URL模式會以^開始、以$結束,但是擁有復雜匹配的靈活性會更好。
你可能會問:如果有人申請訪問/hello(尾部沒有斜杠/)會怎樣。 因為我們的URL模式要求尾部有一個斜杠(/),那個申請URL將不匹配。 然而,默認地,任何不匹配或尾部沒有斜杠(/)的申請URL,將被重定向至尾部包含斜杠的相同字眼的URL。 (這是受配置文件setting中APPEND_SLASH項控制的,參見附件D。)
如果你是喜歡所有URL都以’/’結尾的人(Django開發者的偏愛),那么你只需要在每個URL后添加斜杠,并且設置”APPEND_SLASH”為”True”. 如果不喜歡URL以斜杠結尾或者根據每個URL來決定,那么需要設置”APPEND_SLASH”為”False”,并且根據你自己的意愿來添加結尾斜杠/在URL模式后.
另外需要注意的是,我們把hello視圖函數作為一個對象傳遞,而不是調用它。 這是 Python (及其它動態語言的) 的一個重要特性: 函數是一級對象(first-class objects), 也就是說你可以像傳遞其它變量一樣傳遞它們。 很酷吧?
啟動Django開發服務器來測試修改好的 URLconf, 運行命令行 python manage.py runserver 。 (如果你讓它一直運行也可以,開發服務器會自動監測代碼改動并自動重新載入,所以不需要手工重啟) 開發服務器的地址是http://127.0.0.1:8000/ ,打開你的瀏覽器訪問 http://127.0.0.1:8000/hello/ 。 你就可以看到輸出結果了。 開發服務器將自動檢測Python代碼的更改來做必要的重新加載, 所以你不需要重啟Server在代碼更改之后。服務器運行地址 http://127.0.0.1:8000/ ,所以打開瀏覽器直接輸入 http://127.0.0.1:8000/hello/ ,你將看到由你的Django視圖輸出的Hello world。
萬歲! 你已經創建了第一個Django的web頁面。
正則表達式
正則表達式 (或 regexes ) 是通用的文本模式匹配的方法。 Django URLconfs 允許你 使用任意的正則表達式來做強有力的URL映射,不過通常你實際上可能只需要使用很少的一 部分功能。 這里是一些基本的語法。
符號 | 匹配 |
---|---|
. (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}匹配一個或兩個或三個數字) |
有關正則表達式的更多內容,請訪問 http://www.djangoproject.com/r/python/re-module/.
目前,我們的URLconf只定義了一個單獨的URL模式: 處理URL /hello/ 。 當請求其他URL會怎么樣呢?
讓我們試試看,運行Django開發服務器并訪問類似 http://127.0.0.1:8000/goodbye/ 或者http://127.0.0.1:8000/hello/subdirectory/ ,甚至 http://127.0.0.1:8000/ (網站根目錄)。 你將會看到一個 “Page not found” 頁面(圖 3-1)。 因為你的URL申請在URLconf中沒有定義,所以Django顯示這條信息。
圖3-1: Django的404 Error頁
這個頁面比原始的404錯誤信息更加實用。 它同時精確的告訴你Django調用哪個URLconf及其包含的每個模式。 這樣,你應該能了解到為什么這個請求會拋出404錯誤。
當然,這些敏感的信息應該只呈現給你-開發者。 如果是部署到了因特網上的站點就不應該暴露 這些信息。 出于這個考慮,這個“Page not found”頁面只會在 調試模式(debug mode) 下 顯示。 我們將在以后說明怎么關閉調試模式。
在最后一節,如果你想通過http://127.0.0.1:8000/看網站根目錄你將看到一個404錯誤消息。Django不會增加任何東西在網站根目錄,在任何情況下這個URL都不是特殊的 就像在URLconf中的其他條目一樣,它也依賴于指定給它的URL模式.
盡管匹配網站根目錄的URL模式不能想象,但是還是值得提一下的. 當為網站根目錄實現一個視圖,你需要使用URL模式‘^$’
, 它代表一個空字符串。 例如:
from mysite.views import hello, my_homepage_view
urlpatterns = patterns('',
url(r'^$', my_homepage_view),
# ...
)
在繼續我們的第二個視圖功能之前,讓我們暫停一下去了解更多一些有關Django是怎么工作的知識. 具體地說,當你通過在瀏覽器里敲http://127.0.0.1:8000/hello/來訪問Hello world消息得時候,Django在后臺有些什么動作呢?
所有均開始于setting文件。當你運行python manage.py runserver,腳本將在于manage.py同一個目錄下查找名為setting.py的文件。這個文件包含了所有有關這個Django項目的配置信息,均大寫: TEMPLATE_DIRS , DATABASE_NAME , 等. 最重要的設置時ROOT_URLCONF,它將作為URLconf告訴Django在這個站點中那些Python的模塊將被用到
還記得什么時候django-admin.py startproject創建文件settings.py和urls.py嗎?自動創建的settings.py包含一個ROOT_URLCONF配置用來指向自動產生的urls.py. 打開文件settings.py你將看到如下:
ROOT_URLCONF = 'mysite.urls'
相對應的文件是mysite/urls.py
當訪問 URL /hello/ 時,Django 根據 ROOT_URLCONF 的設置裝載 URLconf 。 然后按順序逐個匹配URLconf里的URLpatterns,直到找到一個匹配的。 當找到這個匹配 的URLpatterns就調用相關聯的view函數,并把HttpRequest 對象作為第一個參數。 (稍后再給出 HttpRequest 的更多信息) (我們將在后面看到HttpRequest的標準)
正如我們在第一個視圖例子里面看到的,一個視圖功能必須返回一個HttpResponse。 一旦做完,Django將完成剩余的轉換Python的對象到一個合適的帶有HTTP頭和body的Web Response,(例如,網頁內容)。
總結一下:
你現在知道了怎么做一個 Django-powered 頁面了,真的很簡單,只需要寫視圖函數并用 URLconfs把它們和URLs對應起來。 你可能會認為用一系列正則表達式將URLs映射到函數也許會比較慢,但事實卻會讓你驚訝。
我們的Hello world視圖是用來演示基本的Django是如何工作的,但是它不是一個動態網頁的例子,因為網頁的內容一直是一樣的. 每次去查看/hello/,你將會看到相同的內容,它類似一個靜態HTML文件。
我們的第二個視圖,將更多的放些動態的東西例如當前日期和時間顯示在網頁上 這將非常好,簡單的下一步,因為它不引入了數據庫或者任何用戶的輸入,僅僅是輸出顯示你的服務器的內部時鐘. 它僅僅有限度的比Helloworld刺激一些,但是它將演示一些新的概念
這個視圖需要做兩件事情: 計算當前日期和時間,并返回包含這些值的HttpResponse 如果你對python很有經驗,那肯定知道在python中需要利用datetime模塊去計算時間 下面演示如何去使用它:
>>> import datetime
>>> now = datetime.datetime.now()
>>> now
datetime.datetime(2008, 12, 13, 14, 9, 39, 2731)
>>> print now
2008-12-13 14:09:39.002731
以上代碼很簡單,并沒有涉及Django。 它僅僅是Python代碼。 需要強調的是,你應該意識到哪些是純Python代碼,哪些是Django特性代碼。 (見上) 因為你學習了Django,希望你能將Django的知識應用在那些不一定需要使用Django的項目上。
為了讓Django視圖顯示當前日期和時間,我們僅需要把語句:datetime.datetime.now()放入視圖函數,然后返回一個HttpResponse對象即可。代碼如下:
from django.http import HttpResponse
import datetime
def current_datetime(request):
now = datetime.datetime.now()
html = "<html><body>It is now %s.</body></html>" % now
return HttpResponse(html)
正如我們的hello函數一樣,這個函數也保存在view.py中。為了簡潔,上面我們隱藏了hello函數。下面是完整的view.py文件內容:
from django.http import HttpResponse
import datetime
def hello(request):
return HttpResponse("Hello world")
def current_datetime(request):
now = datetime.datetime.now()
html = "<html><body>It is now %s.</body></html>" % now
return HttpResponse(html)
(從現在開始,如非必要,本文不再重復列出先前的代碼。 你應該懂得識別哪些是新代碼,哪些是先前的。) (見上)
讓我們分析一下改動后的views.py:
在文件頂端,我們添加了一條語句:import datetime。這樣就可以計算日期了。
函數中的第一行代碼計算當前日期和時間,并以 datetime.datetime 對象的形式保存為局部變量 now 。
函數的第二行代碼用 Python 的格式化字符串(format-string)功能構造了一段 HTML 響應。 字符串中的%s是占位符,字符串后面的百分號表示用它后面的變量now的值來代替%s。變量%s是一個datetime.datetime對象。它雖然不是一個字符串,但是%s(格式化字符串)會把它轉換成字符串,如:2008-12-13 14:09:39.002731。這將導致HTML的輸出字符串為:It is now 2008-12-13 14:09:39.002731。(目前HTML是有錯誤的,但我們這樣做是為了保持例子的簡短。)
最后,正如我們剛才寫的hello函數一樣,視圖返回一個HttpResponse對象,它包含生成的響應。
添加上述代碼之后,還要在urls.py中添加URL模式,以告訴Django由哪一個URL來處理這個視圖。 用/time/之類的字眼易于理解:
from django.conf.urls.defaults import *
from mysite.views import hello, current_datetime
urlpatterns = patterns('',
('^hello/$', hello),
('^time/$', current_datetime),
)
這里,我們修改了兩個地方。 首先,在頂部導入current_datetime函數; 其次,也是比較重要的:添加URL模式來映射URL中的/time/和新視圖。 理解了么?
寫好視圖并且更新URLconf之后,運行命令python manage.py runserver以啟動服務,在瀏覽器中輸入http://127.0.0.1:8000/time/。 你將看到當前的日期和時間。
Django時區
視乎你的機器,顯示的日期與時間可能和實際的相差幾個小時。 這是因為Django是有時區意識的,并且默認時區為America/Chicago。 (它必須有個值,它的默認值是Django的誕生地:美國/芝加哥)如果你處在別的時區,你需要在settings.py文件中更改這個值。請參見它里面的注釋,以獲得最新世界時區列表。
現在是好時機來指出Django和URL配置背后的哲學: 松耦合 原則。 簡單的說,松耦合是一個 重要的保證互換性的軟件開發方法。
Django的URL配置就是一個很好的例子。 在Django的應用程序中,URL的定義和視圖函數之間是松 耦合的,換句話說,決定URL返回哪個視圖函數和實現這個視圖函數是在兩個不同的地方。 這使得 開發人員可以修改一塊而不會影響另一塊。
例如,考慮一下current_datetime視圖。 如果我們想把它的URL 從原來的 /time/ 改變到 /currenttime/ ,我們只需要快速的修改一下URL配置即可, 不用擔心這個函數的內部實現。 同樣的,如果我們想要修改這個函數的內部實現也不用擔心會影響 到對應的URL。
此外,如果我們想要輸出這個函數到 一些 URL, 我們只需要修改URL配置而不用 去改動視圖的代碼。 在這個例子里,current_datetime被兩個URL使用。 這是一個故弄玄虛的例子,但這個方法遲早會用得上。
urlpatterns = patterns('',
('^hello/$', hello),
('^time/$', current_datetime),
('^another-time-page/$', current_datetime),
)
URLconf和視圖是松耦合的。 我們將在本書中繼續給出這一重要哲學的相關例子。
在我們的current_datetime
視圖范例中,盡管內容是動態的,但是URL ( /time/ )是靜態的。 在 大多數動態web應用程序,URL通常都包含有相關的參數。 舉個例子,一家在線書店會為每一本書提供一個URL,如:/books/243/、/books/81196/。
讓我們創建第三個視圖來顯示當前時間和加上時間偏差量的時間,設計是這樣的: /time/plus/1/ 顯示當前時間+1個小時的頁面 /time/plus/2/ 顯示當前時間+2個小時的頁面 /time/plus/3/ 顯示當前時間+3個小時的頁面,以此類推。
新手可能會考慮寫不同的視圖函數來處理每個時間偏差量,URL配置看起來就象這樣:
urlpatterns = patterns('',
('^time/$', current_datetime),
('^time/plus/1/$', one_hour_ahead),
('^time/plus/2/$', two_hours_ahead),
('^time/plus/3/$', three_hours_ahead),
('^time/plus/4/$', four_hours_ahead),
)
很明顯,這樣處理是不太妥當的。 不但有很多冗余的視圖函數,而且整個應用也被限制了只支持 預先定義好的時間段,2小時,3小時,或者4小時。 如果哪天我們要實現 5 小時,我們就 不得不再單獨創建新的視圖函數和配置URL,既重復又混亂。 我們需要在這里做一點抽象,提取 一些共同的東西出來。
關于漂亮URL的一點建議
如果你有其它web平臺的開發經驗(如PHP或Java),你可能會想:嘿!讓我們用查詢字符串參數吧! 就像/time/plus?hours=3里面的小時應該在查詢字符串中被參數hours指定(問號后面的是參數)。
你 可以 在Django里也這樣做 (如果你真的想要這樣做,我們稍后會告訴你怎么做), 但是Django的一個核心理念就是URL必須看起來漂亮。 URL /time/plus/3/ 更加清晰, 更簡單,也更有可讀性,可以很容易的大聲念出來,因為它是純文本,沒有查詢字符串那么 復雜。 漂亮的URL就像是高質量的Web應用的一個標志。
Django的URL配置系統可以使你很容易的設置漂亮的URL,而盡量不要考慮它的 反面 。
那么,我們如何設計程序來處理任意數量的時差? 答案是:使用通配符(wildcard URLpatterns)。正如我們之前提到過,一個URL模式就是一個正則表達式。因此,這里可以使用d+來匹配1個以上的數字。
urlpatterns = patterns('',
# ...
(r'^time/plus/\d+/$', hours_ahead),
# ...
)
這里使用# …來表示省略了其它可能存在的URL模式定義。 (見上)
這個URL模式將匹配類似 /time/plus/2/ , /time/plus/25/ ,甚至 /time/plus/100000000000/ 的任何URL。 更進一步,讓我們把它限制在最大允許99個小時, 這樣我們就只允許一個或兩個數字,正則表達式的語法就是\d{1,2} :
(r'^time/plus/\d{1,2}/$', hours_ahead),
備注
在建造Web應用的時候,盡可能多考慮可能的數據輸入是很重要的,然后決定哪些我們可以接受。 在這里我們就設置了99個小時的時間段限制。
另外一個重點,正則表達式字符串的開頭字母“r”。 它告訴Python這是個原始字符串,不需要處理里面的反斜杠(轉義字符)。 在普通Python字符串中,反斜杠用于特殊字符的轉義。比如n轉義成一個換行符。 當你用r把它標示為一個原始字符串后,Python不再視其中的反斜杠為轉義字符。也就是說,“n”是兩個字符串:“”和“n”。由于反斜杠在Python代碼和正則表達式中有沖突,因此建議你在Python定義正則表達式時都使用原始字符串。 從現在開始,本文所有URL模式都用原始字符串。
現在我們已經設計了一個帶通配符的URL,我們需要一個方法把它傳遞到視圖函數里去,這樣 我們只用一個視圖函數就可以處理所有的時間段了。 我們使用圓括號把參數在URL模式里標識 出來。 在這個例子中,我們想要把這些數字作為參數,用圓括號把 \d{1,2} 包圍起來:
(r'^time/plus/(\d{1,2})/$', hours_ahead),
如果你熟悉正則表達式,那么你應該已經了解,正則表達式也是用圓括號來從文本里 提取 數據的。
最終的URLconf包含上面兩個視圖,如:
from django.conf.urls.defaults import *
from mysite.views import hello, current_datetime, hours_ahead
urlpatterns = patterns('',
(r'^hello/$', hello),
(r'^time/$', current_datetime),
(r'^time/plus/(\d{1,2})/$', hours_ahead),
)
現在開始寫 hours_ahead 視圖。
編碼次序
這個例子中,我們先寫了URLpattern ,然后是視圖,但是在前面的例子中, 我們先寫了視圖,然后是URLpattern 。 哪一種方式比較好?
嗯,怎么說呢,每個開發者是不一樣的。
如果你是喜歡從總體上來把握事物(注: 或譯為“大局觀”)類型的人,你應該會想在項目開始 的時候就寫下所有的URL配置。
如果你從更像是一個自底向上的開發者,你可能更喜歡先寫視圖, 然后把它們掛接到URL上。 這同樣是可以的。
最后,取決與你喜歡哪種技術,兩種方法都是可以的。 (見上)
hours_ahead 和我們以前寫的 current_datetime 很象,關鍵的區別在于: 它多了一個額外參數,時間差。 以下是view代碼:
from django.http import Http404, HttpResponse
import datetime
def hours_ahead(request, offset):
try:
offset = int(offset)
except ValueError:
raise Http404()
dt = datetime.datetime.now() + datetime.timedelta(hours=offset)
html = "<html><body>In %s hour(s), it will be %s.</body></html>" % (offset, dt)
return HttpResponse(html)
讓我們逐行分析一下代碼:
視圖函數, 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”)。
(從技術上來說,捕獲值總是Unicode objects,而不是簡單的Python字節串,但目前不需要擔心這些差別。)
在這里我們命名變量為 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。 如今,這種方式已經過時了。
在完成視圖函數和URL配置編寫后,啟動Django開發服務器,用瀏覽器訪問http://127.0.0.1:8000/time/plus/3/ 來確認它工作正常。 然后是 http://127.0.0.1:8000/time/plus/5/ 。再然后是 http://127.0.0.1:8000/time/plus/24/ 。最后,訪問 http://127.0.0.1:8000/time/plus/100/ 來檢驗URL配置里設置的模式是否只 接受一個或兩個數字;Django會顯示一個 Page not found error 頁面, 和以前看到的 404 錯誤一樣。 訪問URL http://127.0.0.1:8000/time/plus/ (沒有 定義時間差) 也會拋出404錯誤。
花幾分鐘時間欣賞一下我們寫好的Web應用程序,然后我們再來搞點小破壞。 我們故意在 views.py 文件中引入一項 Python 錯誤,注釋掉 hours_ahead 視圖中的 offset = int(offset) 一行。
def hours_ahead(request, offset):
# try:
# offset = int(offset)
# except ValueError:
# raise Http404()
dt = datetime.datetime.now() + datetime.timedelta(hours=offset)
html = "<html><body>In %s hour(s), it will be %s.</body></html>" % (offset, dt)
return HttpResponse(html)
啟動開發服務器,然后訪問 /time/plus/3/ 。你會看到一個包含大量信息的出錯頁,最上面 的一條 TypeError信息是: "unsupported type for timedelta hours component: unicode" .
怎么回事呢? 是的, datetime.timedelta 函數要求 hours 參數必須為整型, 而我們注釋掉了將 offset 轉為整型的代碼。 這樣導致 datetime.timedelta 彈出 TypeError 異常。
這個例子是為了展示 Django 的出錯頁面。 我們來花些時間看一看這個出錯頁,了解一下其中 給出了哪些信息。
以下是值得注意的一些要點:
在頁面頂部,你可以得到關鍵的異常信息: 異常數據類型、異常的參數 (如本例中的 "unsupported type")、在哪個文件中引發了異常、出錯的行號等等。
在關鍵異常信息下方,該頁面顯示了對該異常的完整 Python 追蹤信息。 這類似于你在 Python 命令行解釋器中獲得的追溯信息,只不過后者更具交互性。 對棧中的每一幀,Django 均顯示了其文件名、函數或方法名、行號及該行源代碼。
點擊該行代碼 (以深灰色顯示),你可以看到出錯行的前后幾行,從而得知相關上下文情況。點擊棧中的任何一幀的“Local vars”可以看到一個所有局部變量的列表,以及在出錯 那一幀時它們的值。 這些調試信息相當有用。
注意“Traceback”下面的“Switch to copy-and-paste view”文字。 點擊這些字,追溯會 切換另一個視圖,它讓你很容易地復制和粘貼這些內容。 當你想同其他人分享這些異常 追溯以獲得技術支持時(比如在 Django 的 IRC 聊天室或郵件列表中),可以使用它。
你按一下下面的“Share this traceback on a public Web site”按鈕,它將會完成這項工作。 點擊它以傳回追溯信息至http://www.dpaste.com/,在那里你可以得到一個單獨的URL并與其他人分享你的追溯信息。
接下來的“Request information”部分包含了有關產生錯誤的 Web 請求的大量信息: GET 和 POST、cookie 值、元數據(象 CGI 頭)。 在附錄H里給出了request的對象的 完整參考。
Request信息的下面,“Settings”列出了 Django 使用的具體配置信息。 (我們已經提及過ROOT_URLCONF,接下來我們將向你展示各式的Django設置。 附錄D覆蓋了所有可用的設置。)
Django 的出錯頁某些情況下有能力顯示更多的信息,比如模板語法錯誤。 我們討論 Django 模板系統時再說它們。 現在,取消 offset = int(offset) 這行的注釋,讓它重新正常 工作。
不知道你是不是那種使用小心放置的 print 語句來幫助調試的程序員? 你其實可以用 Django 出錯頁來做這些,而不用 print 語句。 在你視圖的任何位置,臨時插入一個 assert False 來觸發出錯頁。 然后,你就可以看到局部變量和程序語句了。 這里有個使用hours_ahead視圖的例子:
def hours_ahead(request, offset):
try:
offset = int(offset)
except ValueError:
raise Http404()
dt = datetime.datetime.now() + datetime.timedelta(hours=offset)
assert False
html = "<html><body>In %s hour(s), it will be %s.</body></html>" % (offset, dt)
return HttpResponse(html)
最后,很顯然這些信息很多是敏感的,它暴露了你 Python 代碼的內部結構以及 Django 配置,在 Internet 上公開這信息是很愚蠢的。 不懷好意的人會嘗試使用它攻擊你的 Web 應用程序,做些下流之事。 因此,Django 出錯信息僅在 debug 模式下才會顯現。 我們稍后 說明如何禁用 debug 模式。 現在,你只要知道 Django 服務器在你開啟它時默認運行在 debug 模式就行了。 (聽起來很熟悉? 頁面沒有發現錯誤,如前所述,工作正常。)
目前為止,我們已經寫好了視圖函數和硬編碼的HTML。 在演示核心概念時,我們所作的是為了保持簡單。但是在現實世界中,這差不多總是個壞主意。
幸運的是,Django內建有一個簡單有強大的模板處理引擎來讓你分離兩種工作: 下一章,我們將學習模板引擎。