1. 程式人生 > >python後臺架構Django教程——路由對映urls

python後臺架構Django教程——路由對映urls

全棧工程師開發手冊 (作者:欒鵬)

本文銜接至python後臺架構Django開發全解。

前面的教程我們已經建立了名為hello的django專案,app1、app2兩個應用,學習了view檢視層、templates模板層。

地址對映urls

我們既然可以在views.py中實現不同功能的檢視函式,那如何根據使用者的網址對映到對應的函式上呢。只有對映到對應的函式,才能執行對應的功能,返回對應的檢視。

舉個例子:如果我們在app1/views.py中實現了insertuser函式,希望在使用者輸入網址http://127.0.0.1:8000/app1/insertuser/時啟動這個函式,返回此函式影響的模板,那專案如何才能知道將網址對映到這個函式上呢?

根據不同的網址呼叫不同的檢視函式,我們需要編寫urls檔案。這就是路由檔案,根據使用者開啟的網址對應到不同的檢視函式。

請求的URL被看做是一個普通的Python字串,在其上查詢並匹配。進行匹配時將不包括GET或POST請求方式的引數以及域名。

根路由

在Django專案中預設只提供了一個根路由,也就是隻在hello檔案下存在urls.py檔案。當然你可以在這一個檔案中把所有提供服務的網址都寫下來,但是你最好採用多級路由的方法。

也就是說在根路由檔案hello/urls.py中只匹配一部分網址,剩下的交給各功能模組app處理。

我們設定根路由檔案如下。

from django.conf.urls import
include,url # Django1.11中的語法 from django.urls import path,include #Django2中的語法 from django.contrib import admin urlpatterns = [ # url是一個函式。regex和view為必填引數。regex就是使用者輸入的內容,view就是對應的檢視生成函式 url(r'^vip/', admin.site.urls), # Django自帶的超級管理員後臺地址。不要用臺隨意的網址 url(r'^app1/', include('app1.urls')), # 對映到下級路由
path(r'^app2/', include('app2.urls')), # 對映到下級路由 ]

注意:在Django1.11中路由函式為url(),在Django2中路由函式改成了path(),不過路由函式向下相容,在Django2中同樣可以使用url()函式。

這一步是讓專案的主urls也就是根路由檔案指向我們建立的每個app獨有的urls檔案。

上面的程式碼表示如果輸入的一級網址是app1,這將剩下的網址部分交給app1/urls.py子路由檔案處理。例如http://127.0.0.1:8000/app1/xxx

如果輸入的一級網址是app2,這將剩下的網址部分交給app2/urls.py子路由檔案處理。例如http://127.0.0.1:8000/app2/xxx

admin.site.urls為Django系統自帶的admin地址,後面在admin章節講述。這個不涉及路由規則。

子路由

根路由匹配了一部分網址,將剩下的網址部分交給各自的app功能模組路由檔案去識別。比如上面兩個網址中的xxx。

現在,在app1目錄中新建一個檔案,名字為urls.py,它就是功能模組的路由,用來匹配傳遞給app1模組的網址。

在hello/app1/urls.py中路由的設定與根路由相似,示例如下:

from django.conf.urls import url  # Django1.11中的語法
from django.urls import path  #Django2中的語法
from . import views

urlpatterns = [
    ...
]

其中urlpatterns是路由匹配規則。 例如

urlpatterns = [
    url(r'^finduser/', views.finduser),  # 直接對映到函式
]

表示如果子路由接收的網址字串部分以”finduser/”開頭,則將這個網址對映到finduser()函式上。

下面我們來介紹一下路由的設定規則

路由設定規則

1、include函式——路由轉發

路由轉發使用的是include()方法,需要提前匯入,它的引數是轉發目的地路徑的字串,路徑以圓點分割。

url(r'^app1/', include('app1.urls')),  # 對映到下級路由

每當Django 遇到include()(來自django.conf.urls.include())時,它會去掉URL中匹配的部分並將剩下的字串傳送給include的URLconf做進一步處理,也就是轉發到二級路由去。

include函式將接收到的url地址(“app1/insert/”)去除了它前面的正則表示式(“^app1/”),將剩下的字串(“insert/”)傳遞給下一級路由進行判斷。

