天天看點

第二章:視圖層 - 1:URL路由基礎

路由的編寫方式是Django2.0和1.11最大的差別所在。Django官方迫于壓力和同行的影響,不得不将原來的正則比對表達式,改為更加簡單的path表達式,但依然通過re_path()方法保持對1.x版本的相容。

URL是Web服務的入口,使用者通過浏覽器發送過來的任何請求,都是發送到一個指定的URL位址,然後被響應。

在Django項目中編寫路由,就是向外暴露我們接收哪些URL的請求,除此之外的任何URL都不被處理,也沒有傳回。通俗地了解,不恰當的形容,URL路由是你的Web服務對外暴露的API。

Django奉行DRY主義,提倡使用簡潔、優雅的URL,沒有

.php

.cgi

這種字尾,更不會單獨使用0、2097、1-1-1928、00這樣無意義的東西,讓你随心所欲設計你的URL,不受架構束縛。

一、概述

URL路由在Django項目中的展現就是

urls.py

檔案,這個檔案可以有很多個,但絕對不會在同一目錄下。實際上Django提倡項目有個根

urls.py

,各app下分别有自己的一個

urls.py

,既集中又分治,是一種解耦的模式。

随便建立一個Django項目,預設會自動為我們建立一個

/project_name/urls.py

檔案,并且自動包含下面的内容,這就是項目的根URL:

