【譯】在Django中設置URL方法大全

做者:Matt Laymanweb

翻譯:老齊正則表達式


當用戶訪問網站的時候,實際上是經過瀏覽器向網站發起訪問請求,那麼,網站是如何處理請求的?本文以Django框架爲例進行說明,關於Django更詳細的使用方法,請閱讀《跟老齊學Python:Django實戰(第二版)》。django

來自瀏覽器的HTTP請求包含一個URL,URL描述Django應該生成哪些資源。因爲URL能夠有多種形式,咱們必須把web應用能夠處理的URL類型告訴Django,這就是URL配置的目的。在Django文檔中,URL配置簡稱爲URLconf。編程

URLconf在哪裏?URLconf位於項目設置文件中的ROOT_URLCONF所設置的模塊路徑上。若是運行startproject命令,則該以project.url形式命名路由,其中「project」是項目名稱。換句話說,URLconf就放在project/urls.py中,它與settings.py文件在同一個目錄。瀏覽器

知道了文件所在的位置,但並無講它是如何工做的,下面來深刻探索。bash

執行URLconf

能夠將URL配置看做URL路徑列表,Django會把這些路徑從上到下進行匹配。當Django找到匹配的路徑時,HTTP請求將轉到與該路徑相關聯的Python代碼塊。這個「Python代碼塊」被稱爲視圖,咱們將在稍後對其進行更多的探討。目前,請相信視圖知道如何處理HTTP請求。微信

咱們可使用一個URLconf示例來實踐上述觀點。app

# project/urls.py
from django.urls import path

from application import views

urlpatterns = [
    path("", views.home),
    path("about/", views.about),
    path("contact/", views.contact),
    path("terms/", views.terms),
]
複製代碼

這裏的內容與我上面的描述相吻合,Django對列表中的URL路徑從上到下進行匹配。此列表的關鍵是urlpatterns。Django將把urlpatterns變量中的列表視爲URLconf。框架

列表中的順序也很重要,上述示例沒有顯示路徑之間的任何衝突,但能夠建立兩個不一樣的path,它們能夠匹配同一個URL,在後續內容中,會探究這個問題。函數

咱們能夠經過這個例子來看看它在www.itdiffer.com上是如何運行的。對於URLconf中的URL,Django不使用https://協議、或者主域名(www.itdiffer.com)及斜槓匹配路由,其餘的都是URLconf所要匹配的。

  • 對https://www.itdiffer.com/about/匹配「about/」,並匹配第二個路徑。該請求將轉到views.about視圖。
  • 對https://www.itdiffer.com/的請求匹配「」,並匹配第一個路徑。該請求將路由到views.home視圖。

補充說明:你可能注意到Django URL以斜槓結尾。事實上,若是你嘗試訪問像https://www.itdiffer.com/about這樣的URL,Django會自動附加斜槓,再將請求重定向到相同的URL,這是由於使用了默認設置APPEND_SLASH,這種是Django在設計理念上的選擇。

path的做用

若是我給出的只是上面的例子,你可能會說:「哇,Django太笨了,爲何urlpatterns不是下面這樣的字典?」

urlpatterns = {
    "": views.home,
    "about/": views.about,
    "contact/": views.contact,
    "terms/": views.terms,
}
複製代碼

我不會由於你以上言論而苛責。

緣由是path的能力更強大,大部分功能都在傳給函數的第一個字符串參數中。path的字符串部分(例如,「about/」)稱爲路由。

正如你所看到的,路由能夠是一個簡單的字符串,它也能夠包含一些轉換特徵,如此,就能夠從URL中提取信息,並傳給視圖,例如這樣的一個路徑:

path("blog/<int:year>/<slug:slug>/", views.blog_post),
複製代碼

此路徑中的兩個轉換器是:

使用尖括號和一些保留關鍵詞,讓Django對URL進行解析,每一個轉換器都有一些預期的規則要遵循。

  • int必須與整數匹配。
  • slug必須與標稱匹配。Slug是一種出如今Django中的報刊行話,由於Django是從堪薩斯州的一家報紙開始的。slug是一個字符串,它能夠包含字符、數字、破折號和下劃線。

