對照這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
之類的網址。
運行測試網站http://127.0.0.1:8000/polls/
時卻發生錯誤:
NoReverseMatch at /polls/
Reverse for 'detail' with arguments '(2,)' and keyword arguments '{}' not found. 1 pattern(s) tried:[u'$(?P<pk>[0-9]+)/$']
百思不得其解,為什么url解析錯誤。
當時的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>[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:
{% 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的功能。
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:
('^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