第3章——Flask的URL与视图
前言
在使用PyCharm创建一个Flask项目之后,默认会生成app.py文件,文件的默认代码如下:
from flask import Flask
import config
app = Flask(__name__)
@app.route('/')
def hello_world(): # put application's code here
return 'Hello World!'
if __name__ == '__main__':
app.run()
我们把@app.route中的第一个字符串参数叫做URL,把被@app.route装饰的函数叫做视图函数,可以在代码中看到URL与视图函数的映射关系如下:
@app.route('/')
def hello_world():
return 'Hello World!'
其中,@app.route装饰器中添加了访问URL的规则"/“,”/“代表网站的根路径,只要在浏览器中输入网站的域名即可访问到”/“。被@app.route装饰的hello_world()函数会在浏览器访问”/"时被执行,此时hello_world函数没有做任何事情,只是简单的返回了Hello World字符串,因此在浏览器中访问http://127.0.0.1:5000时,我们就可以看到被打印的Hello World字符串。
3.1 定义URL
绝大部分的网站都不可能只有一个首页页面,以一个最简单的博客网站为例,博客页面相关的有博客列表、博客详情等,用户页面相关的有注册、登陆、个人中心等。所以在制作网站时,需要定义许多不同的URL来满足不同页面的访问需求,而URL总体上讲又分为两种,第一种是无参数的URL,第二种是有参数的URL,下面展开来讲。
3.1.1 定义无参数的URL
无参数URL是指在URL定义的过程中,不需要进行定义参数,这里以个人中心为例子,如果定义个人中心的URL为/profile,可以使用下面的代码来实现:
@app.route('/profile')
def profile():
return '这是个人中心'
当我们访问:http://127.0.0.1:5000/profile时,就可以得到在该路由下的对应规则,即返回字符串:‘这是个人中心’。
3.1.2 定义有参数的URL
很多时候,在访问某个URL时需要携带一些参数,如获取博客详情时,需要把博客的id传过去,那么博客详情的URL可能为/blog/13,其中13位博客的id。假如我们现在需要获取第10页的博客列表,那么博客列表的URL可能为/blog/list/10,其中10为页码。在Flask中,如果URL携带了参数,那么视图函数也必须定义相关形参来接收URL中的参数,这里以一个实例来举例:
@app.route('/blog/<int:blog_id>')
def blog_detail(blog_id):
return '您查找的博客id为: %s' %blog_id
URL中的参数可以指定类型,指定参数有两点好处:
- 浏览器访问URL时,如果传的参数不能被转换为指定的参数类型,如定义URL时参数为整形,但是访问的时候传的是不能被转换为整型的参数,如hello,那么这个URL就不会被匹配,从而抛出404错误,保证网站正常运行。
- URL本质上也是一个字符串,如果没有指定参数类型,那么参数传进视图函数时也默认是字符串类型,如果指定了参数类型, 那么在传给视图函数之前,会将参数转换为指定类型,这样视图函数拿到的参数就是经过转换后的,从而更加方便使用。
指定参数类型是通过语法:<类型:参数名>实现的。这里以/blog/<blog_id>这个URL为例,指定blog_id为int类型,就可以如上述代码所展示,就可以正常匹配路由了。
这里有很多的参数类型,举例如下:
参数类型 | 描述 |
---|---|
string | 字符串类型,可以接受除/以外的字符 |
int | 整型,可以接受能通过int()方法转换的字符 |
float | 浮点类型,可以接受能通过float()方法转换的字符 |
path | 路径,类似string,但是中间可以添加/ |
uuid | UUID类型,由一组32位的十六进制数所构成 |
any | any类型,指备选值中的任何一个 |
这里any比较特殊,例如,现在要实现一个获取某个分类的博客列表,但是博客分类只能是Python、Flask、Django之一,那么用any就可以轻松实现。
@app.route('/blog/list/<any(python,flask,django):category>')
def blog_list_with_category(category):
return "您获取的博客分类为:%s"%category
在访问该路由的时候,因为分类python被包含在了备选值中,所以可以正常显示内容:
但是访问其他参数,就会提示404 Not Found:
参数选择什么类型,完全取决于视图函数对这个参数的期望,如果期望是整型,那么就用int;如果期望是字符型,那么就用string。如果URL中需要传递多个参数,则只要用斜杠"/"分割即可,如果要获取某一个用户的博客列表URL,则需要传递用户id和分页页码两个参数,方法如下:
@app.route('/blog/list/<int:user_id>/<int:page>')
def blog_list(user_id,page):
return "您获取的博客id为:%s,博客分页为:%s"%(user_id,page)
在定义URL时,总是会力求简洁, 如以上描述的获取某个用户博客列表的URL,默认情况下都在第一页,这时候如果能把page省略掉,不传这个参数,那么URL会变得更加简洁,代码可以修改如下:
# 注释掉之前的写法
# @app.route('/blog/list/<int:user_id>/<int:page>')
# def blog_list(user_id,page):
# return "您获取的博客id为:%s,博客分页为:%s"%(user_id,page)
# 更加简介的写法
@app.route('/blog/list/<int:user_id>')
@app.route('/blog/list/<int:user_id>/<int:page>')
def blog_list(user_id,page=1):
return "您获取的博客id为:%s,博客分页为:%s"%(user_id,page)
通过上面的代码可以看到,我们定义了两个URL,第一个URL中没有page参数,但是blog_list视图函数的page形参有一个默认值为1,这样当我们访问不带page参数的URL时,默认的page就是1,从而简化了URL的使用:
关于在URL中传递参数,还可以通过查询字符串的方式来实现,即在URL后面通过"?"把参数添加上去,如果有多个参数,则可以通过&进行拼接,规则如下:
格式:URL?参数名1=参数值1&参数名2=参数值2
举例:http://127.0.0.1/list&id=1&page=10
通过查询字符串的方式传递参数,参数先不需要在URL中定义好,只需要在访问URL时将URL参数传进来即可。下面还是以获取某个用户的博客列表为例,用查询字符串的方式传递参数,则可以通过以下URL来访问:
/blog/list?user_id=10&page=8
通过查询字符串的方式传递参数,不需要在定义URL和视图函数时提前定义好参数,参数可以通过Flask中的request.args对象获取,如以上获取某个用户博客列表的URL则可以通过以下代码来实现:
from flask import Flask,request # 导入request
...
@app.route("/blog/list")
def blog_list_query_str():
user_id = request.args.get("user_id")
page = request.args.get("page")
return "您查找的用户为:%s,博客分页为:%s" % (user_id, page)
其中request是一个线程隔离的全局对象,request.args是一个继承自dict的werkzeug.datastructures.MultiDict对象,保存了当前请求的查询字符串参数,并且被解析成以键值对的形式存在,后面就可以通过字典的方式获取参数。接下来在浏览器中访问:
http://127.0.0.1:5000/blog/list?user_id=10&page=8
通过查询字符串的方式传递参数,参数是视图函数先规定好的,然后浏览器再按照规定传递,如上面代码中视图函数是通过user_id这个键来获取用户id,如果前端传的键不是user_id,那么视图函数就无法获取到用户id,从而导致数据获取失败。
3.2 HTTP请求方式
在HTTP协议中,请求URL有不同的方式(method),不同的请求方式有不同的应用场景,这里先了解一下HTTP请求方法和使用方法:
请求方法 | 描述 |
---|---|
GET | 从服务器获取资源。在浏览器中输入网址访问默认使用的GET请求 |
POST | 提交资源到服务器。如提交表单或者上传文件,一般用于创建新资源或者修改已有的资源 |
HEAD | 类似于GET请求,响应体中不包含具体的内容,用于获取消息头 |
DELETE | 请求服务删除资源 |
PUT | 请求服务器替换或修改已有的资源 |
OPTIONS | 请求服务器返回某个资源所支持的所有HTTP请求方法, 如AJAX跨域请求常用OPTIONS方法发送嗅探请求,来判断是否有对某个资源访问的权限 |
PATCH | 与PUT方法类似,但是PATCH方法一般用于局部资源更新,PUT方法用于整个资源的替换 |
在Flask项目中使用app.route装饰器定义URL的时候,默认用的是GET请求方式,而在浏览器中,在地址栏中输入一个URL并进行访问,默认也是GET请求,所以可以正常访问,如果想更改URL的请求方式,可以在定义URL的时候,给app.route设置methods参数,代码如下:
@app.route("/blog/add",methods=['POST'])
def blog_add():
return "使用POST方法添加博客"
通过上面的代码可以看到,在app.route中通过给methods参数赋值一个列表,并且列表中只有一个POST参数,来限制"/blog/add"这个URL只能通过POST方法进行访问,如下所示,会显示报错:“Method not Allowed”:
但是如果我们使用POST方式去访问,则可以正常去访问这个路由了:
如果需要一个URL既可以支持POST传参,又可以支持GET传参,那么可以给methods添加两个参数即可,代码如下:
@app.route('/blog/add/post/get', methods=['POST', 'GET'])
def blog_add_post_get():
if request.method == 'GET':
return "使用GET方法添加博客"
else:
return "使用POST方法添加博客"
这样就既可以通过GET请求方式访问,又可以通过POST方式访问了。
Flask从2.0版本开始,添加了5个快捷路由装饰器,如app.post表示定义的URL只接受POST请求。5个快捷路由装饰器如表:
快捷路由装饰器 | 描述 |
---|---|
app.get(“/login”) | 等价于app.route(“/login”,methods=[“GET”] |
app.post(“/login”) | 等价于app.route(“/login”,methods=[“POST”]) |
app.put(“/login”) | 等价于app.route(“/login”,methods=[“PUT”]) |
app.delete(“/login”) | 等价于app.route(“/login”,methods=[“DELETE”]) |
app.patch(“/login”) | 等价于app.route(“/login”,methods=[“PATCH”]) |
3.3 页面重定向
页面重定向,指的是浏览器会从一个页面自定跳转到另外一个页面,例如。当用户访问了一个需要权限的页面,但是该用户当前并没有登录,因此重定向到登录页面。重定向分为永久性的重定向和临时重定向,以下是具体介绍:
- 永久性重定向:HTTP的状态码是301,多用于旧网址已被废弃,要转到一个新的网址,确保用户正常的访问,比如京东的网站,当用户输入旧域名的时候,就会跳转到新的域名,这种情况就是永久重定向。
- 临时重定向:HTTP的状态码是302,表示页面的暂时性跳转,如访问到一个需要权限的网址或路由,但是当前用户没有登录(身份认证)或者低权限用户,这时候就会重定向到登录页面,这个是暂时性的重定向。
在Flask中,重定向是通过flask.redirect(location,code=302)函数来实现的,其中location表示需要重定向到哪个URL,code表示状态码,默认是302,即暂时性重定向。这里简单的举一个例子:
from flask import Flask,url_for,redirect # 导入库
...
app = Flask(__name__)
@app.route('/login')
def login():
return 'login page'
@app.route('/profile')
def profile():
name = request.args.get('name')
if not name:
# 如果没有name,说明没有登录,重定向到登录页面
return redirect("/login")
else:
return name
从上面的代码就可以看出,在访问/profile的时候,如果没有通过查询字符串的方式传递name,那么就会被重定向到/login,如果访问/profile?name=admin就可以看到在浏览器中显示admin,但是直接访问就会被弹回到/login。
3.4 构造URL
在前面执行redirect(“/login”)函数,让页面跳转到登录页面,这里是直接把/login这个路由硬编码进去的,对于项目的健壮性不太好,更好的方式应该是通过url_for函数来动态地构造URL。url_for接受视图函数名作为第1个参数,以及其他URL定义时的参数,如果还添加了其他参数,则会添加到URL的后面作为查询字符串参数。这里简单的举一个例子:
@app.route('/blog/<int:blog_id>')
def blog_detail(blog_id):
return "您获取的博客id为:%s" % blog_id
@app.route('/urlfor')
def get_url_for():
url = url_for("blog_detail", blog_id=2, user="admin")
return url
在get_url_for视图函数中使用了url_for函数,把函数名blog_detail作为第1个参数,因为blog_detail的URL需要接受一个blog_id参数,因此把blog_id也传递给了url_for函数,除此之外,还添加了一个user参数,因为user参数不是必需的,所以在构建URL后,会把user作为查询字符串参数拼接上去,这样访问/urlfor路由,就可以看到:
相比在代码中硬编码URL,使用url_for函数动态的构建URL函数有以下两个好处:
- URL是对外的,可能会经常发生变化, 但是视图函数不会经常发生变化。如果直接把URL硬编码,若后期URL变化了,凡是硬编码的URL都需要修改。
- URL在网络通信的过程中,需要把一些特殊字符包括中文在内的进行编码,如果URL中包含了特殊字符,用url_for函数会自动进行编码。