在实际的Django项目中,经常需要获取某个具体对象的URL,为生成的内容配置URL链接。
比如,我要在页面上展示一列文章列表,每个条目都是个超级链接,点击就进入该文章的详细页面。
假设现在我们的urlconf是这么配置的:
path('post/<int:pk>/',views.some_view),
在前端中,这就需要为HTML的<a>
标签的href属性提供一个诸如http://www.xxx.com/post/3/
的超级链接。其中的域名部分,Django会自动添加,无须关心,需要关注的是post/3/
。
此时,一定不能硬编码URL为post/3/
,那样费时费力、修改困难,而且容易出错。试想,如果哪天,因为某种原因,需要将urlconf中的表达式改成entry/<int:pk>/
,为了让链接正常工作,必须修改对应的herf属性值,于是你去项目里将所有的post/.../
都改成entry/.../
吗?显然这是不现实的!
我们需要一种安全、可靠、自适应的机制,当修改URLconf中的代码后,无需在项目源码中大范围搜索、替换失效的硬编码URL。
为了解决这个问题,Django提供了一种解决方案,只需在URL中提供一个name参数,并赋值一个你自定义的、好记的、直观的字符串。
通过这个name参数,可以反向解析URL、反向URL匹配、反向URL查询或者简单的URL反查。
在需要解析URL的地方,对于不同层级,Django提供了不同的工具用于URL反查:
url
模板标签。(也就是写前端网页时)reverse()
函数。(也就是写视图函数等情况时)get_absolute_url()
方法。(也就是在模型model或前端代码中,参考前面的章节)所有上面三种方式,都依赖于首先在path中为对应url添加name属性!
范例:
考虑下面的URLconf:
from django.urls import path from . import views urlpatterns = [ # ... path("articles/<int:year>/", views.year_archive, name="news-year-archive"), # ... ]
2020年对应的归档URL是/articles/2020/
。
可以在模板的代码中使用下面的方法获得它们(可以结合模板语法章节来理解下面的内容):
<a href="{% url 'news-year-archive' 2020 %}">2020 Archive</a> {# 或者使用for循环变量 #} <ul> {% for yearvar in year_list %} <li><a href="{% url 'news-year-archive' yearvar %}">{{ yearvar }} Archive</a></li> {% endfor %} </ul>
在Python代码中,这样使用:
from django.http import HttpResponseRedirect from django.urls import reverse def redirect_to_year(request): # ... year = 2020 # ... return HttpResponseRedirect(reverse('news-year-archive', args=(year,)))
其中,起到核心作用的是我们通过name='news-year-archive'
为那条path起了一个可以被全局引用的专用名称。
URL名称name使用的字符串可以包含任何你喜欢的字符,但是过度的放纵有可能带来重名的冲突,比如两个不同的app,在各自的urlconf中为某一条path取了相同的name,这就会带来麻烦。为了解决这个问题,又引出了下面的命名空间。
思考这么一个问题,假设下面的情况:
'index'
'index'
这种情况完全是有可能的,甚至还很常见!
请问,你在某个视图中使用reverse('index',args=(...))
或者在模板中使用{% url 'index' ... %}
,它最终生成的URL到底是路由A还是路由B呢?
不管是哪个,肯定都不好,因为我们需要状态确定的路由,而不是混乱的。
之所以造成这种情况,根本上还是各个app之间没有统一的路由管理,实际上也不可能有。
最佳解决问题的办法就是隔离分治,通过命名空间进行解耦,这也是常用的设计模式。
Django提供了一个叫做app_name
的属性,帮我们实现实例级别的命名空间,这样,虽然大家都叫‘大伟’,但一个是‘张大伟’,一个是‘王大伟’,自然就分清楚了甲乙丙丁。
from django.urls import path from . import views app_name = 'your_app_name' # 重点是这行! urlpatterns = [ ... ]
我么只需要在app自己本身的urls.py文件内,添加一行app_name = 'your_app_name'
即可。
注意不是在根路由文件中。一般就和自己的app同命即可,因为项目里面不会有2个同样名字的app。
接下来,具体的应用方式则很简单:
# 视图中 reverse('your_app_name:index',args=(...)) # 模板中 {% url 'your_app_name:index' ... %}
注意your_app_name
和index
之间是通过冒号连接。
首先我们要知道,Django是可以对app进行实例化的。也就是说:
app_name:name
不够用了,需要在上面加上新的一层命名空间,也就是应用级别的。
以上不好理解,甚至很多人都不知道这个知识点。
假设我们有个app,用于实现一个index页面展示功能:
为此,我们可以这么实现:
author/...
,publisher访问publisher/...
。include('app.urls')
而这,就是所谓的应用的多个实例!
但这种做法有个明显的问题,就是对于每条URL,如何区分两种人员?
使用应用命名?像reverse('your_app_name:index',args=(...))
这样?
显然是不行的,因为多个应用的实例共享实例的空间名,也就是我们前面通过app_name
属性定义的名字!
针对这种情况,Django提供了一个namespace
属性,称之为应用的命名空间,用于标记不同的应用实例。它的层级更高,项目内必须唯一。如下所示:
根urls.py
from django.urls import include, path urlpatterns = [ path('author/', include('app.urls', namespace='author_app')), path('publisher/', include('app.urls', namespace='publisher_app')), ]
app/urls.py
from django.urls import path from . import views app_name = 'app' # 这行不能少 urlpatterns = [ path('index/', views.index, name='index'), path('detail/', views.detail, name='detail'), ]
app/views.py
from django.shortcuts import render,HttpResponse def index(request): return HttpResponse('当前的命名空间是%s'% request.resolver_match.namespace) def detail(request): if request.resolver_match.namespace == 'author_app': return HttpResponse('这里是作者的页面') elif request.resolver_match.namespace == 'publisher_app': return HttpResponse('这里是出版商的页面') else: return HttpResponse('去liujiangblog.com学习Django吧')
启动服务器,分别访问author/index/
和publisher/index
,你应该能看到不同的结果。
要注意:
spacename_appname
。app_name
,如果不设置,会弹出异常namespace
属性的值,通过request.resolver_match.namespace
这样,我们实现了URL的正向解析。那么反向解析怎么做呢?
为根urls.py
添加一条路由:
from app import views path('goto/', views.goto)
为app/views.py
添加一个视图:
def goto(request): return HttpResponseRedirect(reverse('app:index', current_app='author_app' ))
注意reverse
方法提供的current_app
参数!
然后我们访问goto/
,可以看到页面跳转到了author的index。
是直接指定字符串形式的namespace名称,还是使用request.resolver_match.namespace
,取决于你当前视图是从哪个URL路由过来的,该URL是否携带了namespace属性。
在基于类的视图的方法中,我们也可以这样使用:
reverse('app:index', current_app=self.request.resolver_match.namespace)
再拓展一下,实际上我们在reverse的时候完全可以直接指定使用哪个namespace命名空间:
reverse('author_app:index') # 或者 reverse('publisher_app:index')
那么在HTML模板中怎么使用呢?
以前我们是这样写的:
{% url 'app:index' %}
这会有两种情况:
所以为了彻底区分每个实例,需要这么用:
{% url 'author_app:index' %} # 或者 {% url 'publisher_app:index' %}
其实,从这里也能看出,Django为什么要求namespace全局唯一。
注意:不支持reverse('publisher_app:app:index')
这样的写法。
reverse那里就没看明白了,多找点案例实操吧!
我有一个疑问? 应用命名空间在用的时候是这样表示:app:index 实例命名空间在用的时候怎么也是这样表示:author:index、publisher:index 有没有吊大解释下?
app_name跟namespace的例子还可以改改,没有突出这两个的差别。
这章给我看蒙了
确实,这节教程漏洞较多,比如namespace如何使用,没有说清除,没有较好的例子说明。不过瑕不掩瑜吧。
这章翻译的遗漏好多,revserse()函数整个都没,勉强能看懂吧
一脸懵逼,感觉很乱,没说清楚。其实建议博主可以在文章中间加一些图解,帮助理解,django感觉很多东西,容易混淆,有图的话就清晰很多。
说实在的,Django2.x中,namespace真没啥用,有用的是app_name。
适合有django开发基础的,查漏补缺
我也是,看的好懵,看细节真的好难懂啊
Django有一点基础,前面内容都没问题,这个真心没理解
这一节我也看得蒙蒙的
。
同上