考慮到這些轉換器定義,讓咱們與一些URL進行比較!

如今咱們能夠從新審視一下以前的排序問題,以不一樣的順序考慮這兩條路徑:

path("blog/<int:year>/", views.blog_by_year),
path("blog/2020/", views.blog_for_twenty_twenty),

# vs.

path("blog/2020/", views.blog_for_twenty_twenty),
path("blog/<int:year>/", views.blog_by_year),
複製代碼

在第一次排序中,轉換器將匹配blog/以後的任何整數,包括https://www.itdiffer.com/blog/2020/。這意味着第一次排序將永遠不會調用views.blog_for_twenty_twenty,由於Django按順序匹配路徑。

相反,在第二次排序中,blog/2020/將正確地路由到views.blog_for_twenty_twenty,由於它首先匹配。這意味着這裏有一個教訓要謹記:

當包含與轉換器範圍匹配的路徑項時,請確保將它們放在更具體的項以後。

視圖簡介

轉換器如何處理這些從路徑中捕獲的參數?這很難在不觸及視圖的狀況下解釋,因此要對視圖給予入門介紹,關於視圖更詳細的內容,仍是要閱讀《跟老齊學Python:Django實戰(第二版)》的深刻淺出講解。

視圖的做用是接受請求並返回響應,使用Python的可選類型檢查,下面是一個發送Hello World響應的示例。

from django.http import HttpRequest, HttpResponse

def some_view(request: HttpRequest) -> HttpResponse:
    return HttpResponse('Hello World')
複製代碼

Django將用戶的HTTP請求封裝在一個容器類中,這就是HttpRequest。一樣,咱們可使用HttpResponse,以便讓Django將咱們的響應數據轉換爲格式正確的HTTP響應,並將其發送回用戶的瀏覽器。

如今咱們能夠再看一個轉換器。

path("blog/<int:year>/", views.blog_by_year),
複製代碼

路由中有了這個轉換器,blog_by_year會是什麼樣子?

# application/views.py
from django.http import HttpResponse

def blog_by_year(request, year):
    # ... some code to handle the year
    data = 'Some data that would be set by code above'
    return HttpResponse(data)
複製代碼

Django開始在這裏展現出一些不錯的品質!轉換器爲咱們作了一堆乏味的工做。Django設置的year參數已是一個整數,由於Django進行了字符串解析和轉換。

若是有人提交/blog/not_a_number/,Django將返回Not Found響應,由於not_a_number不是整數。這樣作的好處是,咱們沒必要在blog_by_year 中添加額外的檢查邏輯來處理年份看起來不像數字的奇怪狀況。這種函數能夠節省時間!它使代碼更乾淨,處理更精確。

怎樣理解咱們以前看到的另外一個奇怪的例子/blog/0/life-in-rome/呢?這將與前面部分中的模式相匹配,但假設咱們但願匹配一個四位數的年份,咱們該怎麼作到呢?可使用正則表達式。

路由中的正則表達式

在編程中,正則表達式應用很廣泛,經常被比做電鋸:功能強大得使人難以置信,但若是不當心的話,也能夠砍掉「你的腳」。

正則表達式能夠以很是簡潔的方式表示複雜的關係和匹配模式,這種簡潔性經常給正則表達式帶來難以理解的壞名聲。但若是當心使用,它們能夠是很好的工具。

正則表達式(一般縮寫爲「regex」)適合於匹配字符串中的複雜模式。這聽起來像是要解決前年提到的年份問題!在咱們的問題中,只想匹配一個四位數的整數。讓咱們看看Django的解決方案,而後分解它的含義。

提醒一下,此解決方案將匹配某些URL路徑,如blog/2020/urls-lead-way/。

re_path("^blog/(?P<year>[0-9]{4})/(?P<slug>[\w-]+)/$", views.blog_post),
複製代碼

