Flask 框架 - 视图及路由 - 2

1 学习目标

  1. 能够写出带有参数的路由及视图函数

  2. 能够说出 url_for 函数的作用

  3. 能够说出自定义转换器的步骤

2 路由基本定义

  • 明确路由定义的参数,请求方式指定
  • PostMan 的使用

2.1 指定路由地址

# 指定访问路径为 demo1
@app.route('/demo1')
def demo1():
    return 'demo1'

2.2 给路由传参示例

有时我们需要将同一类 URL 映射到同一个视图函数处理,比如:使用同一个视图函数来显示不同用户的个人信息。

# 路由传递参数
@app.route('/user/<user_id>')
def user_info(user_id):
    return 'hello %s' % user_id
  • 路由传递的参数默认当做 string 处理,也可以指定参数的类型  <转换器名字:参数名>
# 路由传递参数
@app.route('/user/<int:user_id>')
def user_info(user_id):
    return 'hello %d' % user_id

这里指定int,尖括号中的内容是动态的,在此暂时可以理解为接受 int 类型的值,实际上 int 代表使用 IntegerConverter 去处理 url 传入的参数

无论 user_id 是什么类型,都可以用 %s 来输出 

2.3 指定请求方式

在 Flask 中,定义一个路由,默认的请求方式为:

  • GET
  • OPTIONS(自带)
  • HEAD(自带)

如果想添加请求方试,那么可以如下指定:

@app.route('/demo2', methods=['GET', 'POST'])
def demo2():
    # 直接从请求中取到请求方式并返回
    return request.method

demo2 请求方式为:

3 使用 PostMan 对请求进行测试

3.1 安装 PostMan 插件

PostMan 是一款功能强大的网页调试与发送网页 HTTP 请求的 Chrome 插件,可以直接去对我们写出来的路由和视图函数进行调试,作为后端程序员是必须要知道的一个工具。

安装方式1:去 Chrome 商店直接搜索 PostMan 扩展程序进行安装

安装方式2:https://www.getpostman.com/ 官网下载桌面版

安装方式3:将已下载好的 PostMan 插件文件夹拖入到浏览器

打开 Chrome 的扩展程序页面,打开 开发者模式 选项

将插件文件夹拖入到浏览器(或者点击加载已解压的扩展程序选择文件夹)

使用 PostMan,打开之后,会弹出注册页面,选择下方的 Skip this,go straight to the app 进行程序

3.2 postman使用

我们使用postman来测试一下这个函数:

 

使用如下:

4 视图常用逻辑

  • 返回 JSON
  • 重定向
    • url_for
  • 自定义状态码

4.1 JSON数据格式&返回JSON

4.1.1 JSON 介绍

新建一个json文件,我们来看一下json数据的个数。

json其实说白了就是一个字符串,格式类似于python中的字典。

作用:用于浏览器与服务器进行数据传输。

json:浏览器与服务器直接进行数据传输的一种数据格式。

4.1.2 字典转换为json

我们在代码中定义一个字典,然后将字典转换为json格式的字符串返回给浏览器
 

访问结果:

验证一下这个格式是否正确如下:(将上图的json字符串复制到下图的大框中,然后点击校验)

网址:http://www.kjson.com/

最终看到正确的json及说明是正确的json。

4.1.3 json转换为字典

4.1.4 返回JSON

在使用 Flask 写一个接口时候需要给客户端返回 JSON 数据,在 Flask 中可以直接使用 jsonify 生成一个 JSON 的响应,并且以application/json的内容格式返回到浏览器.

# 返回JSON
@app.route('/demo4')
def demo4():
    json_dict = {
        "user_id": 10,
        "user_name": "laowang"
    }
    # jsonify 会指定响应内容的数据格式(告诉客户端,返回的数据格式是什么)
    return jsonify(json_dict)

直接返回json字符串和通过jsonify返回json字符串的区别:

jsonify:返回的时候content-type为application/json

直接返回:content-type为默认text/html

为什么要用jsonify:这个跟前端逻辑有关系

不推荐使用 json.dumps 转成 JSON 字符串直接返回,因为返回的数据要符合 HTTP 协议规范,如果是 JSON 需要指定 content-type:application/json

这里大家先记住,如果要返回json格式的字符串,并且让前端知道是一个json格式的字符串,那就必须设置content-type:application/json。

5 重定向

重定向到 京东官网

# 重定向
@app.route('/demo5')
def demo5():
    return redirect('http://www.jd.com')

重定向到自己写的视图函数

可以直接填写自己 url 路径(这种写法有缺陷,如果user的路径修改了,那么我们的重定向的url也需要修改)

也可以使用 url_for 生成指定视图函数所对应的 url(推荐使用,需要导包)

语法:url_for(“函数名”)


@app.route('/demo1')
def demo1():
    return 'demo1'

# 重定向
@app.route('/demo5')
def demo5():
    return redirect(url_for('demo1'))

重定向到带有参数的视图函数

在 url_for 函数中传入参数

