第三章 視圖和URL配置 html
前一章中,咱們解釋瞭如何創建一個 Django 項目並啓動 Django 開發服務器。 在這一章,你將會學到用Django建立動態網頁的基本知識。 python
你的第一個基於Django的頁面: Hello World 程序員
正如咱們的第一個目標,建立一個網頁,用來輸出這個著名的示例信息: web
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
第一份視圖: 瀏覽器
在上一章使用django-admin.py startproject製做的mysite文件夾中,建立一個叫作views.py的空文件。這個Python模塊將包含這一章的視圖。 請留意,Django對於view.py的文件命名沒有特別的要求,它不在意這個文件叫什麼。可是根據約定,把它命名成view.py是個好主意,這樣有利於其餘開發者讀懂你的代碼,正如你很容易的往下讀懂本文。 服務器
咱們的Hello world視圖很是簡單。 這些是完整的函數和導入聲明,你須要輸入到views.py文件: cookie
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可識別的視圖,它必須知足這兩個條件。 (也有例外,可是咱們稍後纔會接觸到。
你的第一個URLconf
如今,若是你再運行: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/.
關於"404錯誤"的快速參考
目前,咱們的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-2)。 由於你的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('',
('^$', my_homepage_view),
# ...
)
Django是怎麼處理請求的
在繼續咱們的第二個視圖功能以前,讓咱們暫停一下去了解更多一些有關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文件中更改這個值。請參見它裏面的註釋,以得到最新世界時區列表。
URL配置和鬆耦合
如今是好時機來指出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和視圖是鬆耦合的。 咱們將在本書中繼續給出這一重要哲學的相關例子。
第三個視圖 動態URL
在咱們的`` 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錯誤。
Django 漂亮的出錯頁面
花幾分鐘時間欣賞一下咱們寫好的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內建有一個簡單有強大的模板處理引擎來讓你分離兩種工做: 下一章,咱們將學習模板引擎。