這個瘋狂的字符串的行爲與前面的例子徹底同樣,只是它把年份精確到四位數,這個瘋狂的字符串被稱爲regex模式。當Django代碼運行時,它將根據此模式中定義的規則測試URL路徑。

要了解它的工做原理,咱們必須知道模式的各個部分意味着什麼,依次解釋:

  • ^:意思是「模式必須從這裏開始」,由於有了這個符號,像myblog/... 這樣開頭的路徑是行不通的。
  • blog/:是字面上的解釋。這些字符必須徹底匹配。
  • (?P<year>[0-9]{4})中,圓括號內的部分是一個捕獲組。?P<year>是與捕獲組關聯的名稱,相似於<int:year>這樣的轉換器中的冒號右側部分。該名稱容許Django將名爲year的參數中的內容傳給視圖。捕獲組的另外一部分[0-9]{4}是模式實際匹配的內容。[0-9]是一個字符類,意思是「匹配從0到9的任意數字,{4}表示它必須精確匹配四次。re_path提供的這種特殊性是int轉換器所不能作到的!
  • 捕獲組之間的斜槓/,是另外一個要匹配的文本字符。
  • 第二個捕捉組(?P<slug>[\w-]+)將匹配的內容放入名爲slug的參數中。[\w-]包含兩種類型,\w是指天然語言中的任何單詞字符,另外一種是破折號(注意:英文的,即-)字符。最後,加號(+)字符表示字符類必須匹配一次或屢次。
  • 最後一個斜槓也是原始字符匹配,即要匹配/
  • 爲了完成這個模式,符號$的做用與^相反,表示「模式必須在這裏結束」。所以blog/2020/some-slug/another-slug/是不匹配的。

祝賀你!這絕對是本文最難的部分。若是你明白咱們對re_path作了什麼,接下來的內容應該會讓你很舒服。若是沒有,請不要擔憂!若是你想了解有關正則表達式的更多信息,請知曉我在模式中描述的全部內容都不是Django所特有的。相反,這是Python的內置行爲,你能夠在網上搜到不少信息。

知道了re_path的強大做用,即便你今天不須要它,它也能夠對你之後的Django開發有幫助。

URL分組配置

到目前爲止,咱們已經研究了能夠在URLconf中映射的各個路由。若是多個視圖函數使用同一個URL時,咱們能夠怎麼作?爲何?

想象一下你正在建立一個教育項目。在你的項目中,有學校、學生和其餘與教育相關的概念。你能夠這樣作:

# project/urls.py
from django.urls import path

from schools import views as schools_views
from students import views as students_views

urlpatterns = [
    path("schools/", schools_views.index),
    path("schools/<int:school_id>/", schools_views.school_detail),
    path("students/", students_views.index),
    path("students/<int:student_id>/", students_views.student_detail),
]
複製代碼

這種方法能夠奏效,可是,若是這樣,根的路由就要遍歷schools_viewsstudents_views每一個應用中的視圖。實際上,咱們可使用include來更好地處理這個問題。

# project/urls.py
from django.urls import include

