转载请注明来源地址和原做者(CFishHome)前端
上一篇文章咱们学习了URL与视图函数的映射、传递参数的三种方式、转换器的简单使用和include函数分层映射管理。接下来这一篇文章着重介绍Path、re_path、include、reverse、redirect函数的使用和自定义URL转换器。学完这些内容,相信咱们对URL和视图都会有了必定的了解和认识。为了让每篇文章具备必定的独立性,我决定每篇文章都从新新建一个项目,便于测试和调试。python
首先,咱们在Pycharm从新新建一个名为book_project的Django项目,而后打开CMD窗口进入虚拟环境,接着跳转到该项目的目录下(含有manage.py),再依次执行建立app命令,以下图:
建立完成后,该项目的样子大概是酱紫的:git
咱们想实现:front前端拥有前端首页和前端登录页面,而cms后端拥有后端首页和后端登录页面,而后分别输入配置好的URL对这四个页面进行访问。
首先,为cms和front两个app手动添加urls.py文件。而后分别按如下步骤添加或修改项目文件代码:
(1)为cms的urls.py文件添加如下代码:正则表达式
from django.urls import path from . import views urlpatterns = [ path("", views.index), path("login/", views.login) ]
(2)为front的urls.py文件添加如下代码:django
from django.urls import path from . import views urlpatterns = [ path("", views.index), path("login/", views.login) ]
(3)为cms的views.py文件添加如下代码:后端
from django.http import HttpResponse def index(request): return HttpResponse("后端首页") def login(request): return HttpResponse("后端登录页面")
(4)为front的views.py文件添加如下代码:app
from django.http import HttpResponse def index(request): return HttpResponse("前端首页") def login(request): return HttpResponse("前端登录页面")
(5)为book_project这个主包的urls.py文件添加如下代码:ide
from django.urls import path,include urlpatterns = [ path('', include("front.urls")), path('cms/', include("cms.urls")) ]
若是学习了上一篇文章的内容,我相信这些代码的编写是彻底OK的。好,咱们运行一下项目(注意,咱们是新建的项目,记得在勾选单一实例运行。)。
运行结果以下(完美,成功运行~):函数
实际需求:要想实现一个相似于知乎主页同样,如果新用户第一次登录知乎,无论哪一个页面都会跳转到登录帐号的页面,对于这一需求,咱们能够经过如下模拟实现(为了更好观察代码,同样的代码部分我直接提示省略):
(1)修改front前台的index视图函数学习
#省略上面代码 from django.shortcuts import redirect # 导入重定向redirect函数,最好记住redirect函数源于哪一个模块,由于要常常重定向。 def index(request): # ?username=xxx username = request.GET.get('username') # 当用户输入127.0.0.1:8000,检测到没有帐户则跳转到注册页面,当输入http://127.0.0.1:8000/?username=1正常显示前台首页。 if username: return HttpResponse('前台首页') else: return redirect('/login/') # 跳转经过redirect函数来进行页面重定向,从新访问指定的url页面 #省略下面代码
修改完代码后,按下Ctrl+S保存(自动从新编译项目)。注意观察,输入127.0.0.1:8000会发现自动跳转到127.0.0.1:8000/login/。
其实还存在这样一种状况,假如老板或项目经理忽然脑壳秀逗了,要求你更改网页的URL,将127.0.0.1:8000/login/更改成127.0.0.1:8000/signin/,假如这时还采用上面的代码进行网页重定向,那你会很是抓狂,举咱们这个项目栗子来讲,不单只要修改成下面这样:
urlpatterns = [ path("", views.index), path("signin/", views.login) ]
还要修改重定向位置的代码这样:
def index(request): # ?username=xxx username = request.GET.get('username') # 当用户输入127.0.0.1:8000,检测到没有帐户则跳转到注册页面,不然正常显示前台首页。 if username: return HttpResponse('前台首页') else: return redirect('/singin/') # 跳转经过redirect函数来进行页面重定向,从新访问指定的url页面
若是公司网站项目十分庞大的话,那可能还有不少地方都要修改,这十分耗时也费力。因此,咱们推荐为URL命名来解决这一问题,给URL取个名字,只要调用reverse反转URL的名字而不是直接重定向写死的URL,那么不管URL怎么修改也影响不到其余地方。
在学习URL命名以前,先详细学习下Path函数的使用。
path 函数的定义为:
path(route,view,name=None,kwargs=None) 。
如下对这几个参数进行讲解。
from django.urls import path from . import views urlpatterns = [ path('blog/<int:year>/', views.year_archive, kwargs={'foo':'bar'}), ]
那么之后在访问 blog/1991/ 这个url的时候,会将{'foo':'bar'}做为关键字参数传给 year_archive函数。year_archive视图函数的形参中最后添加一个kwargs参数来接收这个额外的参数。
学习完path函数各参数,相信都知道该函数的name参数就是用于URL命名的了。
接下来修改front包的urls.py文件代码以下:
urlpatterns = [ path("", views.index), path("login/", views.login, name='login') ]
再次修改front包的views.py文件代码以下:
#省略上面代码 from django.shortcuts import redirect, reverse # 注意这里添加了reverse函数,reverse函数用于将指定URL名字反转成URL。 def index(request): # ?username=xxx username = request.GET.get('username') # 当用户输入127.0.0.1:8000,检测到没有帐户则跳转到注册页面,不然正常显示前台首页。 if username: return HttpResponse('前台首页') else: # reverse('login')函数返回‘/login/’ return redirect(reverse('login')) # 跳转经过redirect函数来进行页面重定向,从新访问指定的url页面(至关于从新访问127.0.0.1:8000/login/) #省略下面代码
按下Ctrl+S保存,输入127.0.0.1:8000成功跳转到127.0.0.1:8000/login登录页面。
在公司实际开发中,若是公司里的多我的同时负责网站的开发,并且A同事负责开发front的app,B同事负责开发cms的app,那么因为两个app都有首页和登录页面,那么有可能url的name属性可能会相同冲突。在多个app之间,有可能产生同名的url,这时候为了不反转url的时候产生混淆,可使用应用命名空间来作区分。定义应用命名空间很是简单,只要在“app”的“urls.py”中定义一个叫作“app_name”的变量,来指定这个应用的命名空间便可。
就比如咱们的项目,将front包里的urls.py和views.py文件和cms包里的urs.py和views.py文件分别为URL映射命名为index和login,以下图所示:
运行项目,输入127.0.0.1:8000自动跳转到127.0.0.1:8000/cms/login网页,而不是以前的127.0.0.1:8000/login网页,因为多个app的URL拥有相同的名字,因此Django在执行reverse函数反转URL时懵逼了。为了解决这个问题,咱们将采用应用命名空间。
修改front包的urls.py文件的代码以下(django在执行到app时,会自动读取这个应用命名空间并将这个名字做为app的名字):
from django.urls import path from . import views app_name = "front" # 添加了应用命名空间 urlpatterns = [ path("", views.index, name="index"), path("login/", views.login, name='login') ]
之后在作反转的时候就可使用“应用命名空间:url名称”的方式进行反转。示例代码以下修改front包的views.py文件的代码以下:
def index(request): # ?username=xxx username = request.GET.get('username') # 当用户输入127.0.0.1:8000,检测到没有帐户则跳转到注册页面,不然正常显示前台首页。 if username: return HttpResponse('前台首页') else: return redirect(reverse('front:login')) # 这里为URL名字前面添加front应用命名空间名
按下Ctrl+S保存,输入127.0.0.1:8000自动跳转到127.0.0.1:8000/login网页,成功了,Django不会再懵逼了。
在上一篇文章咱们知道,在项目不断庞大之后,常常不会把全部的 url 匹配规则都放在项目的 urls.py 文件中,而是每一个 app 都有本身的 urls.py 文件,在这个文件中存储的都是当前这个 app 的全部url 匹配规则。而后再统一include到项目的 urls.py 文件中。 include 函数有多种用法,这里讲下几种经常使用的用法:
(1)include(pattern,namespace=None) :直接把其余 app 的 urls 包含进来。
以前的include用法,举个栗子(下面的代码仅用于解释用法,不是将代码添加到项目):
from django.contrib import admin from django.urls import path,include urlpatterns = [ path('admin/', admin.site.urls), path('book/',include("book.urls")) ]
咱们能够发现这一种用法其实该函数还有参数2指定实例命名空间,默认是None。可是在使用实例命名空间以前,必须先指定一个应用命名空间,不先指定应用命名空间会报错。一个app能够建立多个实例,也就是可使用多个url映射同一个app,因此这就产生一个问题。之后在作反转的时候,若是使用应用命名空间,那么就会发生混淆。
将book_project主包的urls.py文件代码修改以下:
from django.urls import path,include urlpatterns = [ path('', include("front.urls")), path('cms1/', include("cms.urls")), # 这两行代码表明输入127.0.0.1:8000/cms1或127.0.0.1:8000/cms2能映射到cms.urls内部的url路径。 path('cms2/', include("cms.urls")) ]
为cms包的urls.py文件添加应用命名空间:
app_name = "cms"
为cms包的views.py文件修改代码以下:
def index(request): # ?username=xxx username = request.GET.get('username') # 当用户输入127.0.0.1:8000,检测到没有帐户则跳转到注册页面,不然正常显示前台首页。 if username: return HttpResponse('后台首页') else: return redirect(reverse('cms:login')) # 这里使用了应用命名空间反转URL
通过上面代码的修改,这时候按下Ctrl+S保存,输入127.0.0.1:8000/cms1成功跳转到127.0.0.1:8000/cms1/login,可是当输入127.0.0.1:8000/cms2却自动跳转到了127.0.0.1:8000/cms1/login,what?个人127.0.0.1:8000/cms2/login哪里去了?致使上面的缘由就是前面介绍的一个app能够建立多个实例,也就是可使用多个url映射同一个app,因此这就产生一个问题。之后在作反转的时候,若是使用应用命名空间,那么就会发生混淆。为了不这个问题,咱们可使用实例命名空间。实例命名空间也是很是简单,只要在“include”函数中传递一个“namespace”变量便可。
在上面代码的基础上继续修改代码,修改cms包的views.py文件代码以下:
def index(request): username = request.GET.get('username') if username: return HttpResponse("后端首页") else: current_namespace = request.resolver_match.namespace # 返回当前app对应的实例命名空间(cms1或cms2) return redirect(reverse('%s:login' % current_namespace)) # 至关于"cms1:login"或”cms2:login“
接着修改book_project主包的urls.py文件代码以下:
from django.urls import path,include urlpatterns = [ path('', include("front.urls")), path('cms1/', include("cms.urls", namespace="cms1")), # 使用了实例命名空间namespace path('cms2/', include("cms.urls", namespace="cms2")) ]
这时候按下Ctrl+S保存,输入127.0.0.1:8000/cms1成功跳转到127.0.0.1:8000/cms1/login,而后输入127.0.0.1:8000/cms2也成功跳转到了127.0.0.1:8000/cms2/login页面。以下图:
(2)include((pattern_list,app_namespace),namespace=None) :“include”函数的第一个参数既能够做为一个字符串,也能够做为一个元组,若是是元组,那么元组的第一个参数是子“urls.py”模块的字符串,元组的第二个参数是应用命名空间,也就是说应用命名空间既能够在子“urls.py”种经过"app_name"指定,也能够在“include”函数中指定。
将book_project主包的urls.py文件代码修改以下:
from django.urls import path, include from front import views urlpatterns = [ path('', include(([ path('', views.index, name="index"), path("login/", views.login, name='login') ], "front"))), # 注意 path('cms1/', include("cms.urls", namespace="cms1")), path('cms2/', include("cms.urls", namespace="cms2")) ]
上面的代码至关于彻底忽略了front包的urls.py文件的做用,由于已经被include函数的第一个参数列表给替代了,因此urls.py文件的“app_name = "front"”指定的应用命名空间天然也失效了。这时运行代码彻底OK,跟以前的一摸同样,输入127.0.0.1:8000自动跳转到127.0.0.1:8000/login页面。
(3)include(pattern_list) :能够包含一个列表或者一个元组,这个元组或者列表中又包含的是 path 或者是 re_path 函数(该函数后面会讲)。
这个函数跟上一个函数差很少,也是用含有path函数的列表或元组替代了以前的pattern。可是须要注意的是,由于这样会忽略了front包的urls.py文件的做用,因此urls.py文件的“app_name = "front"”指定的应用命名空间天然也失效了。那么若是你映射的视图函数内部进行反转URL时指定了应用命名空间,那么将会报错,会提示找不到front命名空间,以下:
因此,综上建议,若是你映射的视图函数内部进行反转URL时指定了应用命名空间,最好调用include((pattern_list,app_namespace),namespace=None) 函数,在指定列表或元组 的同时也指定应用命名空间。
以前咱们都是经过url来访问视图函数。有时候咱们定义了URL名字,而后经过调用reverse函数反转回它的url。就好像前面的代码调用那样:
reverse("login") > /login/
若是有应用命名空间或者有实例命名空间,那么应该在反转的时候加上命名空间。就好像前面的代码调用那样:
reverse('front:login') > /login/ # 注意,反转的是前端app的url
接着咱们就要考虑另一个问题,就是若是URL映射时须要传递参数,这时reverse函数该怎么调用才能传递参数呢?咱们都知道传递参数有其中两种方式:1.尖括号方式传递参数 2.查询字符串传递参数。
下面分别讲解这两种传递参数方式在reverse函数中的使用:
(1)URL尖括号传递参数
若是这个url中须要传递参数,那么能够经过 kwargs 来传递参数。
首先,咱们修改front包的views.py文件的代码以下:
def index(request): # ?username=xxx username = request.GET.get('username') # 当用户输入127.0.0.1:8000,检测到没有帐户则跳转到注册页面,不然正常显示前台首页。 if username: return HttpResponse('前台首页') else: return redirect(reverse('front:login', kwargs={'user_id': 1})) # 跳转经过redirect函数来进行页面重定向,从新访问指定的url页面 def login(request, user_id): text = "当前用户登录的ID是:%s" % user_id return HttpResponse(text)
接着,因为上节测试include函数,将front的url映射全放在book_project主包的urls.py文件里,因此修改为这样:
urlpatterns = [ path('', include(([ path('', views.index, name="index"), path("login/<user_id>/", views.login, name='login') ], "front"))), path('cms1/', include("cms.urls", namespace="cms1")), path('cms2/', include("cms.urls", namespace="cms2")) ]
按下Ctrl+S保存,运行结果以下(成功访问,说明reverse传递尖括号参数是有效的):
(2)查询字符串传递参数
由于 django 中的 reverse 反转 url 的时候不区分 GET 请求和 POST 请求,所以不能在反转的时候添加查询字符串的参数。若是想要添加查询字符串的参数,只能手动的添加。
首先,咱们修改front包的views.py文件的代码以下(注意代码的细微调动):
def index(request): # ?username=xxx username = request.GET.get('username') # 当用户输入127.0.0.1:8000,检测到没有帐户则跳转到注册页面,不然正常显示前台首页。 if username: return HttpResponse('前台首页') else: return redirect(reverse('front:login') + "?next=/") # 跳转经过redirect函数来进行页面重定向,从新访问指定的url页面 def login(request): n = request.GET.get("next") text = "你输入的next值为:%s" % n return HttpResponse(text)
接着,咱们修改book_project主包的urls.py文件的代码以下(注意代码的细微调动):
urlpatterns = [ path('', include(([ path('', views.index, name="index"), path("login", views.login, name='login') ], "front"))), path('cms1/', include("cms.urls", namespace="cms1")), path('cms2/', include("cms.urls", namespace="cms2")) ]
按下Ctrl+S保存,运行结果以下(成功访问,说明reverse传递查询字符串参数是有效的):
有时候咱们在写url匹配的时候,想要写使用正则表达式来实现一些复杂的需求,那么这时候咱们可使用 re_path 来实现。 re_path 的参数和 path 参数如出一辙,只不过第一个参数也就
是 route 参数能够为一个正则表达式。
一些使用 re_path 的示例代码以下:
from django.urls import path, re_path from . import views urlpatterns = [ path('articles/2003/', views.special_case_2003), re_path(r'articles/(?P<year>[0-9]{4})/', views.year_archive), re_path(r'articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/', views.month_archiv e), re_path(r'articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<slug>[\w-_]+)/', views.article_detail), ]
以上例子中咱们能够看到,全部的 route 字符串前面都加了一个 r ,表示这个字符串是一个原生字符串。在写正则表达式中是推荐使用原生字符串的,这样能够避免在 python 这一层面进行转义。
用法:(?P<参数变量>匹配参数量的正则表达式),这个用法显而易见,这个参数的名字是经过尖括号 <year> 进行包裹,以后才是写正则表达式的语法,说明若是在传递这个参数变量的时候,这个参数变量必须知足后面所写的正则表达式,不然就会报错。
在学习前面的转换器和reverse函数反转时,确定会产生疑惑:为何转换器能实现转换功能?还有reverse函数是怎么反转成一个URL的呢?
好的,这些转换和反转过程允许我一一道来。
咱们都看过converters模块的实现,以下图:
能够看出这个Int转换器类除了定义了正则表达式,还定义了两个函数to_Python和to_url。
转换器和reverse相互转化过程以下:
当给path函数的第一个参数使用尖括号参数,若是使用转换器,那么会将这个参数传递给to_python函数进行int转换,而后将转换后的int值传递给视图函数的同名形参接收,因此视图函数的形参变成int类型的了。与此同时,若是视图函数内调用reverse函数进行反转的话,那么reverse函数的kwargs参数的int类型值(即视图函数的形参)传递给to_url转换为str类型,而后将转换后的str值传递path函数的第一个参数,组合成真正的字符串URL,最后reverse函数再返回这个字符串URL。
以前已经学到过一些django内置的 url 转换器,包括有 int 、 uuid 等。有时候这些内置的 url转换器并不能知足咱们的需求,所以django给咱们提供了一个接口可让咱们本身定义本身的url转换器,即本身定义一个转换器类来实现转换。自定义 url 转换器按照如下五个步骤来走就能够了:
(1)修改my_converters主包的urls.py文件代码以下:
from django.urls import path from my_app import views from django.urls import register_converter #1. 定义一个类 class FourDigitYearConverter: #2. 定义一个正则表达式 regex = '[0-9]{4}' #3. 定义to_python方法 def to_python(self, value): return int(value) #4. 定义to_url方法 def to_url(self, value): return '%04d' % value #5. 注册到django中 register_converter(FourDigitYearConverter, 'four_year') urlpatterns = [ path('index/<four_year:year>/', views.index) ]
(2)修改my_app包的views.py文件代码以下:
from django.http import HttpResponse def index(request, year): text = "你输入的年份是:%s" % year return HttpResponse(text)
而后按下Ctrl+S保存,输入127.0.0.1:8000/index/220,由于不知足咱们本身定义的URL转换器的正则表达式,因此页面显示不出来,报如下错误:
再次输入127.0.0.1:8000/index/2018,页面访问成功,说明咱们定义的four_year转换器有效,以下图:
咱们其实除了在regex下修改,也能够随意编写to_python函数和to_url函数的内部实现,彻底按照咱们的意愿进行工做,很灵活呢。对于自定义URL转换器有个更好的改进之处,就是咱们能够新建一个converters.py文件专门保存咱们自定义的URL转换器,但有个问题就是,这个文件是咱们本身新建的,Django怎么会知道而且执行里面的代码呢?若是Django不执行的话,那咱们这个本身定义的URL转换器不就失去做用了吗?哈哈,有个方法能够解决,有没有发现就是每一个app包里都会有个init.py文件,其实Django每次运行项目都会执行这个文件,那么当咱们在init.py文件里导入这个cnverters.py文件,那么这个converters.py文件的代码就会自动被执行(导入表明导入模块的代码,例如:from . import converters)