路由的編寫方式是Django2.0和1.11最大的區別所在,Django官方迫於壓力和同行的影響,不得不將原來的正則匹配表達式,改成更加簡單的path表達式,但依然經過re_path()方法保持對1.x版本的兼容php
URL是web服務的入口,用戶經過瀏覽器發送過來的任何請求,都會發送到一個指定的URL地址,而後被響應html
在Django項目中編寫路由,就是向外暴露咱們接收哪些URL的請求,django奉行DRY主義,提倡使用簡潔、優雅的URL,沒有.php這種後綴,它可讓你爲所欲爲的設計你的URL,不受框架束縛。前端
URL路由在django項目中的體現就是urls.py文件,這個文件能夠有不少個,但不能在同一目錄下,實際上django提倡項目有個根urls.py,每一個app下分別有本身的urls.py,集中又分治,是一種解耦的模式python
在新建一個django項目後,默認會自動爲咱們建立一個urls.py文件,它會默認建立一個admin後臺的URLgit
from django.contrib import admin from django.urls import path urlpatterns = [ path('admin/', admin.site.urls), ]
當用戶請求一個頁面時,django根據下面的邏輯執行操做:web
(1)決定要使用的根URLconf模塊,一般這是ROOT_URLCONF設置的值,可是若是傳入的HttpRequest對象具備urlconf屬性(由中間件設置),則其值將被用於代替ROOT_URLCONF設置,通俗的講就是你能夠自定義項目入口url是哪一個文件正則表達式
(2)加載該模塊並尋找可用的urlpatterns,它是django.urls.path()或django.urls.re_path實例的一個列表django
(3)依次匹配每一個URL模式,在與親求的URL相匹配的第一個模式停下來,也就是說,url匹配是從上往下的短路操做,因此url在列表中的位置很是關鍵瀏覽器
(4)導入並調用匹配行中給定的視圖,該視圖是一個簡單的python函數django中被稱爲視圖函數,或基於類的視圖,視圖講得到以下參數:安全
(5)若是沒有匹配到任何表達式,或者過程當中拋出異常,講調用一個適當的錯誤處理視圖
下面是一個簡單的URLconf例子:
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), ]
注意:
上面示例匹配例子:
每當urls.py文件被第一次加載的時候,urlpatterns裏的表達式都將被預編譯,這會大大提升系統處理路由的速度。
默認狀況下,Django內置下面的路徑轉換器:
對於更復雜的匹配要求,能夠自定義路徑轉換器
自定義轉換器必須是一個包含如下內容的類:
例如:新建一個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中使用register_converter()註冊自定義轉換器類並使用它:
from django.urls import path, register_converter 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()方法,只不過導入的位置變了
在python正則表達式中,命名正則表達式的語法是(?P<name>pattern),name是組的名稱,而pattern是要匹配的模式
這是上面的示例,使用正則表達式重寫:
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()方法能夠指定轉換成某種類型,在視圖中接收參數時必定要當心
請求的URL被看作是一個普通的python字符串,URLconf在其中查找並匹配,進行匹配是將不包括GET或POST方式的參數及域名,URLconf不檢查使用何種HTTP請求方法,全部請求方法POST、GET、HEAD等都將路由到同一個視圖,在視圖中才根據具體請求方法的不一樣,進行不一樣的處理。
有一個方便的小技巧,咱們能夠指定視圖參數的默認值,下面是一個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、40三、40四、和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模版,用於返回錯誤頁面給用戶,可是這些頁面很簡陋,一般咱們都自定義錯誤頁面
首先在根URLconf中額外增長下面的條目,並導入views模塊:
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
而後在app/views.py文件中增長四個處理視圖:
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')
再根據本身的需求,建立對應的400、40三、40四、500的HTML四個頁面文件就能夠了
一般咱們會在每一個app裏各自建立一個urls.py路由模塊,而後從根路由出發,將app所屬的url請求,所有轉發到相應的urls.py模塊中
例如,下面的django網站自己的URLconf節選,它包含許多其餘URLconf:
from django.urls import include, path urlpatterns = [ # ... snip ... path('community/', include('aggregator.urls')), path('contact/', include('contact.urls')), # ... snip ... ]
路由轉發使用的是include()方法,須要提早導入,它的參數是轉發目的地路徑的字符串,路徑以圓點分隔
每當django遇到include()時,它會去掉URL中匹配到的部分並將剩下的字符串發送給include的URLconf作進一步處理,也就是轉發到二級路由去
另一種轉發其它URL模式的方式是使用一個path()實例的列表,如如下URLconf:
from django.urls import include, path from apps.main import views as main_views from credit import views as credit_views extra_patterns = [ path('reports/', credit_views.report), path('reports/<int:id>/', credit_views.report), path('charge/', credit_views.charge), ] urlpatterns = [ path('', main_views.homepage), path('help/', include('apps.help.urls')), path('credit/', include(extra_patterns)), ]
在此示例中,/credit/reports/ URL將由credit_views.report()視圖處理,這種作法,至關於把二級路由模塊內的代碼寫到根路由模塊裏了,不是很推薦。
再看另一個URLconf示例:
from django.urls import path from . import views urlpatterns = [ path('<page_slug>-<page_id>/history/', views.history), path('<page_slug>-<page_id>/edit/', views.edit), path('<page_slug>-<page_id>/discuss/', views.discuss), path('<page_slug>-<page_id>/permissions/', views.permissions), ]
上面的路由寫得很重複,咱們可改進它,只須要聲明共同的路徑前綴一次,並將後面的部分分組轉發:
from django.urls import include, path from . import views urlpatterns = [ path('<page_slug>-<page_id>/', include([ path('history/', views.history), path('edit/', views.edit), path('discuss/', views.discuss), path('permissions/', views.permissions), ])), ]
這樣就優雅多了,但須要理解這種作法。
目的地URLconf會收到來自父URLconf捕獲的全部參數,看下面的例子:
# In settings/urls/main.py from django.urls import include, path urlpatterns = [ path('<username>/blog/', include('foo.urls.blog')), ] # In foo/urls/blog.py from django.urls import path from . import views urlpatterns = [ path('', views.blog.index), path('archive/', views.blog.archive), ]
在上面的例子中,捕獲的「username」變量將被傳遞給include()指向的URLconf,再進一步傳遞給對應的視圖
URLconfs具備一個鉤子(hook),容許你傳遞一個python字典做爲額外的關鍵字參數給視圖函數,像下面的這樣:
from django.urls import path from . import views urlpatterns = [ path('blog/<int:year>/', views.year_archive, {'foo': 'bar'}), ]
在這個例子中,對於/blog/2005/請求,django將調用views.year_archive(request,year=2005,foo='bar'),理論上,你能夠在這個字典裏傳遞任何你想要傳遞的東西,可是要注意,URL模式捕獲的命名關鍵字參數和在字典中傳遞的額外參數有可用具備相同的名稱,這會發生衝突,要避免。
相似上面的例子,也能夠傳遞額外的參數給include(),參數會傳遞給include指向的urlconf中的每一行。
例如,下面兩種URLconf配置方式在功能上徹底相同:
配置一:
# main.py from django.urls import include, path urlpatterns = [ path('blog/', include('inner'), {'blog_id': 3}), ] # inner.py from django.urls import path from mysite import views urlpatterns = [ path('archive/', views.archive), path('about/', views.about), ]
配置二:
# main.py from django.urls import include, path from mysite import views urlpatterns = [ path('blog/', include('inner')), ] # inner.py from django.urls import path urlpatterns = [ path('archive/', views.archive, {'blog_id': 3}), path('about/', views.about, {'blog_id': 3}), ]
注意:只有當你肯定被include的URLconf中的每個視圖都接收你傳遞給它們額外參數時纔有意義,不然其中一個以上視圖不接收該參數將致使錯誤異常。
在實際的django項目中,常常須要獲取某條URL,爲生成的內容配置URL連接,好比,我要在頁面上展現一列文件列表,每一個條目都是一個超級連接,點擊就進入該文章的詳細頁面。
如今咱們的URLconf是這麼配置的:
path('post/<int:pk>/',views.some_view),
在前端中,這就須要爲HTML的<a>標籤的href屬性提供一個諸如http://www.xxx.com/post/3/的值。其中的域名部分,Django會幫你自動添加無須關心,咱們關注的是post/3/。
此時,必定不能硬編碼URL爲post/3/,那樣費時、不可伸縮,並且容易出錯。試想,若是哪天,由於某種緣由,須要將urlconf中的表達式改爲entry/<int:pk>/,爲了讓連接正常工做,必須修改對應的herf屬性值,因而你去項目裏將全部的post/3/都改爲entry/3/嗎?顯然這是不現實的!
咱們須要一種安全、可靠、自適應的機制,當修改URLconf中的代碼後,無需在項目源碼中大範圍搜索、替換失效的硬編碼URL。
爲了解決這個問題,Django提供了一種解決方案,只需在URL中提供一個name參數,並賦值一個你自定義的、好記的、直觀的字符串。
經過這個name參數,能夠反向解析URL、反向URL匹配、反向URL查詢或者簡單的URL反查。
在須要解析URL的地方,對於不一樣層級,Django提供了不一樣的工具用於URL反查:
先看下面的URLconf示例:
from django.urls import path from . import views urlpatterns = [ #... path('articles/<int:year>/', views.year_archive, name='news-year-archive'), #... ]
2019年對應的歸檔URL是/articles/2019/,能夠在模版的代碼中使用下面的方法得到它們:
<a href="{% url 'news-year-archive' 2012 %}">2012 Archive</a> {# Or with the year in a template context variable: #} <ul> {% for yearvar in year_list %} <li><a href="{% url 'news-year-archive' yearvar %}">{{ yearvar }} Archive</a></li> {% endfor %} </ul>
或者在python代碼中這樣使用:
在視圖函數中使用HttpResponseRedirect重定向,而後經過reverse反向解析name別名的URL和參數
from django.http import HttpResponseRedirect from django.urls import reverse def redirect_to_year(request): # ... year = 2006 # ... return HttpResponseRedirect(reverse('news-year-archive', args=(year,)))
其中,起到核心做用的是咱們經過name='news-year-archive'爲那條URL起了一個能夠被引用的名稱。
URL名稱name使用的字符串能夠包含任何你喜歡的字符,可是過分的放縱有可能帶來重名的衝突,好比兩個不一樣的app,在各自的URLconf中爲某一條URL取了相同的name,這就會帶來麻煩,爲了解決這個問題,又引出了下面的命名空間。
URL命名空間能夠保證反查到惟一的URL,即便不一樣的APP使用相同的URL名稱
第三方應用始終使用帶有命名空間的URL是一個很好的作法,相似地,它還容許你在一個應用有多個實例部署的狀況下反查URL,通俗的將,由於一個應用的多個實例共享相同的命名URL,命名空間提供了一種區分這些命名URL的方法。
實現命名空間的作法很簡單,在URLconf文件中添加app_name = ‘polls’和namespace = 'author-polls'這種相似的定義。
之前面的polls應用的兩個實例爲例:‘publisher-polls’和‘author-polls’
假如咱們已經在建立和顯示投票時考慮了實例命名空間的問題,namespace指定命名空間名,代碼以下:
urls.py from django.urls import include, path urlpatterns = [ path('author-polls/', include('polls.urls', namespace='author-polls')), path('publisher-polls/', include('polls.urls', namespace='publisher-polls')), ]
polls/urls.py from django.urls import path from . import views app_name = 'polls' urlpatterns = [ path('', views.IndexView.as_view(), name='index'), path('<int:pk>/', views.DetailView.as_view(), name='detail'), ... ]
若是當前的APP實例時其中的一個,列如咱們正在渲染實例'author-pools'中的detail視圖,'polls:index'將解析到'author-polls'實例的index視圖。
根據以上設置,在基於類的視圖的方法中,可使用下面的查詢:
reverse('polls:index', current_app=self.request.resolver_match.namespace)
並在模版中使用:
{% url 'polls:index' %}
若是沒有當前APP實例,例如若是咱們在站點的其餘地方渲染一個頁面,'pools:index‘將解析到polls註冊的最後一個APP實例空間,由於沒有默認的實例(命名空間爲'pools'的實例),將使用註冊的polls的最後一個實例,這是將'publisher-polls',由於它是在urlpatterns中最後一個聲明的。
能夠經過兩種方式指定include的URLconf的應用名稱空間
第一種:在include的URLconf模塊中設置與urlpatterns屬性相同級別的app_name屬性,必須將實際模塊或模塊的字符串引用傳遞到include(),而不是urlpatterns自己的列表。
polls/urls.py
from django.urls import path from . import views app_name = 'polls' urlpatterns = [ path('', views.IndexView.as_view(), name='index'), path('<int:pk>/', views.DetailView.as_view(), name='detail'), ... ]
urls.py:
from django.urls import include, path urlpatterns = [ path('polls/', include('polls.urls')), ]
此時,polls.urls中定義的URL將具備應用名稱空間polls
第二種:include一個包含嵌套命名空間輸的對象,格式以下:
(<list of path()/re_path() instances>, <application namespace>)
實例以下:
from django.urls import include, path from . import views polls_patterns = ([ path('', views.IndexView.as_view(), name='index'), path('<int:pk>/', views.DetailView.as_view(), name='detail'), ], 'polls') urlpatterns = [ path('polls/', include(polls_patterns)), ]
這將include指定的URL模式到給定的APP命名空間,可使用include()的namespace參數指定APP實例命名空間,若是未指定,則APP實例命名空間默認爲URLconf的APP命名空間。