您的位置:首页 > 编程语言 > Python开发

python后台架构Django教程——路由映射urls

2018-02-08 11:49 736 查看
全栈工程师开发手册 (作者:栾鹏)

本文衔接至python后台架构Django开发全解。

有其他问题请先阅读:http://blog.csdn.net/luanpeng825485697/article/details/79228895

前面的教程我们已经创建了名为hello的django项目,app1、app2两个应用,学习了view视图层、templates模板层。

地址映射urls

我们既然可以在views.py中实现不同功能的视图函数,那如何根据用户的网址映射到对应的函数上呢。只有映射到对应的函数,才能执行对应的功能,返回对应的视图。

举个例子:如果我们在app1/views.py中实现了insertuser函数,希望在用户输入网址http://127.0.0.1:8000/app1/insertuser/时启动这个函数,返回此函数影响的模板,那项目如何才能知道将网址映射到这个函数上呢?

根据不同的网址调用不同的视图函数,我们需要编写urls文件。这就是路由文件,根据用户打开的网址对应到不同的视图函数。

请求的URL被看做是一个普通的Python字符串,在其上查找并匹配。进行匹配时将不包括GET或POST请求方式的参数以及域名。

https://127.0.0.1/app1/insert/?username=luanpeng&password=123的请求中,URLconf只查找app1/insert/。

根路由

在Django项目中默认只提供了一个根路由,也就是只在hello文件下存在urls.py文件。当然你可以在这一个文件中把所有提供服务的网址都写下来,但是你最好采用多级路由的方法。

也就是说在根路由文件hello/urls.py中只匹配一部分网址,剩下的交给各功能模块app处理。

我们设置根路由文件如下。

from django.conf.urls import include,url  # Django1.11中的语法
from django.urls import path,include  #Django2中的语法
from django.contrib import admin

urlpatterns = [
# url是一个函数。regex和view为必填参数。regex就是用户输入的内容,view就是对应的视图生成函数
url(r'^vip/', admin.site.urls),  # Django自带的超级管理员后台地址。不要用台随意的网址
url(r'^app1/', include('app1.urls')),  # 映射到下级路由
path(r'^app2/', include('app2.urls')),  # 映射到下级路由
]


注意:在Django1.11中路由函数为url(),在Django2中路由函数改成了path(),不过路由函数向下兼容,在Django2中同样可以使用url()函数。

这一步是让项目的主urls也就是根路由文件指向我们建立的每个app独有的urls文件。

上面的代码表示如果输入的一级网址是app1,这将剩下的网址部分交给app1/urls.py子路由文件处理。例如http://127.0.0.1:8000/app1/xxx

如果输入的一级网址是app2,这将剩下的网址部分交给app2/urls.py子路由文件处理。例如http://127.0.0.1:8000/app2/xxx

admin.site.urls为Django系统自带的admin地址,后面在admin章节讲述。这个不涉及路由规则。

子路由

根路由匹配了一部分网址,将剩下的网址部分交给各自的app功能模块路由文件去识别。比如上面两个网址中的xxx。

现在,在app1目录中新建一个文件,名字为urls.py,它就是功能模块的路由,用来匹配传递给app1模块的网址。

在hello/app1/urls.py中路由的设置与根路由相似,示例如下:

from django.conf.urls import url  # Django1.11中的语法
from django.urls import path  #Django2中的语法
from . import views

urlpatterns = [
...
]


其中urlpatterns是路由匹配规则。 例如

urlpatterns = [
url(r'^finduser/', views.finduser),  # 直接映射到函数
]


表示如果子路由接收的网址字符串部分以”finduser/”开头,则将这个网址映射到finduser()函数上。

下面我们来介绍一下路由的设置规则

路由设置规则

1、include函数——路由转发

路由转发使用的是include()方法,需要提前导入,它的参数是转发目的地路径的字符串,路径以圆点分割。

url(r'^app1/', include('app1.urls')),  # 映射到下级路由


每当Django 遇到include()(来自django.conf.urls.include())时,它会去掉URL中匹配的部分并将剩下的字符串发送给include的URLconf做进一步处理,也就是转发到二级路由去。

假设用户输入的网址为http://127.0.0.1/app1/insert/

include函数将接收到的url地址(“app1/insert/”)去除了它前面的正则表达式(“^app1/”),将剩下的字符串(“insert/”)传递给下一级路由进行判断。

