Django 系列部落格(六)
Django 系列部落格(六)
前言
本篇部落格介紹 Django 中的路由控制部分,一個網路請求首先到達的就是路由這部分,經過路由與檢視層的對映關係再執行相應的程式碼邏輯並將結果返回給客戶端。
Django 中路由的作用
URL 配置(URLconf)可以比作是 Django 支撐網站的目錄。它的本質是 URL 要為該 URL 滴啊用的檢視函式之間的對映表。以這種方式告訴 Django,對於客戶端發來的 URL 要具體呼叫檢視層的哪段程式碼。
from django.urls import url from app import views urlpatterns = [ url(r'^$', views.home), ] # ^$這個路由對應檢視函式中的 home 方法,只要瀏覽器往該網址傳送請求,就會響應到這個函式執行。
簡單的路由配置
from django.conf.urls import url
urlpatterns = [
url(正則表示式, views檢視函式, 預設引數, 路由別名),
]
- 正則表示式:一個正則表示式字串;
- views 檢視函式:一個可呼叫物件,通常為一個檢視函式或一個指定檢視路徑的字串;
- 預設引數:可選的要傳遞給檢視函式的預設引數(字典形式);
- 路由別名:一個可選的name 引數。
from django.conf.urls import url from app import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^article/(\d{4})/(\d{2})', views.article), url(r'^user/(?P<name>[a-z]{3})', views.user_havename), url(r'^user/$', views.user), ]
注意:
- 若要從 URL 中捕獲一個值,只需要在它周圍放置一對圓括號;
- 不需要新增一個前導的反斜槓,因為每個 url 都有。例如,應該是
^articles
而不是^/articles
。 - 每個正則表示式前面的
r
是可選的,但是建議加上,表示這是個原生字串,字串中的任何字元都不應該轉義。 - urlpatterns 中的元素按照書寫順序從上往下逐一匹配正則表示式,一旦匹配成功則不再繼續。
一些請求的例子: /articles/2005/03/ 請求將匹配列表中的第三個模式。Django 將呼叫函式views.month_archive(request, '2005', '03')。 /articles/2005/3/ 不匹配任何URL 模式,因為列表中的第三個模式要求月份應該是兩個數字。 /articles/2003/ 將匹配列表中的第一個模式不是第二個,因為模式按順序匹配,第一個會首先測試是否匹配。請像這樣自由插入一些特殊的情況來探測匹配的次序。 /articles/2003 不匹配任何一個模式,因為每個模式要求URL 以一個反斜線結尾。 /articles/2003/03/03/ 將匹配最後一個模式。Django 將呼叫函式views.article_detail(request, '2003', '03', '03')。
APPEND_SLASH
該引數的作用是會在訪問連線的時候,如果連線沒有加斜槓,會自動加上。可以在global_settings.py
中修改。
專案的 settings 中沒有改配置的選項,一個專案會有兩個 settings。
效果:
from django.conf.urls import url
from app_01 import views
urlpatterns = [
url(r'^blog/$', views.blog),
]
當我們訪問http://www.example.com/blog
時,預設將網址自動替換成http://www.example.com/blog/
。如果將該配置值設為false
,此時在向該網址傳送請求時就會提示找不到頁面。
分組
當我們需要捕獲 url 中的引數並要傳遞給檢視函式時,有兩種捕獲方式:無名分組和有名分組。無名分組就是該值沒有變數標識,傳值的時候採用位置引數傳遞;有名分組就是給捕獲的值賦值一個變數,這樣就可以通過關鍵字引數傳值了。
無名分組
# urls.py檔案
from django.conf.urls import url
from app_01 import views
urlpatterns = [
url(r'^article/(\d{4})/(\d{2})', views.article),
]
# views.py檔案
from django.shortcuts import HttpResponse
def article(reques, year, month):
return HttpResponse('您要檢視%s 年%s 月的文章' %(year, month))
通過圓括號捕獲年份和月份,然後通過位置引數傳遞給 year 和 month 變數,接著在 views 函式中使用。
有名分組
import re
ret=re.search('(?P<year>[0-9]{4})/([0-9]{2})','2012/12')
print(ret.group())
print(ret.group(1))
print(ret.group(2))
print(ret.group('year'))
這些示例使用簡單的、沒有命名的正則表示式組(通過圓括號)來捕獲 URL 中的值並以位置引數傳遞給檢視。在更高階的用法中,可以使用命名的正則表示式組來捕獲 URL 中的值並以關鍵子引數傳遞給檢視函式。
在python 正則表示式中,命名有名分組的語法時(P<變數名>pattern)
,其中變數名是分組的識別符號,pattern 是要匹配的正則表示式。下面是上面 URLconf 使用有名分組的重寫:
from django.urls import path, re_path
from app_01 import views
urlpatterns = [
re_path(r'^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<day>[0-9]{2})/$', views.archive_detail),
]
# 捕獲到的資料都是 str 型別
# 檢視函式裡可以指定預設值
url('blog/$', views.blog),
url('blog/?(?P<num>[0-9]{1})', views.blog),
def blog(request, num=1):
print(num)
return HttpResponse('ok')
這個實現與前面的示例完全相同,只有一個細微的差別:捕獲的值作為關鍵字引數而不是位置引數傳遞給檢視函式。例如:
/articles/2005/03/ 請求將呼叫views.month_archive(request, year='2005', month='03')函式,而不是views.month_archive(request, '2005', '03')。
/articles/2003/03/03/ 請求將呼叫函式views.article_detail(request, year='2003', month='03', day='03')。
在實際應用中,這意味你的 URLconf 會更加清晰且不容易產生引數順序問題的錯誤,可以在檢視函式定義中重新安排引數的順序。
路由分發
其實和專案名相同的資料夾下面的urls.py
檔案是整個專案的根路由:
這是整個專案的根路由,所有向該專案傳送的連線請求,首先需要從該路由配置裡面過濾,如果只有一個應用,或者路由配置不多一個根路由就足夠了,但當 app 多起來之後還是使用一個根路由配置會造成路由混亂,所以有了路由分發,比如這是發往app_01
應用的連線,那麼在根路由中進行路由分發,把連線轉向app_01
中的路由中進行處理,這就是路由分發。
# 根路由配置
from django.conf.urls import url, include
from app_01 import views
from app_01 import urls
urlpatterns = [
url(r'^app_01/', include('app_01.urls')), # 注意前面的正則表示式後面不能加$
url(r'^app_01/', include(urls)),
]
# 應用路由配置
from django.conf.urls import url
from app_01 import views
urlpatterns = [
url(r'^test/(?P<year>[0-9]{2})/$', views.url_test),
]
反向解析
在使用 Django 專案時,一個常見的需求是獲得 URL 的最終形式,以用於嵌入到生成的內容中(檢視中和顯示給使用者的 URL 等)或者用於處理伺服器端的導航(重定向等)。人們強烈希望不要硬編碼這些 URL(費時費力、不可擴充套件且易產生錯誤)或者設計一種與 URLconf 毫不相關的專門的 URL 生成機制,因為這樣容易導致一定程度上產生過期的 URL。
在需要 URL 的地方,對於不同層級,Django 提供不同的工具用於 URL 反查:
- 在模板中:使用 url 模板標籤;
- 在 python 程式碼中:使用
from django.urls import reverse
。
# urls.py檔案
from django.urls import path, re_path
from app_01 import views
urlpatterns = [
re_path(r'^test/(?P<year>[0-9]{2})/(P?<month>[0-9]{2})/$', views.url_test, name='test'),
]
<!-- html檔案 -->
<a href="{% url 'test' 10 23 %}">哈哈</a>
# views.py檔案
from django.shortcuts import render, HttpResponse, redirect, reverse
def url_test(request, year, month):
url = reverse('test', args=(10, 20))
return HttpResponse('ok')
總結:
- 在 html 程式碼裡面使用{% url '別名' 引數 引數... %};
- 在檢視函式中:
url = reverse('test')
url = reverse('test', args=(10, 20))
當命名 URL 模式時,要確保使用的名稱不會與其他應用中的名稱產生衝突。如果你的 URL 模式叫做 comment,而另外一個應用中也有一個同樣的名字,當你在模板中使用這個名稱的時候不能保證將插入哪個 URL。在 URL 名稱加上一個字首,比如應用的名稱,將會減少衝突的可能。建議使用myapp-comment
。
名稱空間
名稱空間(namespace)是表示識別符號的可見範圍。一個識別符號可在多個名稱空間中定義,它在不同名稱空間中的含義是互不相干的。這樣在一個新的名稱空間中可以定義任何識別符號,它們不會與任何已有的識別符號產生衝突,因為已有的定義處於其他的名稱空間中。
由於name
沒有作用域,Django 在返解 URL 時,會在專案的全域性路徑中按順序搜尋,當查詢到第一個name
指定的 URL 時,立即返回。
在開發專案時,會經常使用name
屬性反解出 URL,當不小心在不同的 app 的 urls 中定義相同的name
時,可能會導致 URL 反解錯誤,為了避免發生引入了名稱空間。
建立 app01和 app02
python manage.py startapp app01
python manage.py startapp app02
根路由
from django.urls import path,re_path,include
urlpatterns = [
path('app01/', include('app01.urls')),
path('app02/', include('app02.urls'))
]
app01的 urls.py
from django.urls import path,re_path
from app01 import views
urlpatterns = [
re_path(r'index/',views.index,name='index'),
]
app02的 urls.py
from django.urls import path, re_path, include
from app02 import views
urlpatterns = [
re_path(r'index/', views.index,name='index'),
]
app01的檢視
def index(request):
url=reverse('index')
print(url)
return HttpResponse('index app01')
app02的檢視
def index(request):
url=reverse('index')
print(url)
return HttpResponse('index app02')
這樣都找index.html
,app01和 app02找到的都是app02的 index。如何處理?在路由分發的時候指定名稱空間。根路由在路由分發是,指定名稱空間。
# 一
path('app01/', include(('app01.urls','app01'))),
path('app02/', include(('app02.urls','app02')))
# 二
url(r'app01/',include('app01.urls',namespace='app01')),
url(r'app02/',include('app02.urls',namespace='app02'))
# 三
url(r'app01/',include(('app01.urls','app01'))),
url(r'app02/',include(('app02.urls','app02')))
在檢視函式反向解析的時候,指定名稱空間:
url=reverse('app02:index')
print(url)
url2=reverse('app01:index')
print(url2)
在模板裡面也是用相應的名稱空間名:
<a href="{% url 'app02:index'%}">哈哈</a>
Django2.0版的 path
Django2.0的re_path
和1.0的url
一樣。2.0多了個path
思考情況如下:
urlpatterns = [
re_path('articles/(?P<year>[0-9]{4})/', year_archive),
re_path('article/(?P<article_id>[a-zA-Z0-9]+)/detail/', detail_view),
re_path('articles/(?P<article_id>[a-zA-Z0-9]+)/edit/', edit_view),
re_path('articles/(?P<article_id>[a-zA-Z0-9]+)/delete/', delete_view),
]
考慮下這樣的兩個問題:
第一個問題,函式 year_archive
中year引數是字串型別的,因此需要先轉化為整數型別的變數值,當然year=int(year)
不會有諸如如TypeError或者ValueError的異常。那麼有沒有一種方法,在url中,使得這一轉化步驟可以由Django自動完成?
第二個問題,三個路由中article_id都是同樣的正則表示式,但是你需要寫三遍,當之後article_id規則改變後,需要同時修改三處程式碼,那麼有沒有一種方法,只需修改一處即可?
在Django2.0中,可以使用 path
解決以上的兩個問題。
基本示例
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>/', views.article_detail),
# path才支援,re_path不支援
path('order/<int:year>',views.order),
]
基本規則:
- 使用尖括號(<>)從 url中捕獲值;
- 捕獲值中可以包含一個轉化器型別(converter type),比如使用
<int:name>
捕獲一個整數變數。如果沒有轉化器,將匹配任何字串,也包含/
字元。 - 無需新增前導斜槓
以上是示例,分別和上面的基本示例對應:
path 轉換器
- str:匹配除了路徑分隔符(/)之外的非空字串,這是預設的形式;
- int:匹配正整數,包含0;
- slug:匹配字母、數字、橫槓以及下劃線組成的字串;
- uuid:匹配格式化的
uuid
,如075194d3-6885-417e-a8a8-6c931e272f00。 - path: 匹配任何非空字元,包含了路徑分隔符(/)
註冊自定義轉化器
對於一些複雜或者複用的需要,可以定義自己的轉化器。轉化器是一個類或介面,它的要求有三點:
- regex類屬性,字串型別;
- to_python(self, value)方法,value 是由類屬性 regex 所匹配到的字串,返回具體的 python 變數值,以供 Django 傳遞到對應的檢視函式中;
- to_url(self, value)方法,和to_python相反,value 是一個具體的 python 變數值,返回其字串,通常用於 url 反向引用。
示例
class FourDigitYearConverter:
regex = '[0-9]{4}'
def to_python(self, value):
return int(value)
def to_url(self, value):
return '%04d' % value
使用register_converter
將其註冊到 URL 配置中:
from django.urls import register_converter, path
from . import converters, views
register_converter(converters.FourDigitYearConverter, 'yyyy')
urlpatterns = [
path('articles/2003/', views.spwcial_case_2003),
path('articles/<yyyy:year>/', views.year_archive),
]