urlpatterns = [
    path("schools/", include("schools.urls"),
    path("students/", include("students.urls"),
]
複製代碼

而後,在每一個應用中建立urls.py,並按照以下方式配置該應用的路由:

# schools/urls.py
from django.urls import path

from schools import views

urlpatterns = [
    path("", views.index),
    path("<int:school_id>/", views.school_detail),
]
複製代碼

include使每一個Django應用在須要定義的視圖中擁有自主權,項目能夠幸運地「忽視」應用要作的事情。

此外,還刪除了重複的schools/students/。當Django處理一個路由時,它將匹配路由的第一部分,並將其他部分傳遞到在單個應用中定義的URLconf。經過這種方式,URL配置能夠造成一個樹,其中的root URLconf 是全部請求開始的地方,可是當一個請求被轉到相應的應用時,該應用能夠處理細節。

命名URL

咱們已經研究了用pathre-pathinclude定義URL的主要方法。還有另一個方面須要考慮,如何在代碼中使用URL?想一想這個(至關愚蠢的)作法:

# application/views.py
from django.http import HttpResponseRedirect

def old_blog_categories(request):
    return HttpRequestRedirect("/blog/categories/")
複製代碼

重定向是指用戶試圖訪問一個頁面時,被瀏覽器發送到其餘地方。有比這個例子更好的方法來處理重定向,可是這個例子說明了一個不一樣的觀點。若是你但願從新構造該項目,以便將博客類別從/blog/categories/改成/marketing/blog/categories/,會發生什麼狀況?在當前代碼中,咱們必須重寫這個視圖,而且經過路由轉到其餘視圖。

真是浪費時間!Django爲咱們提供解決此問題的方法。能夠在路由中設置每一個URL的名稱,使用pathname參數來實現。

# project/urls.py
from django.urls import path

from blog import views as blog_views

urlpatterns = [
    ...
    path("/marketing/blog/categories/",
        blog_views.categories, name="blog_categories"),
    ...
]
複製代碼

這使得blog_categories成爲/marketing/blog/categories/的名稱。要使用這個名稱,咱們須要返回到它的對應項。修改後的視圖以下:

# application/views.py
from django.http import HttpResponseRedirect
from django.urls import reverse

def old_blog_categories(request):
    return HttpRequestRedirect(reverse("blog_categories"))
複製代碼

reverse的任務是查找任何url的名稱並返回其對應路徑。這意味着:

reverse("blog_categories") == "/marketing/blog/categories/"
複製代碼

至少持續到你想再次改變它以前。😁

名稱衝突

若是你對多個URL取相同的名稱,會發生什麼狀況?例如,index或detail是你可能要應用的常見名稱。咱們能夠向《Python之禪》尋求建議。

若是編程時間不長,命名空間對你來講多是新知識,它們是共享的名稱空間。也許這一點很清楚,但我記得當我第一次開始寫軟件的時候,我還在爲這個概念而掙扎。

咱們用現實中的一類場景來類別,假設你有兩個紅色球、兩個藍色球和分別標有「A」和「B」的兩個桶,把球分別放在兩個桶裏面。若是我想要藍色球,我不能說「請把藍色球給我」,由於那樣會模棱兩可。相反,爲了獲得一個特定的球,我須要說「請給我B桶中的藍色球。」在這個場景中,bucket(桶)是命名空間。

咱們所舉的關於學校和學生的示例,也能夠做爲例子說明這個想法。兩個應用都有一個index視圖函數來表示該應用部分的根(即schools/students/)。若是想指向某個視圖中的函數,不得不使用views.index,而後,這樣Django就沒法判斷應該轉向哪一個視圖的index函數。

一種解決方案是經過在名稱前面加上schools_之類的常見名稱來建立本身的命名空間。這種方法的問題在於URLconf會自我重複。

# schools/urls.py
from django.urls import path

from schools import views

urlpatterns = [
    path("", views.index, name="schools_index"),
    path("<int:school_id>/", views.school_detail, name="schools_detail"),
]
複製代碼

Django提供了一個替代方法,可讓你保留一個較短的名稱。

# schools/urls.py
from django.urls import path

from schools import views

app_name = "schools"
urlpatterns = [
    path("", views.index, name="index"),
    path("<int:school_id>/", views.school_detail, name="detail"),
]
複製代碼

經過添加app_name,咱們向Django發出信號,這些視圖位於一個命名空間中。如今,當咱們想要得到一個URL時,咱們使用命名空間的名稱和URL名稱,並用冒號將它們鏈接起來。

reverse("schools:index") == "/schools/"
複製代碼

這是Django提供的另外一個便利,它使咱們更容易專一於應用的開發。

咱們的URL話題進入尾聲。最後給你們推薦的是一本學習Django的好書《跟老齊學Python:Django實戰(第二版)》。

原文連接:www.mattlayman.com/understand-…

關注微信公衆號:老齊教室。讀深度文章,得精湛技藝,享絢麗人生。

相關文章
相關標籤/搜索