include的背后是一种即插即用的思想。项目根路由不关心具体app的路由策略,只管往指定的二级路由转发,实现了解耦的特性。app所属的二级路由可以根据自己的需要随意编写,不会和其它的app路由发生冲突。app目录可以放置在任何位置,而不用修改路由。这是软件设计里很常见的一种模式。

建议:除了admin路由外,你应该尽量给每个app设计自己独立的二级路由。

2、url函数——路由匹配

url()函数可以传递4个参数,其中2个是必须的:regex和view,以及2个可选的参数:kwargs和name。下面是具体的解释:

regex:

regex是正则表达式的通用缩写,它是一种匹配字符串或url地址的语法。Django拿着用户请求的url地址,在urls.py文件中对urlpatterns列表中的每一项条目从头开始进行逐一对比,一旦遇到匹配项,立即执行该条目映射的视图函数或二级路由,其后的条目将不再继续匹配。因此,url路由的编写顺序至关重要!

需要注意的是,regex不会去匹配GET或POST参数或域名,例如对于https://www.example.com/myapp/,regex只尝试匹配myapp/。对于https://www.example.com/myapp/?page=3,regex也只尝试匹配myapp/。

如果你想深入研究正则表达式,可以参考

http://blog.csdn.net/luanpeng825485697/article/details/78386400

但是在Django的实践中,你不需要多高深的正则表达式知识。

性能注释:正则表达式会进行预先编译当URLconf模块加载的时候,因此它的匹配搜索速度非常快,你通常感觉不到。

view:

当正则表达式匹配到某个条目时,自动将封装的HttpRequest对象作为第一个参数,正则表达式“捕获”到的值作为第二个参数,传递给该条目指定的视图。如果是简单捕获,那么捕获值将作为一个位置参数进行传递,如果是命名捕获,那么将作为关键字参数进行传递。

kwargs:

任意数量的关键字参数可以作为一个字典传递给目标视图。

name:

对你的URL进行命名,可以让你能够在Django的任意处,尤其是模板内显式地引用它。相当于给URL取了个全局变量名,你只需要修改这个全局变量的值,在整个Django中引用它的地方也将同样获得改变。这是极为古老、朴素和有用的设计思想,而且这种思想无处不在。

url参数捕获

很多时候,我们需要获取URL中的一些片段,作为参数,传递给处理请求的视图函数。

例如头条文章的网址https://www.toutiao.com/i6498210200338563598/

我们处理能匹配这个网址还要能将6498210200338563598这串数字传递给函数,以便来查询文章。这个时候就是获取url中的一个片段作为参数传递给视图函数。

url传递指定参数的语法为:

(?P<name>pattern)


name 可以理解为所要传递的参数的名称,pattern代表所要匹配的模式。例如,

url(r'^(?P<userid>[0-9]+)/detail/$', views.detail),


这个url映射不仅可以匹配….555/detail/ 或者…25/detail/这样的网址。同时还可以将正则部分匹配到的数据作为参数传递给views.detail()函数,views.detail()函数也会多出一个参数,名为userid。当然作为一个函数,userid参数是可以提供默认值的。

def detail(request,userid='1'):
users = User.objects.filter(id=userid)
....


Django2中的路由参数传递

在Django2中路由参数传递改变了一点写法。

urlpatterns = [
path('user/<int:userid>/', views.detail),
]


注意:

要捕获一段url中的值,需要使用尖括号,而不是之前的圆括号;

可以转换捕获到的值为指定类型,比如例子中的int。默认情况下,捕获到的结果保存为字符串类型,不包含/这个特殊字符;

Django2中路由的参数类型:

str:匹配任何非空字符串,但不含斜杠/,如果你没有专门指定转换器,那么这个是默认使用的;

int:匹配0和正整数,返回一个int类型

slug:可理解为注释、后缀、附属等概念,是url拖在最后的一部分解释性字符。该转换器匹配任何ASCII字符以及连接符和下划线,比如’building-your-1st-django-site’;

uuid:匹配一个uuid格式的对象。为了防止冲突,规定必须使用破折号,所有字母必须小写,例如’075194d3-6885-417e-a8a8-6c931e272f00’。返回一个UUID对象;

path:匹配任何非空字符串,重点是可以包含路径分隔符’/‘。这个转换器可以帮助你匹配整个url而不是一段一段的url字符串。

URL反向解析和命名空间

除了要匹配用户输入的网址,有时我们还需要在python代码中生成网址,例如重定向,在html中为元素添加url链接。

我们试想这样一个问题。我们编写好了路由规则。也对应上了视图函数。在html需要链接的地方也写好了链接的网址。在所有需要引用这个链接的地方也写好了这个网址字符串。可突然通知要修改网址命名规则。那就要修改所有也使用了这个网址的地方。这可是个麻烦事。