"""dj_test URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/2.0/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path

urlpatterns = [
    path('admin/', admin.site.urls),
]
           

前面一堆幫助性的文字,我們不用管,關鍵是預設導入了path方法和admin子產品,然後有一條指向admin背景的url路徑。

我們自己要編寫的url路由,基本也是這個套路。

二、Django如何處理請求

當使用者請求一個頁面時,Django根據下面的邏輯執行操作:

  1. 決定要使用的根URLconf子產品。通常,這是

    ROOT_URLCONF

    設定的值,但是如果傳入的HttpRequest對象具有urlconf屬性(由中間件設定),則其值将被用于代替

    ROOT_URLCONF

    設定。通俗的講,就是你可以自定義項目入口url是哪個檔案!
  2. 加載該子產品并尋找可用的urlpatterns。 它是

    django.urls.path()

    或者

    django.urls.re_path()

    執行個體的一個清單。
  3. 依次比對每個URL模式,在與請求的URL相比對的第一個模式停下來。也就是說,url比對是從上往下的短路操作,是以url在清單中的位置非常關鍵。
  4. 導入并調用比對行中給定的視圖,該視圖是一個簡單的Python函數(被稱為視圖函數),或基于類的視圖。 視圖将獲得如下參數:
    1. 一個HttpRequest 執行個體。
    2. 如果比對的表達式傳回了未命名的組,那麼比對的内容将作為位置參數提供給視圖。
    3. 關鍵字參數由表達式比對的命名組組成,但是可以被

      django.urls.path()

      的可選參數kwargs覆寫。
  5. 如果沒有比對到任何表達式,或者過程中抛出異常,将調用一個适當的錯誤處理視圖。

三、簡單示例

先看一個例子:

from django.urls import path

from . import views

urlpatterns = [
    path('articles/2003/', views.special_case_2003),
    path('articles/<int:year>/', views.year_archive),
    path('articles/<int:year>/<int:month>/', views.month_archive),
    path('articles/<int:year>/<int:month>/<slug:slug>/', views.article_detail),
]
           

注意:

  1. 要捕獲一段url中的值,需要使用尖括号,而不是之前的圓括号;
  2. 可以轉換捕獲到的值為指定類型,比如例子中的int。預設情況下,捕獲到的結果儲存為字元串類型,不包含

    /

    這個特殊字元;
  3. 比對模式的最開頭不需要添加

    /

    ,因為預設情況下,每個url都帶一個最前面的

    /

    ,既然大家都有的部分,就不用浪費時間特别寫一個了。

比對例子:

  • /articles/2005/03/ 将比對第三條,并調用views.month_archive(request, year=2005, month=3);
  • /articles/2003/比對第一條,并調用views.special_case_2003(request);
  • /articles/2003将一條都比對不上,因為它最後少了一個斜杠,而清單中的所有模式中都以斜杠結尾;
  • /articles/2003/03/building-a-django-site/ 将比對最後一個,并調用views.article_detail(request, year=2003, month=3, slug="building-a-django-site"

每當urls.py檔案被第一次加載的時候,urlpatterns裡的表達式們都将被預先編譯,這會大大提高系統處理路由的速度。

四、path轉換器

預設情況下,Django内置下面的路徑轉換器:

  • str

    :比對任何非空字元串,但不含斜杠

    /

    ,如果你沒有專門指定轉換器,那麼這個是預設使用的;
  • int

    :比對0和正整數,傳回一個int類型
  • slug

    :可了解為注釋、字尾、附屬等概念,是url拖在最後的一部分解釋性字元。該轉換器比對任何ASCII字元以及連接配接符和下劃線,比如

    building-your-1st-django-site

  • uuid

    :比對一個uuid格式的對象。為了防止沖突,規定必須使用破折号,所有字母必須小寫,例如

    075194d3-6885-417e-a8a8-6c931e272f00

    。傳回一個UUID對象;
  • path

    :比對任何非空字元串,重點是可以包含路徑分隔符’/‘。這個轉換器可以幫助你比對整個url而不是一段一段的url字元串。要區分path轉換器和path()方法。

五、自定義path轉換器

其實就是寫一個類,并包含下面的成員和屬性:

  • 類屬性regex:一個字元串形式的正規表達式屬性;
  • to_python(self, value) 方法:一個用來将比對到的字元串轉換為你想要的那個資料類型,并傳遞給視圖函數。如果轉換失敗,它必須彈出ValueError異常;
  • to_url(self, value)方法:将Python資料類型轉換為一段url的方法,上面方法的反向操作。

例如,建立一個converters.py檔案,與urlconf同目錄,寫個下面的類:

class FourDigitYearConverter:
    regex = '[0-9]{4}'

    def to_python(self, value):
        return int(value)

    def to_url(self, value):
        return '%04d' % value
           

寫完類後,在URLconf 中注冊,并使用它,如下所示,注冊了一個yyyy:

from django.urls import register_converter, path

from . import converters, views

register_converter(converters.FourDigitYearConverter, 'yyyy')

urlpatterns = [
    path('articles/2003/', views.special_case_2003),
    path('articles/<yyyy:year>/', views.year_archive),
    ...
]
           

六、使用正規表達式

Django2.0的url雖然改‘配置’了,但它依然向老版本相容。而這個相容的辦法,就是用

re_path()

方法代替

path()

方法。

re_path()

方法在骨子裡,根本就是以前的

url()

方法,隻不過導入的位置變了。下面是一個例子,對比一下Django1.11時代的文法,有什麼太大的差别?

from django.urls import path, re_path

from . import views

urlpatterns = [
    path('articles/2003/', views.special_case_2003),
    re_path(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
    re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive),
    re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<slug>[\w-]+)/$', views.article_detail),
]
           

path()

方法不同的在于兩點:

  • year中比對不到10000等非四位數字,這是正規表達式決定的
  • 傳遞給視圖的所有參數都是字元串類型。而不像

    path()

    方法中可以指定轉換成某種類型。在視圖中接收參數時一定要小心。

七、URLconf比對URL中的哪些部分

請求的URL被看做是一個普通的Python字元串,URLconf在其上查找并比對。進行比對時将不包括GET或POST請求方式的參數以及域名。

例如,在

https://www.example.com/myapp/

的請求中,URLconf将查找

myapp/

https://www.example.com/myapp/?page=3

的請求中,URLconf也将查找

myapp/

URLconf不檢查使用何種HTTP請求方法,所有請求方法POST、GET、HEAD等都将路由到同一個URL的同一個視圖。在視圖中,才根據具體請求方法的不同,進行不同的處理。

八、指定視圖參數的預設值

有一個小技巧,我們可以指定視圖參數的預設值。 下面是一個URLconf和視圖的示例:

# URLconf
from django.urls import path

from . import views

urlpatterns = [
    path('blog/', views.page),
    path('blog/page<int:num>/', views.page),
]

# View (in blog/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值。

九、自定義錯誤頁面

當Django找不到與請求比對的URL時,或者當抛出一個異常時,将調用一個錯誤處理視圖。Django預設的自帶的錯誤視圖包括400、403、404和500,分别表示請求錯誤、拒絕服務、頁面不存在和伺服器錯誤。它們分别位于:

  • handler400 —— django.conf.urls.handler400。
  • handler403 —— django.conf.urls.handler403。
  • handler404 —— django.conf.urls.handler404。
  • handler500 —— django.conf.urls.handler500。

這些值可以在根URLconf中設定。在其它app中的二級URLconf中設定這些變量無效。

Django有内置的HTML模版,用于傳回錯誤頁面給使用者,但是這些403,404頁面實在醜陋,通常我們都自定義錯誤頁面。

from django.contrib import admin
from django.urls import path
from app import views

urlpatterns = [
    path('admin/', admin.site.urls),
]

# 增加的條目
handler400 = views.bad_request
handler403 = views.permission_denied
handler404 = views.page_not_found
handler500 = views.error
           
def bad_request(request):
    return render(request, '400.html')


def permission_denied(request):
    return render(request, '403.html')


def page_not_found(request):
    return render(request, '404.html')


def error(request):
    return render(request, '500.html')