# 路由传递参数
@app.route('/user/<int:user_id>')
def user_info(user_id):
    return 'hello %d' % user_id

# 重定向
@app.route('/demo5')
def demo5():
    # 使用 url_for 生成指定视图函数所对应的 url
    return redirect(url_for('user_info', user_id=100))

6 自定义状态码

在 Flask 中,可以很方便的返回自定义状态码,以实现不符合 http 协议的状态码,例如:status code: 666

@app.route('/demo6')
def demo6():
    return '状态码为 666', 666

还可以为自定义的状态码进行说明

语法: “状态码 状态码名字”   (双引号引起来   空格隔开)

7 正则匹配路由

7.1 默认转换器的缺点:

如果我们现在想限制user/后边只能有6个数字,int转换器做不到

7.2 自定义转换器

定义一个自定义的转换器:RegexConverter

继承BaseConverter基类,重写regex属性

访问:

如果是4位数字,就找不到:

我们来看下原理:

看下int转换器:IntegerConverter

注:基类中 regex 为类属性,而重写 regex 属性实质上是添加了实例属性 regex。

7.3 自定义转换器升级

发现上面的正则转换器,有点笨,只能处理一种正则表达式,所以需要进行升级。

7.3.1 实现步骤

在 web 开发中,可能会出现限制用户访问规则的场景,那么这个时候就需要用到正则匹配,根据自己的规则去限定请求参数再进行访问

具体实现步骤为:

  1. 导入转换器基类:在 Flask 中,所有的路由的匹配规则都是使用转换器对象进行记录
  2. 自定义转换器:自定义类继承转换器基类
  3. 添加转换器到默认的转换器字典中
  4. 使用自定义转换器实现自定义匹配规则

7.3.2 代码实现

导入转换器基类

from werkzeug.routing import BaseConverter

自定义转换器:继承转换器基类

# 自定义正则转换器
class RegexConverter(BaseConverter):
    def __init__(self, url_map, *args):
        super(RegexConverter, self).__init__(url_map)
        # 将接受的第1个参数当作匹配规则进行保存
        self.regex = args[0]

添加转换器到默认的转换器字典中,并指定转换器使用时名字为:re

app = Flask(__name__)

# 将自定义转换器添加到转换器字典中,并指定转换器使用时名字为: re
app.url_map.converters['re'] = RegexConverter

使用转换器去实现自定义匹配规则

当前此处定义的规则是:6位数字

# <re("\\d{6}"):user_id>  或者  <re(r"\d{6}"):user_id>
@app.route('/user/<re("[0-9]{6}"):user_id>')
def user_info(user_id):
    return "user_id 为 %s" % user_id

运行测试:http://127.0.0.1:5000/user/123 ,如果访问的url不符合规则,会提示找不到页面

8 补充-startflask

配置:live Templates 活动模板, 配置完之后,就可以快速编码-代码块。

注意第七步:

原本不是change:而是define.

点击Define,弹出如下框. 选择Python( 这一步的意思就是,你这个代码块要在哪里使用.我们在python代码中使用)

 

输入startflask敲回车:


 就会生成代码。

9 自定义转换器其他两个函数实现

9.1 转换器 to_python

断点调试访问:(如上图,在36行打上断点,然后访问下边的url)

观察下图可以发现,user_ids的值是一个字符串:

但是我们想接收到的是一个列表怎么办?换句话说,如何才能在视图函数中接收到的 user_ids 就是一个列表?

接下来就来看一下我们要分析的 to_python

先定义一个转换器,并且重写 to_python 如下

注:regex 还可以写为:regex = r"(\d+,?)+\d$"

使用转换器.

再次访问之后,发现跟之前没有区别。这里的user_ids也没有变成我们想要的列表.

怎么办呢? 做如下修改:

再次访问:

断点如下:

最终结果:

思考:这个int转换器是如何将匹配到的字符串转换为int类型之后给user_id的?(提示:to_python)

 to_python 分析:

9.2 转换器to_url

9.2.1 问题

我们先来看一个例子:

新建demo3函数。重定向到demo2

上图红框代码说明:

其实demo2,在访问的时候url应该是:127.0.0.1:5000/users/1,2,3,4  然后list转换器将1,2,3,4转换为列表。

那么上图中通过url_for传递数据user_ids数据的时候,应该传递字符串:1,2,3,4

但是我们程序中可能就是一个user id的一个列表。

所以我们这里就传递列表过去。

来访问:

发现报错。肯定报错了。这是因为url中怎么可能传递一个列表的数据呢?

上述url是经过浏览器编码了。

其实应该是如下:

其实修改一下我们传递的数据即可,如下:

但是我们的逻辑中可能user的id本身是一个列表。传的时候,我们确实也可以将列表转换为一个字符串,传递到下边。但是这件事情,能不能别人帮咱们处理呢?

9.2.2 to_url介绍

来看一下to_url:

注意,demo3的代码,还应该是列表:

我们先来研究下to_url

访问/demo3,断点调试如下:

发现to_url中的value是我们url_for传递的参数。user_ids。

得出如下结论:

那就简单了。我们利用to_url,对value做一个转换即可:

再次访问:

断点调试如下:

断点走完,最终结果:

9.3 总结

继承于自定义转换器之后,还可以实现 to_python 和 to_url 这两个函数去对匹配参数做进一步处理:

to_python:

  • 该函数参数中的 value 值代表匹配到的值,可输出进行查看
  • 匹配完成之后,对匹配到的参数作最后一步处理再返回,比如:转成 int 类型的值再返回:
class RegexConverter(BaseConverter):
    def __init__(self, url_map, *args):
        super(RegexConverter, self).__init__(url_map)
        # 将接受的第1个参数当作匹配规则进行保存
        self.regex = args[0]

    def to_python(self, value):
        return int(value)

运行测试,在视图函数中可以查看参数的类型,由之前默认的 str 已变成 int 类型的值

to_url:

  • 在使用 url_for 去获取视图函数所对应的 url 的时候,会调用此方法对 url_for 后面传入的视图函数参数做进一步处理
  • 具体可参见 Flask 的 app.py 中写的示例代码:ListConverter

9.4 系统自带转换器

DEFAULT_CONVERTERS = {
    'default':          UnicodeConverter,
    'string':           UnicodeConverter,
    'any':              AnyConverter,
    'path':             PathConverter,
    'int':              IntegerConverter,
    'float':            FloatConverter,
    'uuid':             UUIDConverter,
}

系统自带的转换器具体使用方式在每种转换器的注释代码中有写,请留意每种转换器初始化的参数。

10 异常捕获

10.1 HTTP 异常主动抛出

abort 方法

  • 抛出一个给定状态代码的 HTTPException 或者 指定响应,例如想要用一个页面未找到异常来终止请求,你可以调用 abort(404)。
  • 参数:code – HTTP的错误状态码
# abort(404)
abort(500)

抛出状态码的话,只能抛出 HTTP 协议的错误状态码

常见的状态码:HTTP Status Code

10.2 捕获错误

errorhandler 装饰器

  • 注册一个错误处理程序,当程序抛出指定错误状态码的时候,就会调用该装饰器所装饰的方法

参数:

  • code_or_exception – HTTP的错误状态码或指定异常

例如统一处理状态码为500的错误给用户友好的提示:

@app.errorhandler(500)
def internal_server_error(error):
    return '服务器搬家了'

捕获指定异常

@app.errorhandler(ZeroDivisionError)
def zero_division_error(e):
    return '除数不能为0'

11 请求钩子

在客户端和服务器交互的过程中,有些准备工作或扫尾工作需要处理,比如:

  • 在请求开始时,建立数据库连接;
  • 在请求开始时,根据需求进行权限校验;
  • 在请求结束时,指定数据的交互格式;

为了让每个视图函数避免编写重复功能的代码,Flask提供了通用设施的功能,即请求钩子。

请求钩子是通过装饰器的形式实现,Flask支持如下四种请求钩子:

  • before_first_request
    • 在处理第一个请求前执行
  • before_request
    • 在每次请求前执行
    • 如果在某修饰的函数中返回了一个响应,视图函数将不再被调用
  • after_request
    • 如果没有抛出错误,在每次请求后执行
    • 接受一个参数:视图函数作出的响应
    • 在此函数中可以对响应值在返回之前做最后一步修改处理
    • 需要将参数中的响应在此参数中进行返回
  • teardown_request:
    • 在每次请求后执行
    • 接受一个参数:错误信息,如果有相关错误抛出

总结:请求钩子类似于装饰器,可以在不修改函数内部的逻辑前提下增加逻辑.

代码测试

from flask import Flask
from flask import abort

app = Flask(__name__)

# 在第一次请求之前调用,可以在此方法内部做一些初始化操作
@app.before_first_request
def before_first_request():
    print("before_first_request")


# 在每一次请求之前调用,这时候已经有请求了,可能在这个方法里面做请求的校验
# 如果请求的校验不成功,可以直接在此方法中进行响应,直接return之后那么就不会执行视图函数
@app.before_request
def before_request():
    print("before_request")
    # if 请求不符合条件:
    #     return "请求失败!"


# 在执行完视图函数之后会调用,并且会把视图函数所生成的响应传入,可以在此方法中对响应做最后一步统一的处理
@app.after_request
def after_request(response):
    print("after_request")
    response.headers["Content-Type"] = "application/json"
    return response


# 请每一次请求之后都会调用,会接受一个参数,参数是服务器出现的错误信息
# 没有报异常也会在index之后执行此函数,只不过没有将异常信息传递给error而已.
@app.teardown_request
def teardown_request(error):
    print("teardown_request")


@app.route('/')
def index():
    return 'index'

if __name__ == '__main__':
    app.run(debug=True)

 在第1次请求时的打印:

before_first_request
before_request
after_request
teardown_request

在第2次请求时的打印:

before_request
after_request
teardown_request

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值