include的背後是一種即插即用的思想。專案根路由不關心具體app的路由策略,只管往指定的二級路由轉發,實現瞭解耦的特性。app所屬的二級路由可以根據自己的需要隨意編寫,不會和其它的app路由發生衝突。app目錄可以放置在任何位置,而不用修改路由。這是軟體設計裡很常見的一種模式。

建議:除了admin路由外,你應該儘量給每個app設計自己獨立的二級路由。

2、url函式——路由匹配

url()函式可以傳遞4個引數,其中2個是必須的:regex和view,以及2個可選的引數:kwargs和name。下面是具體的解釋:

regex:
regex是正則表示式的通用縮寫,它是一種匹配字串或url地址的語法。Django拿著使用者請求的url地址,在urls.py檔案中對urlpatterns列表中的每一項條目從頭開始進行逐一對比,一旦遇到匹配項,立即執行該條目對映的檢視函式或二級路由,其後的條目將不再繼續匹配。因此,url路由的編寫順序至關重要!

效能註釋:正則表示式會進行預先編譯當URLconf模組載入的時候,因此它的匹配搜尋速度非常快,你通常感覺不到。

view:
當正則表示式匹配到某個條目時,自動將封裝的HttpRequest物件作為第一個引數,正則表示式“捕獲”到的值作為第二個引數,傳遞給該條目指定的檢視。如果是簡單捕獲,那麼捕獲值將作為一個位置引數進行傳遞,如果是命名捕獲,那麼將作為關鍵字引數進行傳遞。

kwargs:
任意數量的關鍵字引數可以作為一個字典傳遞給目標檢視。

name:
對你的URL進行命名,可以讓你能夠在Django的任意處,尤其是模板內顯式地引用它。相當於給URL取了個全域性變數名,你只需要修改這個全域性變數的值,在整個Django中引用它的地方也將同樣獲得改變。這是極為古老、樸素和有用的設計思想,而且這種思想無處不在。

url引數捕獲

很多時候,我們需要獲取URL中的一些片段,作為引數,傳遞給處理請求的檢視函式。

我們處理能匹配這個網址還要能將6498210200338563598這串數字傳遞給函式,以便來查詢文章。這個時候就是獲取url中的一個片段作為引數傳遞給檢視函式。

url傳遞指定引數的語法為:

(?P<name>pattern)

name 可以理解為所要傳遞的引數的名稱,pattern代表所要匹配的模式。例如,

url(r'^(?P<userid>[0-9]+)/detail/$', views.detail),

這個url對映不僅可以匹配….555/detail/ 或者…25/detail/這樣的網址。同時還可以將正則部分匹配到的資料作為引數傳遞給views.detail()函式,views.detail()函式也會多出一個引數,名為userid。當然作為一個函式,userid引數是可以提供預設值的。

def detail(request,userid='1'):
    users = User.objects.filter(id=userid)  
    ....

Django2中的路由引數傳遞

在Django2中路由引數傳遞改變了一點寫法。

urlpatterns = [
    path('user/<int:userid>/', views.detail),
]

注意:

  • 要捕獲一段url中的值,需要使用尖括號,而不是之前的圓括號;
  • 可以轉換捕獲到的值為指定型別,比如例子中的int。預設情況下,捕獲到的結果儲存為字串型別,不包含/這個特殊字元;

Django2中路由的引數型別:

  • str:匹配任何非空字串,但不含斜槓/,如果你沒有專門指定轉換器,那麼這個是預設使用的;
  • int:匹配0和正整數,返回一個int型別
  • slug:可理解為註釋、字尾、附屬等概念,是url拖在最後的一部分解釋性字元。該轉換器匹配任何ASCII字元以及連線符和下劃線,比如’building-your-1st-django-site’;
  • uuid:匹配一個uuid格式的物件。為了防止衝突,規定必須使用破折號,所有字母必須小寫,例如’075194d3-6885-417e-a8a8-6c931e272f00’。返回一個UUID物件;
  • path:匹配任何非空字串,重點是可以包含路徑分隔符’/‘。這個轉換器可以幫助你匹配整個url而不是一段一段的url字串。

URL反向解析和名稱空間

除了要匹配使用者輸入的網址,有時我們還需要在python程式碼中生成網址,例如重定向,在html中為元素新增url連結。

