1. 程式人生 > >Django第八篇,深入理解檢視和url

Django第八篇,深入理解檢視和url

目錄

 URL 配置小技巧

 簡化匯入函式的方式

 在除錯模式下提供特殊的 URL

 具名分組

示例請求:

 捕獲的引數始終是字串

為檢視的引數指定預設值

錯誤處理

 引入其他 URL 配置

捕獲的引數

給檢視函式傳遞額外引數

反向解析 URL

示例

URL名稱空間


 URL 配置小技巧

 簡化匯入函式的方式

from mysite.views import hello, current_datetime, hours_ahead

我們可以匯入 views 模組自身避免 配置的內容會越變越多的麻煩

from . import views

from mysite.views import *

 在除錯模式下提供特殊的 URL

當 DEBUG 的值為 True 時, /debuginfo/ URL 才可以訪問

urlpatterns = [
    url(r'^$', views.homepage),
    url(r'^(\d{4})/([a-z]{3})/$', views.archive_month),
]
if settings.DEBUG:
    urlpatterns += [url(r'^debuginfo/$', views.debug),]

 具名分組

在高階用法中,可以使用具名的正則表示式分組捕獲 URL 片段,通過關鍵字引數把片段傳給檢視。

from django.conf.urls import url
from . import views
urlpatterns = [
    url(r'^reviews/2003/$', views.special_case_2003),
    url(r'^reviews/([0-9]{4})/$', views.year_archive),
    url(r'^reviews/([0-9]{4})/([0-9]{2})/$', views.month_archive),
    url(r'^reviews/([0-9]{4})/([0-9]{2})/([0-9]+)/$', views.review_detail),
]

• 若想捕獲 URL 中的值,把值放在括號裡
無需使用前導斜線,每個 URL 本身就有。例如,是 ^reviews ,而非 ^/reviews 。
• 每個正則表示式字串前面的 'r' 是可選的,但是建議加上。它的作用是告訴 Python,那是“原始”字
符串,裡面的一切內容都不應該轉義。

示例請求:

• 對 /reviews/2005/03/ 的請求匹配上述列表中的第三個條目。Django 呼叫 views.month_archive(re-quest, '2005', '03') 函式。
• /reviews/2005/3/ 不匹配任何 URL 模式,因為列表中的第三個條目要求月份有兩個數字。
• /reviews/2003/ 匹配列表中的第一個模式,而不是第二個,因為模式按順序測試,而第一個就能讓測試通過。你可以調整順序,新增類似這樣的特例。

下述 URL 配置與前面的作用一樣,不過換用具名分組了:

from django.conf.urls import url
from . import views
urlpatterns = [
    url(r'^reviews/2003/$', views.special_case_2003),
    url(r'^reviews/(?P<year>[0-9]{4})/$',     views.year_archive),
    url(r'^reviews/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$',
views.month_archive),
    url(r'^reviews/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})/$',
views.review_detail),
]

• 對 /reviews/2005/03/ 的請求呼叫 views.month_archive(request,year='2005',month='03') 函式,而不是 views.month_archive(request,'2005','03') 。
• 對 /reviews/2003/03/03/ 的請求呼叫 views.review_detail(re-quest,year='2003',month='03',day='03') 函式。

 捕獲的引數始終是字串

不管正則表示式匹配的是什麼型別,捕獲的每個引數都以普通的 Python 字串傳給檢視。對 URL 配置中的這一行來說:

url(r'^reviews/(?P<year>[0-9]{4})/$', views.year_archive),

雖然 [0-9]{4} 只匹配字串中的整數,但是傳給 views.year_archive() 檢視函式的 year 引數是字串,而不是整數。

為檢視的引數指定預設值

# URL 配置
from django.conf.urls import url
from . import views
urlpatterns = [
    url(r'^reviews/$', views.page),
    url(r'^reviews/page(?P<num>[0-9]+)/$', views.page),
]
# 檢視(在 reviews/views.py 檔案中)
def page(request, num="1"):
    # 輸出指定數量的書評

在上述示例中,兩個 URL 模式指向同一個檢視,即 views.page ,但是第一個模式沒有從 URL 中匹配任何內容。如果匹配第一個模式, page() 函式使用 num 的預設值,即 "1" ;如果匹配第二個模式, page() 函式使用正則表示式匹配的 num 值

 效能

urlpatterns 中的每個正則表示式在首次訪問時編譯,因此係統的速度異常得快。

錯誤處理

找不到匹配所請求 URL 的正則表示式或有異常丟擲時,Django 會呼叫一個錯誤處理檢視。具體使用的檢視由四個引數指定。這四個引數是:

• handler404
• handler500
• handler403
• handler400

 引入其他 URL 配置

Django 遇到 include() 時,會把截至那一位置匹配的 URL 截斷,把餘下的字串傳給引入它的 URL 配置,做進一步處理。利用這一行為可以去除 URL 配置中的重複,在多處使用相同的模式字首。來看這個 URL 配置:

from django.conf.urls import url
from . import views
urlpatterns = [
    url(r'^(?P<page_slug>\w+)-(?P<page_id>\w+)/history/$',
views.history),
    url(r'^(?P<page_slug>\w+)-(?P<page_id>\w+)/edit/$',
views.edit),
    url(r'^(?P<page_slug>\w+)-(?P<page_id>\w+)/discuss/$',
views.discuss),
    url(r'^(?P<page_slug>\w+)-(?P<page_id>\w+)/permissions/$',
views.permissions),
]

我們可以改進這個 URL 配置,把共用的路徑字首提取出來,再把不同的部分放在其後:

from django.conf.urls import include, url
from . import views
urlpatterns = [
    url(r'^(?P<page_slug>\w+)-(?P<page_id>\w+)/',
        include([
            url(r'^history/$', views.history),
            url(r'^edit/$', views.edit),
            url(r'^discuss/$', views.discuss),
            url(r'^permissions/$', views.permissions),
        ])    
    ),
]

捕獲的引數

被引入的 URL 配置會接收到父級 URL 配置捕獲的引數,因此下述示例是有效的:

# settings/urls/main.py
from django.conf.urls import include, url
urlpatterns = [
    url(r'^(?P<username>\w+)/reviews/', include('foo.urls.reviews')),
]
# foo/urls/reviews.py
from django.conf.urls import url
from . import views
urlpatterns = [
    url(r'^$', views.reviews.index),
    url(r'^archive/$', views.reviews.archive),
]

給檢視函式傳遞額外引數

URL 配置允許向檢視函式傳遞額外的引數,這些引數放在一個 Python 字典中。

from django.conf.urls import url
from . import views
urlpatterns = [
    url(r'^reviews/(?P<year>[0-9]{4})/$',
        views.year_archive,
    {'foo': 'bar'}
    ),
]

對這個示例來說,請求 /reviews/2005/ 時,Django 呼叫 views.year_archive(request, year='2005',foo='bar') 。

反向解析 URL

在 Django 專案中經常需要獲取 URL 的最終形式,這麼做是為了在生成的內容中嵌入 URL(檢視和靜態資源的 URL、呈現給使用者的 URL,等等),或者在伺服器端處理導航流程(重定向等)。此時,一定不能硬編碼 URL(費時、不可伸縮,而且容易出錯),或者參照 URL 配置創造一種生成 URL 的機制,因為這樣容易導致線上 URL 失效。也就是說,我們需要一種不自我重複的機制。

這種機制的一個優點是,改進 URL 設計之後無需在專案的原始碼中大範圍搜尋,替換掉過期的 URL。目前,我們能獲取的資訊有負責處理 URL 的檢視標識(即檢視名稱),以及查詢正確 URL 所需的檢視引數型別(位置引數或關鍵字引數)和值。

Django 提供了一種方案,只需在 URL 對映中設計 URL。我們為其提供 URL 配置,然後可以雙向使用:
• 從使用者(瀏覽器)請求的 URL 開始,這個方案能呼叫正確的 Django 檢視,並從 URL 中提取可能需要的引數及其值,傳給檢視。
• 從 Django 檢視對應的標識以及可能傳入的引數值開始,獲取相應的 URL。

Django 在不同的層中提供了執行 URL 反轉所需的工具:

• 在模板中,使用 url 模板標籤。
• 在 Python 程式碼中,使用 django.core.urlresolvers.reverse() 函式。

• 在處理 Django 模型例項 URL 相關的高層程式碼中,使用 get_absolute_url() 方法。

示例

仍以下述 URL 配置為例:

from django.conf.urls import url
from.import views
urlpatterns = [
    #...
    url(r'^reviews/([0-9]{4})/$', views.year_archive,
name='reviews-year-archive'),
    #...
]

根據這個設計,nnnn 年的存檔對應的 URL 是 /reviews/nnnn/ 。在模板中可以使用下述程式碼獲取這個 URL:

<a href="{% url 'reviews-year-archive' 2012 %}">2012 Archive</a>
{# 或者把年份儲存在一個模板上下文變數中:#}
<ul>
{% for yearvar in year_list %}
<li><a href="{% url 'reviews-year-archive' yearvar %}">{{
yearvar }} Archive</a></li>
{% endfor %}
</ul>

在 Python 程式碼中則要這麼做:

from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
def redirect_to_year(request):
    # ...
    year = 2012
    # ...
    return HttpResponseRedirect(reverse('reviews-year-archive', args=(year,)))

URL名稱空間

URL 名稱空間在反轉具名 URL 模式時具有唯一確定性,即便不同的應用使用相同的名稱也不怕。鑑於此,第三方應用最好始終把 URL 放在名稱空間中。類似地,URL 名稱空間在部署多個應用程式例項時也能正確反轉 URL。也就是說,同一個應用程式的多個例項使用相同的 URL 模式名稱,而通過名稱空間可以把它們區分開。

例如:

url(r'^reviews/', include('reviews.urls',namespace='author-reviews',app_name='reviews')),

上述程式碼把 reviews.urls 中定義的 URL 放在應用名稱空間 reviews 中,放在例項名稱空間 author-reviews中。第二種方式是,引入包含名稱空間資料的物件。如果使用 include() 引入一組 url() 例項,那個物件中的 URL 都新增到全域性名稱空間中。然而, include() 的引數還可以是一個三元素元組,其內容如下:

(<list of url() instances>, <application namespace>, <instance namespace>)

例如:

from django.conf.urls import include, url
from . import views
reviews_patterns = [
    url(r'^$', views.IndexView.as_view(), name='index'),
    url(r'^(?P<pk>\d+)/$', views.DetailView.as_view(), name='detail'),
]
    url(r'^reviews/', include((reviews_patterns, 'reviews', 'author-reviews'))),

上述程式碼把指定的 URL 模式引入指定的應用和例項名稱空間中。例如,部署的 Django 管理後臺是 AdminSite的例項。 AdminSite 物件有個 urls 屬性,它的值是一個三元素元組,包含相應管理後臺的所有 URL 模式,以及應用名稱空間 'admin' 和管理後臺例項的名稱。部署管理後臺例項時,引入專案的 urlpatterns 中的就是這個 urls 屬性

記得要把一個元組傳給 include() 。如果直接傳入三個引數,例如 include(reviews_patterns, 'reviews','author-reviews') ,Django 不會丟擲錯誤,但是根據 include() 的簽名, 'reviews' 是例項名稱空間, 'au-thor-reviews' 是應用名稱空間,而正確的順序應該反過來。