在初級篇中,我們接觸了: 1.url 的簡單編寫 2.兩種傳參的方式 3.捕獲的參數總是字元串 4.為視圖設置預設參數 …… 在中級篇中將更進一步。 包含其它的URLconfs 當網站非常大的時候,將所有的url都寫在一個url模塊中會非常的臃腫,且後期不便於維護。此時,就可以使用包含的方式將部分的 ...
在初級篇中,我們接觸了:
1.url 的簡單編寫
2.兩種傳參的方式
3.捕獲的參數總是字元串
4.為視圖設置預設參數
……
在中級篇中將更進一步。
包含其它的URLconfs
當網站非常大的時候,將所有的url都寫在一個url模塊中會非常的臃腫,且後期不便於維護。此時,就可以使用包含的方式將部分的url放在另一個url模塊中。最常見的就是每個app的url都進行分離。
官方代碼示例:
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_viewsfrom 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 urlfrom . 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, urlfrom . 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), ])), ]
下麵,我們總結一下使用 include 能達到什麼效果:
1.分離 url ,例如將 app 相關的 url 都移到對應的 app 目錄下,這樣邏輯更清晰,也更易維護。
2.去除 url 中的冗餘。
當然,還有更高級的用法,例如:命名空間,反向解析等。這些等到高級篇再進行討論。
包含之後的參數傳遞
包含的 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" 變數將被如期傳遞給 include() 指向的 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/)的一部分,而不只是視圖所要的值(通常我只需要知道一個頁碼,也就是這裡的 2 就行了)。這種耦合在反查時更加顯著,因為反查視圖時我們需要傳遞 URL 的一個片段而不只是 page 的值。
總結:儘量不要捕獲不需要的參數,因為這樣不僅在函數中需要額外的處理,而且在進行各種反向查詢的時候也會困難些。
傳遞額外的選項給視圖函數
URLconfs 具有一個鉤子,讓你傳遞一個Python 字典作為額外的參數傳遞給視圖函數。
django.conf.urls.url() 函數可以接收一個可選的第三個參數,它是一個字典,表示想要傳遞給視圖函數的額外關鍵字參數。
例如:
from django.conf.urls import urlfrom . 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')。
但是這樣會出現一定的衝突,當 URL 模式捕獲的命名關鍵字參數和在字典中傳遞的額外參數具有相同的名稱時,將使用字典中的參數而不是URL 中捕獲的參數。(也就是說字典中的參數優先順序更高。)
傳遞額外的選項給include
類似地,你可以傳遞額外的選項給include()。當你傳遞額外的選項給include() 時,被包含的URLconf 的每一行將被傳遞這些額外的選項。
也就是說在 include 中傳遞的額外參數將傳給所有被包含的 url。
設置一: # main.py from django.conf.urls import include, url urlpatterns = [ url(r'^blog/', include('inner'), {'blogid': 3}),
] # inner.pyfrom django.conf.urls import urlfrom mysite import views urlpatterns = [ url(r'^archive/$', views.archive), url(r'^about/$', views.about),
] 設置二: # main.py from django.conf.urls import include, urlfrom mysite import views urlpatterns = [ url(r'^blog/', include('inner')),
] # inner.pyfrom django.conf.urls import url urlpatterns = [ url(r'^archive/$', views.archive, {'blogid': 3}), url(r'^about/$', views.about, {'blogid': 3})
,]
但是,如果你不確定你的所有的相關視圖函數都需要這個額外的參數的話,將會導致參數傳遞錯誤,例如多傳了參數。而發生這種情況時,毫無疑問會發生報錯。所以在使用這個功能的時候要足夠的謹慎。你要知道你做了什麼。
URL 的反向解析
反向解析是做什麼的?反向解析,就是在模板中,或 views 函數中進行 url 的獲取或者重定向到指定頁面的時候,可以更加靈活的寫入目標 url 而不用硬編碼。
例如,在沒有使用反向解析之前:
<a href="/xxx/xxx/">測試</a> #模板中 def xxx(request): #視圖函數需要重定向時 ..... return HttpResponseRedirect('/xxx/xxx/')
如果此時處於業務需要,想要改動 url 時,那麼代碼中所以用到的地方就都需要改。如果此時用的這個 url 的地方有 N 個,就意味著要改動 N 次。此時,你可能想死的心都有了。為了延長程式員的壽命,反向解析就顯得非常重要了。
Django 通過為指定的url命名的方式,來代替硬編碼,而為瞭解決命名重覆的問題,又引入了命名空間,下麵逐一介紹。
例子:
from django.conf.urls import url from . import views urlpatterns = [ #... url(r'^articles/([0-9]{4})/$', views.year_archive, name='news-year-archive'), #...]
在這裡,添加了 name 這個屬性,並將其賦值為'news-year-archive'。這就是為這個 url 進行了命名。
在進行命名了以後,就可以這樣寫:
<a href="{% url ‘new-year-archive’}>測試</a> #模板中 from django.core.urlresolvers import reverse def xxx(request): #視圖函數 ...... return HttpResponseRedirect(reverse('news-year-archive',))
這裡的reverse()函數的作用是進行反向解析,以直接訪問其他視圖函數。
下麵來分析一下這個函數要怎麼用:
reverse(viewname, urlconf=None, args=None, kwargs=None, current_app=None)
這裡接收 5 個參數,下麵來看看這些參數都是做什麼的:
viewname:是一個字元串,可以是一個Python路徑視圖對象(廢棄),一個URL模式名稱( reverse('news_archive') ),或可調用視圖對象( from news import views /reverse(views.archive) )。
urlconf : reverse 在其內部是這樣處理的: if urlconf is None: urlconf = get_urlconf() ,那我們去看看get_urlconf()是做什麼的。這時,我們看到這樣的一句說明:
def get_urlconf(default=None): """ Returns the root URLconf to use for the current thread if it has been changed from the default one. """ return getattr(_urlconfs, "value", default)
返回根 url 模塊的。
也就是說這個屬性決定此次反向解析使用哪個 url 模塊。預設是當前的根 url 模塊
args : 用於傳參,也就是說方向解析到指定的 view 函數後,並傳遞了參數,可以是元組或列表,表示按照順序進行位置傳參。
kwargs:也是用於傳參的,不同的是,這裡是一個字典,傳參時使用關鍵字傳參的方式。
current_app :參數允許您提供一個提示解析器指示應用程式當前執行的視圖所屬(所在app)。這個current_app參數作為一個提示,根據名稱空間URL解決策略,來解決應用程式名稱空間URL在特定的應用程式實例。
註意:
若沒有找到匹配的對象,則拋出 NoReverseMatch 異常。
一般而言幾乎所有的正則都能逆向解析,但是目前並不能匹配含有選擇符(|)的正則。除此之外幾乎可以很輕鬆得逆向解析,但這種逆向解析是不可逆的。
反向解析Python路徑的能力,如反向(“news.views.archive”),已被棄用。(在1.8中)
官方手冊例子:
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>
傳遞多個參數時用空格隔開:
{% url 'add' 123 321 %} {% url 'add' num1=123 num2=321 %}
在Python 代碼中,這樣使用:
rom 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 中的內容。