对照这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