我們試想這樣一個問題。我們編寫好了路由規則。也對應上了檢視函式。在html需要連結的地方也寫好了連結的網址。在所有需要引用這個連結的地方也寫好了這個網址字串。可突然通知要修改網址命名規則。那就要修改所有也使用了這個網址的地方。這可是個麻煩事。

在需要解析URL的地方,對於不同層級,Django提供了不同的工具用於URL反查:

  • 在模板語言中:使用url模板標籤。(也就是寫前端網頁時)
  • 在Python程式碼中:使用reverse()函式。(也就是寫檢視函式等情況時)
  • 在更高層的與處理Django模型例項相關的程式碼中:使用get_absolute_url()方法。(也就是在模型model中)

刪除模板中硬編碼的URLs

在html模板檔案中,可能會有一部分硬編碼存在,所謂硬編碼就是直接輸入路徑字串,而不是以代號的形式存在。比如href或action裡面的網址“/app1/insert/”部分:

<form action="/app1/insert/" method="post">

它對於程式碼修改非常不利。如果我們在urls.py檔案中更換了正則表示式,那麼你所有的html模板中對這個url的引用都需要修改,這是無法接受的!

我們前面知道可以給url定義一個name別名,可以用它來解決這個問題。

在app1/urls.py中,我們為一個檢視網址新增一個name屬性。程式碼如下:

url(r'^insert/', views.insertuser,name='inserpath')

而這個name一般不會變,我們就可以利用這個name來設定html模板中的網址。如下

<form action="{% url 'inserpath' %}" method="post">

這樣form提交的網址就使用中name為insertpath這個url對應的網址。

那如果有多個app模組,每個app模組有相同name屬性的url該怎麼區分呢。

答案是使用URLconf的名稱空間。在app1/urls.py檔案的開頭部分,新增一個app_name的變數來指定該應用的名稱空間:

如 app1/urls.py檔案

...
app_name = 'app1_name'  # 關鍵是這行

urlpatterns = [
...
]

在app1/templates/insert.html檔案中的引用也要由

<form action="{% url 'inserpath' %}" method="post">

修改為

<form action="{% url 'app1_name:inserpath' %}" method="post">

注意引用方法是冒號而不是圓點也不是斜槓!!!!!!!!!!!!

帶參路由的生成

url引數除了能匹配使用者輸入的網址。Django還提供了直接用url模式和提供的引數生成一個網址字串。例如:

from django.shortcuts import reverse

urlpath = reverse('app1_name:detail', args=(user.id,)) # 將args傳遞給app1_name:detail對應的檢視,然後生成網址。

上面程式碼中的app1_name為某一路由的名稱空間,detail為某一對映的name名稱。

重定向

在實現邏輯功能時,可能會需要實現重定向的功能。

1、通過redirect函式或HttpResponseRedirect函式硬編碼的形式。

函式的引數可以是絕對路徑跟相對路徑,甚至可以使用get方式傳引數

from django.shortcuts import redirect,HttpResponseRedirect

return redirect('/app1/alluser/')  # 硬編碼形式
return redirect('/app1/finduser/?userid=62')  # 傳遞引數

就是重新開啟‘/app1/alluser/’網址。

2、通過URLconf路由名稱空間的形式。

return redirect('app1_name:alluserpath')

3、如果在邏輯函式中不做任何處理,可以直接在url中配置

自定義錯誤頁面

當Django找不到與請求匹配的URL時,或者當丟擲一個異常時,將呼叫一個錯誤處理檢視。錯誤檢視包括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頁面實在醜陋,通常我們都自定義錯誤頁面。

首先,在根路由檔案hello/urls.py檔案中額外增加下面的條目:

urlpatterns = [
....
]

from . import views
# 增加的條目
handler400 = views.bad_request
handler403 = views.permission_denied
handler404 = views.page_not_found
handler500 = views.page_error

然後在根檢視檔案hello/views.py檔案(如果沒有此檔案就新建一個)中新增

from django.shortcuts import render

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


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


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


def bad_request(request):
    return render(request, '400.html')

然後在根模板目錄hello/templates資料夾下新建400.html、403.html、404.html、500.html根據自己的需求建立這些檔案。

我這裡只是簡單編寫了這幾個檔案,示例如下400.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>您出錯了</title>
</head>
<body>
    <p>400頁面</p>
</body>
</html>