在需要解析URL的地方,对于不同层级,Django提供了不同的工具用于URL反查:

在模板语言中:使用url模板标签。(也就是写前端网页时)

在Python代码中:使用reverse()函数。(也就是写视图函数等情况时)

在更高层的与处理Django模型实例相关的代码中:使用get_absolute_url()方法。(也就是在模型model中)

删除模板中硬编码的URLs

在html模板文件中,可能会有一部分硬编码存在,所谓硬编码就是直接输入路径字符串,而不是以代号的形式存在。比如href或action里面的网址“/app1/insert/”部分:

<form action="/app1/insert/" method="post">


它对于代码修改非常不利。如果我们在urls.py文件中更换了正则表达式,那么你所有的html模板中对这个url的引用都需要修改,这是无法接受的!

我们前面知道可以给url定义一个name别名,可以用它来解决这个问题。

在app1/urls.py中,我们为一个视图网址添加一个name属性。代码如下:

url(r'^insert/', views.insertuser,name='inserpath')


而这个name一般不会变,我们就可以利用这个name来设置html模板中的网址。如下

<form action="{% url 'inserpath' %}" method="post">


这样form提交的网址就使用中name为insertpath这个url对应的网址。

那如果有多个app模块,每个app模块有相同name属性的url该怎么区分呢。

答案是使用URLconf的命名空间。在app1/urls.py文件的开头部分,添加一个app_name的变量来指定该应用的命名空间:

如 app1/urls.py文件

...
app_name = 'app1_name'  # 关键是这行

urlpatterns = [
...
]


在app1/templates/insert.html文件中的引用也要由

<form action="{% url 'inserpath' %}" method="post">


修改为

<form action="{% url 'app1_name:inserpath' %}" method="post">


注意引用方法是冒号而不是圆点也不是斜杠!!!!!!!!!!!!

带参路由的生成

url参数除了能匹配用户输入的网址。Django还提供了直接用url模式和提供的参数生成一个网址字符串。例如:

from django.shortcuts import reverse

urlpath = reverse('app1_name:detail', args=(user.id,)) # 将args传递给app1_name:detail对应的视图,然后生成网址。


上面代码中的app1_name为某一路由的命名空间,detail为某一映射的name名称。

重定向

在实现逻辑功能时,可能会需要实现重定向的功能。

1、通过redirect函数或HttpResponseRedirect函数硬编码的形式。

函数的参数可以是绝对路径跟相对路径,甚至可以使用get方式传参数

from django.shortcuts import redirect,HttpResponseRedirect

return redirect('/app1/alluser/')  # 硬编码形式
return redirect('/app1/finduser/?userid=62')  # 传递参数


就是重新打开‘/app1/alluser/’网址。

2、通过URLconf路由命名空间的形式。

return redirect('app1_name:alluserpath')


3、如果在逻辑函数中不做任何处理,可以直接在url中配置

自定义错误页面

当Django找不到与请求匹配的URL时,或者当抛出一个异常时,将调用一个错误处理视图。错误视图包括400、403、404和500,分别表示请求错误、拒绝服务、页面不存在和服务器错误。它们分别位于:

handler400 —— django.conf.urls.handler400。

handler403 —— django.conf.urls.handler403。

handler404 —— django.conf.urls.handler404。

handler500 —— django.conf.urls.handler500。

这些值可以在根URLconf中设置。在其它app中的二级URLconf中设置这些变量无效。

Django有内置的HTML模版,用于返回错误页面给用户,但是这些403,404页面实在丑陋,通常我们都自定义错误页面。

首先,在根路由文件hello/urls.py文件中额外增加下面的条目:

urlpatterns = [
....
]

from . import views
# 增加的条目
handler400 = views.bad_request
handler403 = views.permission_denied
handler404 = views.page_not_found
handler500 = views.page_error


然后在根视图文件hello/views.py文件(如果没有此文件就新建一个)中添加

from django.shortcuts import render

def page_not_found(request):
return render(request, '404.html')

def page_error(request):
return render(request, '500.html')

def permission_denied(request):
return render(request, '403.html')

def bad_request(request):
return render(request, '400.html')


然后在根模板目录hello/templates文件夹下新建400.html、403.html、404.html、500.html根据自己的需求创建这些文件。

我这里只是简单编写了这几个文件,示例如下400.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>您出错了</title>
</head>
<body>
<p>400页面</p>
</body>
</html>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: