簡潔、優雅的URL 模式在高質量的Web 應用中是一個很是重要的細節。Django 容許你任意設計你的URL,不受框架束縛。php
不要求有.php
或.cgi
,更不會要求相似0,2097,1-1-1928,00
這樣無心義的東西。html
參見萬維網的發明者Berners-Lee 的Cool URIs don’t change,裏面有關於爲何URL 應該保持整潔和有意義的卓越的論證。python
爲了給一個應用設計URL,你須要建立一個Python 模塊,一般稱爲URLconf(URL configuration)。這個模塊是純粹的Python 代碼,包含URL 模式(簡單的正則表達式)到Python 函數(你的視圖)的簡單映射。web
映射可短可長,隨便你。它能夠引用其它的映射。並且,由於它是純粹的Python 代碼,它能夠動態構造。正則表達式
Django 還提供根據當前語言翻譯URL 的一種方法。更多信息參見國際化文檔。算法
當一個用戶請求Django 站點的一個頁面,下面是Django 系統決定執行哪一個Python 代碼使用的算法:django
Django 決定要使用的根URLconf
模塊。一般,這個值就是ROOT_URLCONF
的設置,可是若是進來的HttpRequest
對象具備一個urlconf
屬性(經過中間件request
processing
設置),則使用這個值來替換ROOT_URLCONF
設置。瀏覽器
Django 加載該Python 模塊並尋找可用的urlpatterns
。它是django.conf.urls.url()
實例的一個Python 列表。服務器
Django 依次匹配每一個URL 模式,在與請求的URL 匹配的第一個模式停下來。app
一旦其中的一個正則表達式匹配上,Django 將導入並調用給出的視圖,它是一個簡單的Python 函數(或者一個基於類的視圖)。視圖將得到以下參數:
一個HttpRequest
實例。
若是匹配的正則表達式沒有返回命名的組,那麼正則表達式匹配的內容將做爲位置參數提供給視圖。
關鍵字參數由正則表達式匹配的命名組組成,可是能夠被django.conf.urls.url()
的可選參數kwargs
覆蓋。
若是沒有匹配到正則表達式,或者若是過程當中拋出一個異常,Django 將調用一個適當的錯誤處理視圖。請參見下面的錯誤處理。
下面是一個簡單的 URLconf:
from django.conf.urls import url from . import views urlpatterns = [ url(r'^articles/2003/$', views.special_case_2003), url(r'^articles/([0-9]{4})/$', views.year_archive), url(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive), url(r'^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$', views.article_detail), ]
注:
若要從URL 中捕獲一個值,只須要在它周圍放置一對圓括號。
不須要添加一個前導的反斜槓,由於每一個URL 都有。例如,應該是^articles
而不是 ^/articles
。
每一個正則表達式前面的'r' 是可選的可是建議加上。它告訴Python 這個字符串是「原始的」 —— 字符串中任何字符都不該該轉義。參見Dive Into Python 中的解釋。
一些請求的例子:
/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')
。
上面的示例使用簡單的、沒有命名的正則表達式組(經過圓括號)來捕獲URL 中的值並以位置 參數傳遞給視圖。在更高級的用法中,可使用命名的正則表達式組來捕獲URL 中的值並以關鍵字 參數傳遞給視圖。
在Python 正則表達式中,命名正則表達式組的語法是(?P<name>pattern)
,其中name
是組的名稱,pattern
是要匹配的模式。
下面是以上URLconf 使用命名組的重寫:
from django.conf.urls import url from . import views urlpatterns = [ url(r'^articles/2003/$', views.special_case_2003), url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive), url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive), url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})/$', views.article_detail), ]
這個實現與前面的示例徹底相同,只有一個細微的差異:捕獲的值做爲關鍵字參數而不是位置參數傳遞給視圖函數。例如:
/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 會更加明晰且不容易產生參數順序問題的錯誤 —— 你能夠在你的視圖函數定義中從新安排參數的順序。固然,這些好處是以簡潔爲代價;有些開發人員認爲命名組語法醜陋而繁瑣。
下面是URLconf 解析器使用的算法,針對正則表達式中的命名組和非命名組:
若是有命名參數,則使用這些命名參數,忽略非命名參數。
不然,它將以位置參數傳遞全部的非命名參數。
根據傳遞額外的選項給視圖函數(下文),這兩種狀況下,多餘的關鍵字參數也將傳遞給視圖。
URLconf 在請求的URL 上查找,將它當作一個普通的Python 字符串。不包括GET和POST參數以及域名。
例如,http://www.example.com/myapp/ 請求中,URLconf 將查找myapp/
。
在 http://www.example.com/myapp/?page=3 請求中,URLconf 仍將查找myapp/
。
URLconf 不檢查請求的方法。換句話講,全部的請求方法 —— 同一個URL的POST
、GET
、HEAD
等等 —— 都將路由到相同的函數。
每一個捕獲的參數都做爲一個普通的Python 字符串傳遞給視圖,不管正則表達式使用的是什麼匹配方式。例如,下面這行URLconf 中:
url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
... views.year_archive()
的year
參數將是一個字符串,即便[0-9]{4}
值匹配整數字符串。
有一個方便的小技巧是指定視圖參數的默認值。 下面是一個URLconf 和視圖的示例:
# URLconf from django.conf.urls import url from . import views urlpatterns = [ url(r'^blog/$', views.page), url(r'^blog/page(?P<num>[0-9]+)/$', 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
值。
urlpatterns
中的每一個正則表達式在第一次訪問它們時被編譯。這使得系統至關快。
urlpatterns
應該是url()
實例的一個Python 列表。
當Django 找不到一個匹配請求的URL 的正則表達式時,或者當拋出一個異常時,Django 將調用一個錯誤處理視圖。
這些狀況發生時使用的視圖經過4個變量指定。它們的默認值應該知足大部分項目,可是經過賦值給它們以進一步的自定義也是能夠的。
完整的細節請參見自定義錯誤視圖。
這些值能夠在你的根URLconf 中設置。在其它URLconf 中設置這些變量將不會生效果。
它們的值必須是可調用的或者是表示視圖的Python 完整導入路徑的字符串,能夠方便地調用它們來處理錯誤狀況。
這些值是:
handler404
—— 參見django.conf.urls.handler404
。
handler500
—— 參見django.conf.urls.handler500
。
handler403
—— 參見django.conf.urls.handler403
。
handler400
—— 參見django.conf.urls.handler400
。
在任什麼時候候,你的urlpatterns 均可以包含其它URLconf 模塊。這實際上將一部分URL 放置與其它URL 下面。
例如,下面是URLconf for the Django 網站本身的URLconf 中一個片斷。它包含許多其它URLconf:
from django.conf.urls import include, url urlpatterns = [ # ... snip ... url(r'^community/', include('django_website.aggregator.urls')), url(r'^contact/', include('django_website.contact.urls')), # ... snip ... ]
注意,這個例子中的正則表達式沒有包含$(字符串結束匹配符),可是包含一個末尾的反斜槓。每當Django 遇到include()
(django.conf.urls.include()
)時,它會去掉URL 中匹配的部分並將剩下的字符串發送給包含的URLconf 作進一步處理。
另一種包含其它URL 模式的方式是使用一個url() 實例的列表。例如,請看下面的URLconf:
from django.conf.urls import include, url from apps.main import views as main_views from credit import views as credit_views extra_patterns = [ url(r'^reports/(?P<id>[0-9]+)/$', credit_views.report), url(r'^charge/$', credit_views.charge), ] urlpatterns = [ url(r'^$', main_views.homepage), url(r'^help/', include('apps.help.urls')), url(r'^credit/', include(extra_patterns)), ]
在這個例子中,/credit/reports/
URL將被 credit.views.report()
這個Django 視圖處理。
這種方法能夠用來去除URLconf 中的冗餘,其中某個模式前綴被重複使用。例如,考慮這個URLconf:
from django.conf.urls import url from . import views urlpatterns = [ url(r'^(?P<page_slug>[\w-]+)-(?P<page_id>\w+)/history/$', views.history), url(r'^(?P<page_slug>[\w-]+)-(?P<page_id>\w+)/edit/$', views.edit), url(r'^(?P<page_slug>[\w-]+)-(?P<page_id>\w+)/discuss/$', views.discuss), url(r'^(?P<page_slug>[\w-]+)-(?P<page_id>\w+)/permissions/$', views.permissions), ]
咱們能夠改進它,經過只聲明共同的路徑前綴一次並將後面的部分分組:
from django.conf.urls import include, url from . import views urlpatterns = [ url(r'^(?P<page_slug>[\w-]+)-(?P<page_id>\w+)/', include([ url(r'^history/$', views.history), url(r'^edit/$', views.edit), url(r'^discuss/$', views.discuss), url(r'^permissions/$', views.permissions), ])), ]
被包含的URLconf 會收到來之父URLconf 捕獲的任何參數,因此下面的例子是合法的:
# In settings/urls/main.py from django.conf.urls import include, url urlpatterns = [ url(r'^(?P<username>\w+)/blog/', include('foo.urls.blog')), ] # In foo/urls/blog.py from django.conf.urls import url from . import views urlpatterns = [ url(r'^$', views.blog.index), url(r'^archive/$', views.blog.archive), ]
在上面的例子中,捕獲的"username
"變量將被如期傳遞給包含的 URLconf。
正則表達式容許嵌套的參數,Django 將解析它們並傳遞給視圖。當反查時,Django 將嘗試填滿全部外圍捕獲的參數,並忽略嵌套捕獲的參數。考慮下面的URL 模式,它帶有一個可選的page
參數:
from django.conf.urls import url urlpatterns = [ url(r'blog/(page-(\d+)/)?$', blog_articles), # bad url(r'comments/(?:page-(?P<page_number>\d+)/)?$', comments), # good ]
兩個模式都使用嵌套的參數,其解析方式是:例如blog/page-2/
將匹配blog_articles
並帶有兩個位置參數page-2/
和2。第二個comments
的模式將匹配comments/page-2/
並帶有一個值爲2 的關鍵字參數page_number
。這個例子中外圍參數是一個不捕獲的參數(?:...)
。
blog_articles
視圖須要最外層捕獲的參數來反查,在這個例子中是page-2/
或者沒有參數,而comments
能夠不帶參數或者用一個page_number
值來反查。
嵌套捕獲的參數使得視圖參數和URL 之間存在強耦合,正如blog_articles
所示:視圖接收URL(page-2/
)的一部分,而不僅是視圖感興趣的值。這種耦合在反查時更加顯著,由於反查視圖時咱們須要傳遞URL 的一個片斷而不僅是page 的值。
做爲一個經驗的法則,當正則表達式須要一個參數但視圖忽略它的時候,只捕獲視圖須要的值並使用非捕獲參數。
URLconfs 具備一個鉤子,讓你傳遞一個Python 字典做爲額外的參數傳遞給視圖函數。
django.conf.urls.url()
函數能夠接收一個可選的第三個參數,它是一個字典,表示想要傳遞給視圖函數的額外關鍵字參數。
例如:
from django.conf.urls import url from . import views urlpatterns = [ url(r'^blog/(?P<year>[0-9]{4})/$', views.year_archive, {'foo': 'bar'}), ]
在這個例子中,對於/blog/2005/
請求,Django 將調用views.year_archive(request, year='2005', foo='bar')
。
這個技術在Syndication 框架 中使用,來傳遞元數據和選項給視圖。
處理衝突
URL 模式捕獲的命名關鍵字參數和在字典中傳遞的額外參數有可能具備相同的名稱。當這種狀況發生時,將使用字典中的參數而不是URL 中捕獲的參數。
相似地,你能夠傳遞額外的選項給include()。當你傳遞額外的選項給include() 時,被包含的URLconf 的每一 行將被傳遞這些額外的選項。
例如,下面兩個URLconf 設置功能上徹底相同:
設置一次:
# main.py from django.conf.urls import include, url urlpatterns = [ url(r'^blog/', include('inner'), {'blogid': 3}), ] # inner.py from django.conf.urls import url from mysite import views urlpatterns = [ url(r'^archive/$', views.archive), url(r'^about/$', views.about), ]
設置兩次:
# main.py from django.conf.urls import include, url from mysite import views urlpatterns = [ url(r'^blog/', include('inner')), ] # inner.py from django.conf.urls import url urlpatterns = [ url(r'^archive/$', views.archive, {'blogid': 3}), url(r'^about/$', views.about, {'blogid': 3}), ]
注意,額外的選項將永遠傳遞給被包含的URLconf 中的每一行,不管該行的視圖其實是否定爲這些選項是合法的。因爲這個緣由,該技術只有當你肯定被包含的URLconf 中的每一個視圖都接收你傳遞給它們的額外的選項。
在使用Django 項目時,一個常見的需求是得到URL 的最終形式,以用於嵌入到生成的內容中(視圖中和顯示給用戶的URL等)或者用於處理服務器端的導航(重定向等)。
人們強烈但願不要硬編碼這些URL(費力、不可擴展且容易產生錯誤)或者設計一種與URLconf 絕不相關的專門的URL 生成機制,由於這樣容易致使必定程度上產生過時的URL。
換句話講,須要的是一個DRY 機制。除了其它有點,它還容許設計的URL 能夠自動更新而不用遍歷項目的源代碼來搜索並替換過時的URL。
獲取一個URL 最開始想到的信息是處理它視圖的標識(例如名字),查找正確的URL 的其它必要的信息有視圖參數的類型(位置參數、關鍵字參數)和值。
Django 提供一個辦法是讓URL 映射是URL 設計惟一的地方。你填充你的URLconf,而後能夠雙向使用它:
根據用戶/瀏覽器發起的URL 請求,它調用正確的Django 視圖,並從URL 中提取它的參數須要的值。
根據Django 視圖的標識和將要傳遞給它的參數的值,獲取與之關聯的URL。
第一種方式是咱們在前面的章節中一直討論的用法。第二種方式叫作反向解析URL、反向URL 匹配、反向URL 查詢或者簡單的URL 反查。
在須要URL 的地方,對於不一樣層級,Django 提供不一樣的工具用於URL 反查:
在模板中:使用url 模板標籤。
在Python 代碼中:使用django.core.urlresolvers.reverse()
函數。
在更高層的與處理Django 模型實例相關的代碼中:使用get_absolute_url()
方法。
例子
考慮下面的URLconf:
from django.conf.urls import url from . import views urlpatterns = [ #... url(r'^articles/([0-9]{4})/$', views.year_archive, name='news-year-archive'), #... ]
根據這裏的設計,某一年nnnn對應的歸檔的URL是/articles/nnnn/
。
你能夠在模板的代碼中使用下面的方法得到它們:
<a href="{% url 'news-year-archive' 2012 %}">2012 Archive</a> <ul> {% for yearvar in year_list %} <li><a href="{% url 'news-year-archive' yearvar %}">{{ yearvar }} Archive</a></li> {% endfor %} </ul>
在Python 代碼中,這樣使用:
from django.core.urlresolvers import reverse from django.http import HttpResponseRedirect def redirect_to_year(request): # ... year = 2006 # ... return HttpResponseRedirect(reverse('news-year-archive', args=(year,)))
若是出於某種緣由決定按年歸檔文章發佈的URL應該調整一下,那麼你將只須要修改URLconf 中的內容。
在某些場景中,一個視圖是通用的,因此在URL 和視圖之間存在多對一的關係。對於這些狀況,當反查URL 時,只有視圖的名字還不夠。請閱讀下一節來了解Django 爲這個問題提供的解決辦法。
爲了完成上面例子中的URL 反查,你將須要使用命名的URL 模式。URL 的名稱使用的字符串能夠包含任何你喜歡的字符。不僅限制在合法的Python 名稱。
當命名你的URL 模式時,請確保使用的名稱不會與其它應用中名稱衝突。若是你的URL 模式叫作comment
,而另一個應用中也有一個一樣的名稱,當你在模板中使用這個名稱的時候不能保證將插入哪一個URL。
在URL 名稱中加上一個前綴,好比應用的名稱,將減小衝突的可能。咱們建議使用myapp-comment
而不是comment
。
URL 命名空間容許你反查到惟一的命名URL 模式,即便不一樣的應用使用相同的URL 名稱。第三方應用始終使用帶命名空間的URL 是一個很好的實踐(咱們在教程中也是這麼作的)。相似地,它還容許你在一個應用有多個實例部署的狀況下反查URL。換句話講,由於一個應用的多個實例共享相同的命名URL,命名空間將提供一種區分這些命名URL 的方法。
在一個站點上,正確使用URL 命名空間的Django 應用能夠部署屢次。例如,django.contrib.admin
具備一個AdminSite
類,它容許你很容易地部署多個管理站點的實例。在下面的例子中,咱們將討論在兩個不一樣的地方部署教程中的polls 應用,這樣咱們能夠爲兩種不一樣的用戶(做者和發佈者)提供相同的功能。
一個URL 命名空間有兩個部分,它們都是字符串:
應用命名空間
它表示正在部署的應用的名稱。一個應用的每一個實例具備相同的應用命名空間。例如,能夠預見Django 的管理站點的應用命名空間是'admin
'。
實例命名空間
它表示應用的一個特定的實例。實例的命名空間在你的所有項目中應該是惟一的。可是,一個實例的命名空間能夠和應用的命名空間相同。它用於表示一個應用的默認實例。例如,Django 管理站點實例具備一個默認的實例命名空間'admin'。
URL 的命名空間使用':' 操做符指定。例如,管理站點應用的主頁使用'admin:index
'。它表示'admin
' 的一個命名空間和'index
' 的一個命名URL。
命名空間也能夠嵌套。命名URL'sports:polls:index
' 將在命名空間'polls
'中查找'index
',而poll
定義在頂層的命名空間'sports
' 中。
當解析一個帶命名空間的URL(例如'polls:index
')時,Django 將切分名稱爲多個部分,而後按下面的步驟查找:
首先,Django 查找匹配的應用的命名空間(在這個例子中爲'polls
')。這將獲得該應用實例的一個列表。
若是有定義當前 應用,Django 將查找並返回那個實例的URL 解析器。當前 應用能夠經過請求上的一個屬性指定。但願能夠屢次部署的應用應該設置正在處理的request
上的current_app
屬性。
Changed in Django 1.8: 在之前版本的Django 中,你必須在用於渲染模板的每一個`Context` 或 `RequestContext`上設置`current_app` 屬性。
當前應用還能夠經過reverse()
函數的一個參數手工設定。
若是沒有當前應用。Django 將查找一個默認的應用實例。默認的應用實例是實例命名空間 與應用命名空間 一致的那個實例(在這個例子中,polls
的一個叫作'polls
' 的實例)。
若是沒有默認的應用實例,Django 將該應用挑選最後部署的實例,無論實例的名稱是什麼。
若是有嵌套的命名空間,將爲命名空間的每一個部分重複調用這些步驟直至剩下視圖的名稱還未解析。而後該視圖的名稱將被解析到找到的這個命名空間中的一個URL。
爲了演示解析的策略,考慮教程中polls 應用的兩個實例:'author-polls
' 和'publisher-polls
'。假設咱們已經加強了該應用,在建立和顯示投票時考慮了實例命名空間。
#urls.py from django.conf.urls import include, url urlpatterns = [ url(r'^author-polls/', include('polls.urls', namespace='author-polls', app_name='polls')), url(r'^publisher-polls/', include('polls.urls', namespace='publisher-polls', app_name='polls')), ]
#polls/urls.py from django.conf.urls import url from . import views urlpatterns = [ url(r'^$', views.IndexView.as_view(), name='index'), url(r'^(?P<pk>\d+)/$', views.DetailView.as_view(), name='detail'), ... ]
根據以上設置,可使用下面的查詢:
若是其中一個實例是當前實例 —— 若是咱們正在渲染'author-polls
' 實例的detail
頁面 —— 'polls:index
' 將解析成'author-polls
' 實例的主頁面;例以下面兩個都將解析成"/author-polls/
"。
在基於類的視圖的方法中:
reverse('polls:index', current_app=self.request.resolver_match.namespace)
和在模板中:
{% url 'polls:index' %}
注意,在模板中的反查須要添加request
的current_app
屬性,像這樣:
def render_to_response(self, context, **response_kwargs): self.request.current_app = self.request.resolver_match.namespace return super(DetailView, self).render_to_response(context, **response_kwargs)
若是沒有當前實例 —— 若是咱們在站點的其它地方渲染一個頁面 —— 'polls:index
' 將解析到最後註冊的polls
的一個實例。由於沒有默認的實例(命名空間爲'polls'的實例),將使用註冊的polls
的最後一個實例。它將是'publisher-polls
',由於它是在urlpatterns
中最後一個聲明的。
'author-polls:index
' 將永遠解析到 'author-polls
' 實例的主頁('publisher-polls
' 相似)。
若是還有一個默認的實例 —— 例如,一個名爲'polls
' 的實例 —— 上面例子中惟一的變化是當沒有當前實例的狀況(上述第二種狀況)。在這種狀況下 'polls:index
' 將解析到默認實例而不是urlpatterns
中最後聲明的實例的主頁。
被包含的URLconf 的命名空間能夠經過兩種方式指定。
首先,在你構造你的URL 模式時,你能夠提供 應用 和 實例的命名空間給include()
做爲參數。例如:
url(r'^polls/', include('polls.urls', namespace='author-polls', app_name='polls')),
這將包含polls.urls
中定義的URL 到應用命名空間 'polls
'中,其實例命名空間爲'author-polls
'。
其次,你能夠include 一個包含嵌套命名空間數據的對象。若是你include()
一個url()
實例的列表,那麼該對象中包含的URL 將添加到全局命名空間。然而,你還能夠include()
一個3個元素的元組:
(<list of url() instances>, <application namespace>, <instance namespace>)
例如:
from django.conf.urls import include, url from . import views polls_patterns = [ url(r'^$', views.IndexView.as_view(), name='index'), url(r'^(?P<pk>\d+)/$', views.DetailView.as_view(), name='detail'), ] url(r'^polls/', include((polls_patterns, 'polls', 'author-polls'))),
這將include 命名的URL 模式到給定的應用和實例命名空間中。
例如,Django 的管理站點部署的實例叫AdminSite
。AdminSite
對象具備一個urls
屬性:一個3元組,包含管理站點中的全部URL 模式和應用的命名空間'admin
'以及管理站點實例的名稱。你include()
到你項目的urlpattern
s 中的是這個urls
屬性。
請確保傳遞一個元組給include()
。若是你只是傳遞3個參數:include(polls_patterns, 'polls', 'author-polls')
,Django 不會拋出一個錯誤,可是根據include()
的功能,'polls
' 將是實例的命名空間而'author-polls
' 將是應用的命名空間,而不是反過來的。
譯者:Django 文檔協做翻譯小組,原文:URLconfs。
本文以 CC BY-NC-SA 3.0 協議發佈,轉載請保留做者署名和文章出處。
Django 文檔協做翻譯小組人手緊缺,有興趣的朋友能夠加入咱們,徹底公益性質。交流羣:467338606。