對照這django官方教程(1.8)寫第一個APP,在第3部分(Removing hardcoded URLs in templates),將index.html的連接<a href="/polls/{{ question.id }}/">
更改成<a href="{% url 'polls:detail' question.id %}">
,指望輸出polls/1
之類的網址。html
運行測試網站http://127.0.0.1:8000/polls/
時卻發生錯誤:python
NoReverseMatch at /polls/
Reverse for 'detail' with arguments '(2,)' and keyword arguments '{}' not found. 1 pattern(s) tried:[u'$(?P<pk>[0-9]+)/$']
正則表達式
百思不得其解,爲何url解析錯誤。django
當時的polls/urls.py:app
from django.conf.urls import url from . import views urlpatterns = [ url(r'^$', views.IndexView.as_view(), name='index'), url(r'^(?P<pk>[0-9]+)/$', views.DetailView.as_view(), name='detail'), url(r'^(?P<pk>[0-9]+)/results/$', views.ResultsView.as_view(), name='results'), url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'), ]
polls/template/polls/index.html:dom
{% if latest_question_list %} <ul> {% for question in latest_question_list %} <li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li> {% endfor %} </ul> {% else %} <p>No polls are available.</p> {% endif %}
查看url tag的文檔,裏面是這麼描述url tag的功能。ide
url
Returns an absolute path reference (a URL without the domain name) matching a given view and optional parameters.測試For example, suppose you have a view, app_views.client, whose URLconf takes a client ID (here, client() is a method inside the views file app_views.py). The URLconf line might look like this:網站
('^client/([0-9]+)/$', app_views.client, name='app-views-client')
If this app’s URLconf is included into the project’s URLconf under a path such as this:this
('^clients/', include('project_name.app_name.urls'))
...then, in a template, you can create a link to this view like this:
{% url 'app-views-client' client.id %}
The template tag will output the string /clients/client/123/.
Note that if the URL you’re reversing doesn’t exist, you’ll get an NoReverseMatch exception raised, which will cause your site to display an error page.
說的是不存在想要反析的URL就產生NoReverseMatch異常,反析的過程應該就是讓網址括號裏的正則表達式裏匹配傳過去的參數(參考Reverse resolution of URLs)。
在以前的index.html,question.id做爲參數傳過去,結果不匹配。竟懷疑question.id不爲數字,直接將參數改成10,結果仍然是產生了NoReverseMatch異常:
NoReverseMatch at /polls/
Reverse for 'detail' with arguments '(10,)' and keyword arguments '{}' not found. 1 pattern(s) tried:[u'$(?P<pk>[0-9]+)/$']
但注意到參數發生了變化,確實變成了10, arguments '(**10**,)'
。既然傳輸的參數是正確的,那問題仍是出在正則表達式的網址上。
仔細查看錯誤提示,試驗了1個正則表達式(1 pattern(s) tried: [u'$(?P<pk>[0-9]+)/$']
),這個試驗的正則表達式有點怪異,前面竟然有一個匹配結尾元字符$
,而polls/urls.py中name='detail'對應的網址正則表達式爲r'^(?P<pk>[0-9]+)/$'
。
逐想到上層mysite/urls.py中的配置,查看mysite/urls.py:
from django.conf.urls import include, url from django.contrib import admin urlpatterns = [ url(r'^$', include('polls.urls', namespace="polls")), url(r'^polls/', include('polls.urls', namespace="polls")), url(r'^admin/', include(admin.site.urls)), ]
竟然定義了兩個namespace="polls",當執行{% url 'polls:detail' question.id %}
時,django先找到mysite/polls第一個namespace=polls的url regex,而後加上polls/urls.py對應name=detail的url regex,獲得最後的url regex: $(?P<pk>[0-9]+)/$
,這個正則式要求第一個位置匹配結束,這估計怎麼都不會反析成功。而正確的url regex應該是第二個namespace=polls的url regex加上polls/urls.py對應name=detail的url regex,既polls/(?P<pk>[0-9]+)/$
。
發現問題所在後,將第一個namespace=polls所在行刪掉,再次打開http://127.0.0.1:8000/polls/
,正確的網頁顯示出來了,並且每個問題對應的網址就是http://127.0.0.1:8000/polls/[數字]
。
django先應用找到的第一個namespace,因此將第一個namespace和第二個互換位置,結果也是能夠正常顯示。
慘痛的教訓告誡咱們,不要在網站的urls.py使用同一個名字的namespace。另外,網站的urls.py若使用了include,不要在正則式後再加$
。參考官方文檔:Including other URLconfs