Flask框架

python学习网 2020-10-31 16:27:03

Flask框架

Flask诞生于2010年,是Armin ronacher(人名)用 Python 语言基于 Werkzeug 工具箱编写的轻量级Web开发框架。

Flask 本身相当于一个内核,其他几乎所有的功能都要用到扩展(邮件扩展Flask-Mail,用户认证Flask-Login,数据库Flask-SQLAlchemy),都需要用第三方的扩展来实现。比如可以用 Flask 扩展加入ORM、窗体验证工具,文件上传、身份验证等。Flask 没有默认使用的数据库,你可以选择 MySQL,也可以用 NoSQL。

Flask是一个基于Python开发并且依赖jinja2模板和Werkzeug WSGI服务的一个微型框架,对于Werkzeug本质是Socket服务端,其用于接收http请求并对请求进行预处理,然后触发Flask框架,开发人员基于Flask框架提供的功能对请求进行相应的处理,并返回给用户,如果要返回给用户复杂的内容时,需要借助jinja2模板来实现对模板的处理,即:将模板和数据进行渲染,将渲染后的字符串返回给用户浏览器。

“微”(micro) 并不表示你需要把整个 Web 应用塞进单个 Python 文件(虽然确实可以 ),也不意味着 Flask 在功能上有所欠缺。微框架中的“微”意味着 Flask 旨在保持核心简单而易于扩展。Flask 不会替你做出太多决策——比如使用何种数据库。而那些 Flask 所选择的——比如使用何种模板引擎——则很容易替换。除此之外的一切都由可由你掌握。如此,Flask 可以与您珠联璧合。

默认情况下,Flask 不包含数据库抽象层、表单验证,或是其它任何已有多种库可以胜任的功能。然而,Flask 支持用扩展来给应用添加这些功能,如同是 Flask 本身实现的一样。众多的扩展提供了数据库集成、表单验证、上传处理、各种各样的开放认证技术等功能。Flask 也许是“微小”的,但它已准备好在需求繁杂的生产环境中投入使用。

注:其 WSGI 工具箱采用 Werkzeug(路由模块),模板引擎则使用 Jinja2。这两个也是 Flask 框架的核心。

官网

官方文档

Falsk常用第三方扩展包
  • Flask-SQLalchemy:操作数据库,ORM;
  • Flask-script:终端脚本工具,脚手架;
  • Flask-migrate:管理迁移数据;
  • Flask-Session:Session存储方式指定;
  • Flask-WTF:表单;
  • Flask-Mail:邮件;
  • Flask-Bable:提供国际化和本地支持,翻译;
  • Flask-Login:认证用户状态;
  • Falsk:OpenID:认证,OAuth;
  • Falsk JSON-RPC:开发rpc远程服务[过程]调用
  • Flask-Bootstrap:集成前端Twitter Bootstrap框架
  • Flask-Moment:本地化日期和时间
  • Flask-Admin:简单而可扩展的管理接口的框架

可以通过 https://pypi.org/search/?c=Framework+%3A%3A+Flask 查看更多flask官方推荐的扩展

一、路由

1、路由的使用

路由和视图的名称必须全局统一,不能出现重复,否则报错

2、路由映射的方式
  • 任意路由参数

    • @app.route('/user/<username>')

      # 路由传递参数[没有限定类型]
      @app.route('/user/<username>')
      def user_info(username):
          return 'hello %s' % username
      
  • 限定路由参数

    • 限定路由参数的类型,flask系统自带转换器编写在werkzeug.routing.py文件中。底部可以看到以下字典:

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

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

      转换器名称 描述
      string 默认类型,接受不带斜杠的任何文本
      int 接受正整数
      float 接受正浮点值
      path 接收string但也接受斜线
      uuid 接受UUID(通用唯一识别码)字符串 xxxx-xxxx-xxxxx-xxxxx
    • @app.route('/post/<int:post_id>')

      # flask提供了路由转换器可以让我们对路由参数进行限定
      # 路由传递参数[限定数据类型]
      @app.route('/user/<int:user_id>')
      def user_info(user_id):
          return 'hello %d' % user_id
      
    • @app.route('/post/<float:post_id>')

      # flask提供了路由转换器可以让我们对路由参数进行限定
      # 路由传递参数[限定数据类型]
      @app.route('/user/<float:user_id>')
      def user_info(user_id):
          return 'hello %d' % user_id
      
    • @app.route('/post/<path:path>')

      # flask提供了路由转换器可以让我们对路由参数进行限定
      # 路由传递参数[限定数据类型]
      @app.route('/user/<path:user_id>')
      def user_info(user_id):
          return 'hello %d' % user_id
      
    • @app.route('/login', methods=['GET', 'POST'])

      # flask提供了路由转换器可以让我们对路由参数进行限定
      # 路由传递参数[限定数据类型]
      @app.route('/login', methods=['GET', 'POST'])
      def login():
          return 'hello World!!!'
      
3、自定义路由参数转换器

正则匹配路由参数

在web开发中,可能会出现限制用户访问规则的场景,这种情况就需要使用正则匹配,根据自己的规则去限定请求参数在进行访问

实现步骤:

  • 导入转换器基类:在Flask中,所有的路由的匹配规则都是使用转换器对象进行记录

    from werkzeug.routing import BaseConverter
    
  • 自定义转换器:自定义类继承于转换器基类

    # 自定义正则转换器
    from werkzeug.routing import BaseConverter
    class RegexConverter(BaseConverter):
        def __init__(self,map,*args):
            super().__init__(map)
            # 正则参数
            self.regex = args[0]
    
  • 添加转换器到默认的转换器字典中

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

    # 正则匹配路由(手机号正则)
    @app.route("/login/<re('1\d{10}'):mobile>")
    def login(mobile):
        return mobile
    
  • 测试代码:

    from flask import Flask,request
    # 初始化
    app = Flask(import_name=__name__)
    
    # 编写路由视图
    @app.route(rule='/')
    def index():
        return "<h1>hello world!</h1>"
    
    # 关于路由参数的限制,flask内置的类型不够具体,在开发中,我们经常接受参数,需要更加精确的限制
    # 这时候,可以使用正则匹配路由参数
    # 正则匹配路由参数,其实就是扩展flask内置的路由限定类型,需要完成4个步骤
    # 1. 引入flask的路由转换器
    from werkzeug.routing import BaseConverter
    # 2. 创建自定义路由转换器
    class MobileConverter(BaseConverter):
        """手机号码类型限制"""
        def __init__(self,map,*args):
            super().__init__(map)
            self.regex = "1[3-9]\d{9}"
    # 3. 把自定义转换器添加到flask默认的转换器字典中,也就是和原来的int,float等放在一块
    app.url_map.converters['mob'] = MobileConverter
    
    # 4. 类似原来的路由参数限制一样,调用自定义转换器名称即可
    @app.route(rule='/user/<mob:mobile>')
    def user(mobile):
        return mobile
    
    # 1. 引入flask的路由转换器
    from werkzeug.routing import BaseConverter
    # 2. 创建自定义路由转换器
    class RegexConverter(BaseConverter):
        """根据正则进行参数限制"""
        def __init__(self,map,*args):
            super().__init__(map)
            self.regex = args[0]
    # 3. 把自定义转换器添加到flask默认的转换器字典中,也就是和原来的int,float等放在一块
    app.url_map.converters['re'] = RegexConverter
    
    # 4. 类似原来的路由参数限制一样,调用自定义转换器名称即可
    @app.route(rule='/user/<re("\w+@\w+\.\w+"):email>')
    def user2(email):
        print(app.url_map) # 获取所有的路由列表
        return email
    
    # 声明和加载配置
    class Config():
        DEBUG = True
    app.config.from_object(Config)
    
    if __name__ == '__main__':
        # 运行flask
        app.run(host="0.0.0.0")
    

二、HTTP请求与响应

1、请求

官方文档:

  • request:flask中代表当前请求的 request 对象

  • 作用:在视图函数中取出本次请求数据

  • 导入from flask import request

  • 代码位置from flask.app import Request

  • 常用属性

    属性 说明 类型
    data 记录请求体的数据,并转换为字符串
    只要是通过其他属性无法识别转换的请求体数据
    最终都是保留到data属性中
    bytes类型
    form 记录请求中的html表单数据 MultiDict
    args 记录请求中的查询字符串,也可以是query_string MultiDict
    cookies 记录请求中的cookie信息 Dict
    headers 记录请求中的请求头 EnvironHeaders
    method 记录请求使用的HTTP方法 GET/POST
    url 记录请求的URL地址 string
    files 记录请求上传的文件列表 *
    json 记录ajax请求的json数据 json

2、获取用户请求相关的信息

  • request.method
  • request.args
  • request.form
  • request.values
  • request.files
  • request.cookies
  • request.headers
  • request.path
  • request.full_path
  • request.script_root
  • request.url
  • request.base_url
  • request.url_root
  • request.host_url
  • request.host
获取请求中各项数据
from flask import Flask,request

# 初始化
app = Flask(import_name=__name__)
# 编写路由视图
@app.route(rule='/')
def index():
    return "<h1>hello world!</h1>"

"""== 获取查询字符串 =="""
@app.route(rule="/args",methods=["post","get"])
def args():
    print(request.args) # 获取查询字符串
    """
    请求地址:
        http://127.0.0.1:5000/args?name=xiaoming&password=123&lve=swimming&lve=shopping
    打印效果:
        ImmutableMultiDict([('name', 'xiaoming'), ('password', '123')])
        ImmutableMultiDict是一个由flask封装的字典类,在字典的基础上,提供了一些其他的方法而已。
        格式:
            ImmutableMultiDict([('键', '值'), ('键', '值')])
        字典本身不支持同名键的,ImmutableMultiDict类解决了键同名问题
        操作ImmutableMultiDict,完全可以操作字典操作,同时还提供了get,getlist方法,获取指定键的1个值或多个值    
    """
    print(request.args["name"]) # xiaoming
    print(request.args.get("name")) # xiaoming
    print(request.args.getlist("lve")) # ['swimming', 'shopping']

    # 把ImmutableMultiDict转换成普通字典
    print(request.args.to_dict(flat=False)) # {'name': ['xiaoming'], 'password': ['123'], 'lve': ['swimming', 'shopping']}
    print(request.args.to_dict(flat=True)) # {'name': 'xiaoming', 'password': '123', 'lve': 'swimming'}

    return "ok"

"""== 获取请求体数据 =="""
@app.route(rule="/data",methods=["post","put","patch"])
def data():
    """接受客户端发送过来的请求体数据,是request.json,request.form,request.files等无法接受的数据,全部会保留到这里"""
    print(request.data) #

    # 接受表单提交的数据
    print(request.form) # ImmutableMultiDict([('username', 'root'), ('password', '123456')])

    # 接受ajax或其他客户端提交过来的json数据
    print(request.json) # {'username': 'root', 'password': '123456'}

    # 接受上传文件
    avatar = request.files["avatar"] # ImmutableMultiDict([('avatar', <FileStorage: '123.jpg' ('image/jpeg')>)])
    print(avatar) # <FileStorage: '123.jpg' ('image/jpeg')>


    # 获取请求头信息
    print( request.headers ) # 获取全部的而请求头信息
    print( request.headers.get("Host") )
    # 获取自定义请求头
    print( request.headers.get("company") ) # oldboy
    print( request.headers["company"] )     # oldboy
    
    # 本次请求的url地址
    print( request.url) # http://127.0.0.1:5000/data
    print( request.path ) # /data
    
    return "ok"

# 声明和加载配置
class Config():
    DEBUG = True
app.config.from_object(Config)

if __name__ == '__main__':
    # 运行flask
    app.run(host="0.0.0.0")

3、响应

flask默认支持两种相应方式:
数据响应:默认响应html文本,也可以返回JSON格式,或者其他格式
# 响应html文本
from flask import make_response

@app.route("/")
def index():
    # [默认支持]响应html文本
    return "<img src='http://flask.pocoo.org/static/logo.png'>"
	return make_response("<h1>hello user</h1>") # 等同于上面的一段

# 返回JSON数据:在Flsak中可以直接使用jsonify生成一个JSON的响应
from flask import Flask, request, jsonify
# jsonify 就是json里面的jsonify

@app.route("/")
def index():
    # 也可以响应json格式代码
    data = [
        {"id":1,"username":"liulaoshi","age":18},
        {"id":2,"username":"liulaoshi","age":17},
        {"id":3,"username":"liulaoshi","age":16},
        {"id":4,"username":"liulaoshi","age":15},
    ]
    return jsonify(data)
	# flask中返回json 数据,都是flask的jsonify方法返回就可以了.
页面响应:
  • 重定向

  • url_for 视图之间的跳转

    # 重定向到百度页面
    from flask import redirect
    # 页面跳转响应
    @app.route("/user")
    def user():
        # 页面跳转 redirect函数就是response对象的页面跳转的封装
        # Location: http://www.baidu.com
        return redirect("http://www.baidu.com")
    
    # 重定向到自己写的视图函数:可以直接填些自己的URL路径,也可以使用url_for生成指定试图函数所对应的URL----from flask import url_for
    # 内容响应
    @app.route("/")
    def index():
        # [默认支持]响应html文本
        # return "<img src='http://flask.pocoo.org/static/logo.png'>"
    
        # 也可以响应json格式代码
        data = [
            {"id":1,"username":"liulaoshi","age":18},
            {"id":2,"username":"liulaoshi","age":17},
            {"id":3,"username":"liulaoshi","age":16},
            {"id":4,"username":"liulaoshi","age":15},
        ]
        return jsonify(data)
    
    #使用url_for可以实现视图方法之间的内部跳转
    # url_for("视图方法名")
    @app.route("/login")
    def login():
        return redirect( url_for("index") )
    
    # 重定向到带有参数的视图函数:在url_for函数中传入参数
    # 路由传递参数
    @app.route('/user/<user_id>')
    def user_info(user_id):
        return 'hello %d' % user_id
    
    # 重定向
    @app.route('/demo4')
    def demo4():
        # 使用 url_for 生成指定视图函数所对应的 url
        return redirect( url_for(endpoint="user",user_id=100) )
    
    
自定义http响应状态码:在Flask中,可以很方便的返回自定义状态码,可以实现不符合http协议的状态码
@app.route('/demo4')
def demo4():
    return '状态码为 666', 400
  
"""还可以使用make_response创建Response对象,然后通过response对象返回数据"""
from flask import make_response
@app.route("/rep")
def index7():
    response = make_response("ok")
    print(response)
    response.headers["Company"] = "oldboy" # 自定义响应头
    response.status_code = 201 # 自定义响应状态码
    return response

4、http的会话机制

所谓的会话,就是客户端浏览器和服务端网站之间一次完整的交互过程.

会话的开始是在用户通过浏览器第一次访问服务端网站开始.

会话的结束时在用户通过关闭浏览器以后,与服务端断开.

所谓的会话控制,就是在客户端浏览器和服务端网站之间,进行多次http请求响应之间,记录、跟踪和识别用户的信息而已。

因为 http 是一种无状态协议,浏览器请求服务器是无状态的。

无状态:指一次用户请求时,浏览器、服务器无法知道之前这个用户做过什么,每次请求都是一次新的请求。

无状态原因:浏览器与服务器是使用 socket 套接字进行通信的,服务器将请求结果返回给浏览器之后,会关闭当前的 socket 连接,而且服务器也会在处理页面完毕之后销毁页面对象。

有时需要保持下来用户浏览的状态,比如用户是否登录过,浏览过哪些商品等

实现状态保持主要有两种方式:

  • 在客户端存储信息使用Cookie,token[jwt,oauth]
  • 在服务器端存储信息使用Session

1)Cookie

Cookie是由服务器端生成,发送给客户端浏览器,浏览器会将Cookie的key/value保存,下次请求同一网站时就发送该Cookie给服务器(前提是浏览器设置为启用cookie)。Cookie的key/value可以由服务器端自己定义。

使用场景: 登录状态, 浏览历史, 网站足迹,购物车 [不登录也可以使用购物车]

Cookie是存储在浏览器中的一段纯文本信息,建议不要存储敏感信息如密码,因为电脑上的浏览器可能被其它人使用

Cookie基于域名安全,不同域名的Cookie是不能互相访问的

如访问luffy.com时向浏览器中写了Cookie信息,使用同一浏览器访问baidu.com时,无法访问到luffy.com写的Cookie信息

浏览器的同源策略针对cookie也有限制作用.

当浏览器请求某网站时,会将本网站下所有Cookie信息提交给服务器,所以在request中可以读取Cookie信息

设置cookie

设置cookie需要通过flask的Response响应对象来进行设置,由响应对象会提供了方法set_cookie给我们可以快速设置cookie信息。

from flask imoprt Flask,make_response
@app.route('/set_cookie')
def set_cookie():
    resp = make_response('this is to set cookie')
    resp.set_cookie('username', 'xiaoming', max_age=3600)
    return resp
获取cookie
from flask import Flask,request
@app.route('/get_cookie')
def resp_cookie():
    resp = request.cookies.get('username')
    return resp

2)Session

对于敏感、重要的信息,建议要存储在服务器端,不能存储在浏览器中,如用户名、余额、等级、验证码等信息

在服务器端进行状态保持的方案就是Session

Session依赖于Cookie,session的ID一般默认通过cookie来保存到客户端。

flask中的session需要加密,所以使用session之前必须配置SECRET_KEY选项,否则报错.

session的有效期默认是会话期,会话结束了,session就废弃了。

如果将来希望session的生命周期延长,可以通过修改cookie中的sessionID来完成配置。
注:session也可以通过一些方式做到不依赖cookie;
设置session
from flask import session
@app.route('/set_session')
def set_session():
    session['username'] = 'xiaoming'
    return 'ok!'
获取session
from flask import session
@app.route('/get_session')
def get_session():
    return session.get('username')

三、请求钩子函数

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

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

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

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

  • before_first_request
    • 在处理第一个请求前执行[项目初始化时的钩子]
  • before_request
    • 在每次请求前执行
    • 如果在某修饰的函数中返回了一个响应,视图函数将不再被调用
  • after_reauest
    • 如果没有抛出错误,在每次请求后执行
    • 接收一个参数:视图函数做出的响应
    • 在此函数中可以对响应值在返回之前做最后一步修改的处理
    • 需要将参数中的相应在此参数中进行返回
  • teardown_request:
    • 在每次请求后执行
    • 接收一个参数:错误信息,如果有相关错误抛出
    • 需要设置flask的配置DEBUG=False,teardown_request才会接收到异常对象
代码示例
from flask import Flask,request

# 初始化
app = Flask(import_name=__name__)

# 声明和加载配置
class Config():
    DEBUG = True
app.config.from_object(Config)

@app.before_first_request
def before_first_request():
    """
    这个钩子会在项目启动后第一次被用户访问时执行
    可以编写一些初始化项目的代码,例如,数据库初始化,加载一些可以延后引入的全局配置
    """
    print("----before_first_request----")
    print("系统初始化的时候,执行这个钩子方法")
    print("会在接收到第一个客户端请求时,执行这里的代码")

@app.before_request
def before_request():
    """
    这个钩子会在每次客户端访问视图的时候执行
    # 可以在请求之前进行用户的身份识别,以及对于本次访问的用户权限等进行判断。..
    """
    print("----before_request----")
    print("每一次接收到客户端请求时,执行这个钩子方法")
    print("一般可以用来判断权限,或者转换路由参数或者预处理客户端请求的数据")

@app.after_request
def after_request(response):
    print("----after_request----")
    print("在处理请求以后,执行这个钩子方法")
    print("一般可以用于记录会员/管理员的操作历史,浏览历史,清理收尾的工作")
    response.headers["Content-Type"] = "application/json"
    response.headers["Company"] = "python oldboy..."
    # 必须返回response参数
    return response


@app.teardown_request
def teardown_request(exc):
    print("----teardown_request----")
    print("在每一次请求以后,执行这个钩子方法")
    print("如果有异常错误,则会传递错误异常对象到当前方法的参数中")
    # 在项目关闭了DEBUG模式以后,则异常信息就会被传递到exc中,我们可以记录异常信息到日志文件中
    print(exc)

# 编写路由视图
@app.route(rule='/')
def index():
    print("-----------视图函数执行了---------------")
    return "<h1>hello world!</h1>"

if __name__ == '__main__':
    # 运行flask
    app.run(host="0.0.0.0")
  • 在第1次请求时的打印:
----before_first_request----
系统初始化的时候,执行这个钩子方法
会在接收到第一个客户端请求时,执行这里的代码
----before_request----
127.0.0.1 - - [04/Aug/2020 14:40:22] "GET / HTTP/1.1" 200 -
每一次接收到客户端请求时,执行这个钩子方法
一般可以用来判断权限,或者转换路由参数或者预处理客户端请求的数据
-----------视图函数执行了---------------
----after_request----
在处理请求以后,执行这个钩子方法
一般可以用于记录会员/管理员的操作历史,浏览历史,清理收尾的工作
----teardown_request----
在每一次请求以后,执行这个钩子方法
如果有异常错误,则会传递错误异常对象到当前方法的参数中
None
  • 在第2次请求时的打印:
----before_request----
每一次接收到客户端请求时,执行这个钩子方法
一般可以用来判断权限,或者转换路由参数或者预处理客户端请求的数据
127.0.0.1 - - [04/Aug/2020 14:40:49] "GET / HTTP/1.1" 200 -
-----------视图函数执行了---------------
----after_request----
在处理请求以后,执行这个钩子方法
一般可以用于记录会员/管理员的操作历史,浏览历史,清理收尾的工作
----teardown_request----
在每一次请求以后,执行这个钩子方法
如果有异常错误,则会传递错误异常对象到当前方法的参数中
None

四、异常捕获

1、主动抛出异常

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

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

abort在工作中基本不会被使用,工作中的异常抛出往往在业务错误的时候使用raise进行抛出错误类型,而不是抛出http异常。

abort一般用于权限等页面上错误的展示提示.

2、捕获错误

  • errorhandler 装饰器
    • 注册一个错误处理程序,当程序抛出指定错误状态码的时候,就会调用该装饰器所装饰的方法
  • 参数:
    • code_or_exception – HTTP的错误状态码或指定异常
  • 例如统一处理状态码为500的错误给用户友好的提示:
@app.errorhandler(500)
def internal_server_error(e):
    return '服务器搬家了'
  • 捕获指定异常类型
@app.errorhandler(ZeroDivisionError)
def zero_division_error(e):
    return '除数不能为0'

代码:

from flask import Flask
# 创建flask应用
app = Flask(__name__)

"""加载配置"""
class Config():
    DEBUG = True
app.config.from_object(Config)

"""
flask中内置了app.errorhander提供给我们捕获异常,实现一些在业务发生错误时的自定义处理。
1. 通过http状态码捕获异常信息
2. 通过异常类进行异常捕获
"""

"""1. 捕获http异常[在装饰器中写上要捕获的异常状态码也可以是异常类]"""
@app.errorhandler(404)
def error_404(e):
    return "<h1>您访问的页面失联了!</h1>"
    # return redirect("/")

"""2. 捕获系统异常或者自定义异常"""
class APIError(Exception):
    pass

@app.route("/")
def index():
    raise APIError("api接口调用参数有误!")
    return "个人中心,视图执行了!!"

@app.errorhandler(APIError)
def error_apierror(e):
    return "错误: %s" % e

if __name__ == '__main__':
    app.run(host="localhost",port=8080)

五、context

执行上下文:即语境,语意,在程序中可以理解为在代码执行到某一行时,根据之前代码所做的操作以及下文即将要执行的逻辑,可以决定在当前时刻下可以使用到的变量,或者可以完成的事情。

Flask中上下文对象:相当于一个容器,保存了 Flask 程序运行过程中的一些信息[变量、函数、类与对象等信息]。

Flask中有两种上下文,请求上下文(request context)和应用上下文(application context)。

  1. application 指的就是当你调用app = Flask(__name__)创建的这个对象app
  2. request 指的是每次http请求发生时,WSGI server(比如gunicorn)调用Flask.__call__()之后,在Flask对象内部创建的Request对象;
  3. application 表示用于响应WSGI请求的应用本身,request 表示每次http请求;
  4. application的生命周期大于request,一个application存活期间,可能发生多次http请求,所以,也就会有多个request

请求上下文(request context)

思考:在视图函数中,如何取到当前请求的相关数据?比如:请求地址,请求方式,cookie等等

在 flask 中,可以直接在视图函数中使用 request 这个对象进行获取相关数据,而 request 就是请求上下文的对象,保存了当前本次请求的相关数据,请求上下文对象有:request、session

  • request
    • 封装了HTTP请求的内容,针对的是http请求。举例:user = request.args.get('user'),获取的是get请求的参数。
  • session
    • 用来记录请求会话中的信息,针对的是用户信息。举例:session['name'] = user.id,可以记录用户信息。还可以通过session.get('name')获取用户信息。

请求上下文提供的变量/属性/方法/函数/类与对象,只能在视图中或者被视图调用的地方使用

应用上下文(application context)

它的字面意思是 应用上下文,但它不是一直存在的,它只是request context 中操作当前falsk应用对象 app 的代理(人),所谓local proxy。它的作用主要是帮助 request 获取当前的flask应用相关的信息,它是伴 request 而生,随 request 而灭的。

应用上下文对象有:current_app,g

1、current_app

应用程序上下文,用于存储应用程序中的变量,可以通过current_app.name打印当前app的名称,也可以在current_app中存储一些变量,例如:

  • 应用的启动脚本是哪个文件,启动时指定了哪些参数
  • 加载了哪些配置文件,导入了哪些配置
  • 连接了哪个数据库
  • 有哪些可以调用的工具类、常量
  • 当前flask应用在哪个机器上,哪个IP上运行,内存多大
from flask import Flask,request,session,current_app,g

# 初始化
app = Flask(import_name=__name__)

# 声明和加载配置
class Config():
    DEBUG = True
app.config.from_object(Config)

# 编写路由视图
@app.route(rule='/')
def index():
    # 应用上下文提供给我们使用的变量,也是只能在视图或者被视图调用的地方进行使用,
    # 但是应用上下文的所有数据来源于于app,每个视图中的应用上下文基本一样
    print(current_app.config)   # 获取当前项目的所有配置信息
    print(current_app.url_map)  # 获取当前项目的所有路由信息

    return "<h1>hello world!</h1>"

if __name__ == '__main__':
    # 运行flask
    app.run(host="0.0.0.0")

2、g变量

g 作为 flask 程序全局的一个临时变量,充当者中间媒介的作用,我们可以通过它传递一些数据,g 保存的是当前请求的全局变量,不同的请求会有不同的全局变量,通过不同的thread id区别

g.name='abc' # name是举例,实际要保存什么数据到g变量中,可以根据业务而定,你可以任意的数据进去

注意:不同的请求,会有不同的全局变量g

from flask import Flask,request,session,current_app,g

# 初始化
app = Flask(import_name=__name__)

# 声明和加载配置
class Config():
    DEBUG = True
app.config.from_object(Config)

@app.before_request
def before_request():
    g.name = "root"

def get_two_func():
    name = g.name
    print("g.name=%s" % name)

def get_one_func():
    get_two_func()

# 编写路由视图
@app.route(rule='/')
def index():
    # 请求上下文提供的变量/属性/方法/函数/类与对象,只能在视图中或者被视图调用的地方使用
    # 请求上下文里面信息来源于每次客户端的请求,所以每个视图中请求上下文的信息都不一样
    # print(session)

    # 应用上下文提供给我们使用的变量,也是只能在视图或者被视图调用的地方进行使用,
    # 但是应用上下文的所有数据来源于于app,每个视图中的应用上下文基本一样
    print(current_app.config)   # 获取当前项目的所有配置信息
    print(current_app.url_map)  # 获取当前项目的所有路由信息
    get_one_func()
    return "<h1>hello world!</h1>"


if __name__ == '__main__':
    # 运行flask
    app.run(host="0.0.0.0")

两者区别:

  • 请求上下文:保存了客户端和服务器交互的数据,一般来自于客户端。
  • 应用上下文:flask 应用程序运行过程中,保存的一些配置信息,比如路由列表,程序名、数据库连接、应用信息等

六、Flask-Script扩展

这个模块的作用可以让我们通过终端来控制flask项目的运行,类似于django的manage.py

安装命令:

pip install flask-script

集成 Flask-Script到flask应用中,创建一个主应用程序,一般我们叫manage.py/run.py/main.py都行。

from flask import Flas 

app = Flask(__name__)

"""使用flask_script启动项目"""
from flask_script import Manager
manage = Manager(app)

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

if __name__ == "__main__":
    manager.run()

启动终端脚本的命令:

# 端口和域名不写,默认为127.0.0.1:5000
python run.py runserver

# 通过-h设置启动域名,-p设置启动端口
python run.py runserver -h127.0.0.1 -p8888

Flask-Script 还可以为当前应用程序添加脚本命令

1. 引入Command命令基类
2. 创建命令类必须直接或间接继承Command,并在内部实现run方法,同时如果有自定义的其他参数,则必须实现__init__
3. 使用flask_script应用对象manage.add_command对命令类进行注册,并设置调用终端别名。
"""自定义flask_script终端命令"""
from flask_script import Command

class HelloCommand(Command):
    """命令的相关描述"""
    def run(self):
        with open("text.txt","w") as f:
            f.write("hello\r\nhello")
            pass
        print("这是执行了hello命令")

manage.add_command('hello', HelloCommand() )

七、Jinja2模板引擎

Falsk内置的模板语言,它的设计思想来源于Django的模板引擎,并扩展了其语法和一系列强大的功能。

渲染模版函数

  • Flask提供的 render_template 函数封装了该模板引擎
  • render_template 函数的第一个参数是模板的文件名,后面的参数都是键值对,表示模板中变量对应的真实值。

1、模板基本使用

  1. 在flask应用对象创建的时候,设置或者保留template_folder参数,创建模板目录

    默认的情况创建templates文件夹下flask可以自动找到,否则需要使用下面的方式加载

    app = Flask(__name__,template_folder='templates')
    
  2. 在项目下创建 templates 文件夹,用于存放所有的模板文件,并在目录下创建一个模板html文件 index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <h1>{{title}}</h1>
    </body>
    </html>
    
  3. 在视图函数设置渲染模板并设置模板数据

    from flask import Flask, render_template
    # 初始化
    app = Flask(import_name=__name__,template_folder='templates')
    
    # 配置终端脚本运行项目
    from flask_script import Manager
    manager = Manager(app)
    
    # 声明和加载配置
    class Config():
        DEBUG = True
    app.config.from_object(Config)
    
    # 编写路由视图
    @app.route(rule='/')
    def index():
        data={}
        data["title"] = "我的flask项目"
        return render_template("index.html",**data)
    
    if __name__ == '__main__':
        # 运行flask
        manager.run()
    

2、输出变量

{{}} 来表示变量名,这种 {{}} 语法叫做 变量代码块

视图代码:

@app.route("/")
def index():
    data={}
    data["title"] = "我的flask项目"
    return render_template("index.html",**data)

模板代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{{title}}</title>
</head>
<body>
    <h1>{{title}}</h1>
</body>
</html>

Jinja2 模版中的变量代码块可以是任意 Python 类型或者对象,只要它能够被 Python 的 __str__ 方法或者str()转换为一个字符串就可以,比如,可以通过下面的方式显示一个字典或者列表中的某个元素:

视图代码:

from flask import Flask,render_template
from settings.dev import Config
from flask_script import Manager
"""创建flask应用"""
app = Flask(__name__,template_folder='templates')
"""使用脚手架[终端脚本]启动项目"""
manage = Manager(app)
"""加载配置"""
app.config.from_object(Config)

@app.route("/")
def index():
    data = {}
    data["title"] = "我的项目"
    data["data_list"] = ["a","b","c"]
    data["data_dict"] = {
        "name":"xiaoming",
        "id":100,
    }
    # return render_template("index.html",
    #                        title="我的flask项目",
    #                        data_list=data_list,
    #                        data_dict=data_dict
    #                        )
    return render_template("index.html",**data)
if __name__ == '__main__':
    manage.run()

模板代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{{title}}</title>
</head>
<body>
    <div>{{title}}</div>
    <div>{{list}}</div>
    <div>{{list[0]}}</div>
    <div>{{list.0}}</div>
    <div>{{list[-1]}}</div>
    <div>{{dict}}</div>
    <div>{{dict['name']}}</div>
    <div>{{dict.name}}</div>
</body>
</html>

使用 {# #} 进行注释,注释的内容不会在html中被渲染出来

{# {{ name }} #}

3、模板中特有的变量和函数

你可以在自己的模板中访问一些 Flask 默认内置的函数和对象

config

你可以从模板中直接访问Flask当前的config对象:

{{config.SQLALCHEMY_DATABASE_URI}}
sqlite:///database.db

request

就是flask中代表当前请求的request对象:

{{request.url}}
http://127.0.0.1

session

为Flask的session对象,显示session数据

{{session.new}}
True

g变量

在视图函数中设置g变量的 name 属性的值,然后在模板中直接可以取出

{{ g.name }}

url_for()

url_for会根据传入的路由器函数名,返回该路由对应的URL,在模板中始终使用url_for()就可以安全的修改路由绑定的URL,则不必担心模板中渲染出错的链接:

{{url_for('home')}}

如果我们定义的路由URL是带有参数的,则可以把它们作为关键字参数传入url_for(),Flask会把他们填充进最终生成的URL中:

{{ url_for('index', post_id=1)}}
/1
代码示例:

主程序 run.py:

from flask import Flask, render_template
# 初始化
app = Flask(import_name=__name__,template_folder='templates')

# 配置终端脚本运行项目
from flask_script import Manager
manager = Manager(app)

# 声明和加载配置
class Config():
    DEBUG = True
    SECRET_KEY = "abc"
app.config.from_object(Config)

# 编写路由视图
@app.route(rule='/')
def index():
    data={}
    data["title"] = "我的项目"
    data["list"] = ["a","b","c"]
    data["dict"] = {
        "name":"xiaoming",
        "id":100,
    }
    return render_template("index.html",**data)

from flask import session
@app.route("/session/set")
def set_session():
    session["name"] = "root"
    return "ok"

if __name__ == '__main__':
    # 运行flask
    manager.run()

模板 templates/index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{{title}}</title>
</head>
<body>
    <div>{{title}}</div>
    <div>{{list}}</div>
    <div>{{list[0]}}</div>
    <div>{{list.0}}</div>
    <div>{{list[-1]}}</div>
    <div>{{dict}}</div>
    <div>{{dict['name']}}</div>
    {# flask模板引擎的注释 #}
    {# <div>{{dict.name}}</div> #}
    <div>{{config}}</div>
    <div>{{config.DEBUG}}</div>
    <div>name={{request.args.name}}</div>
    <div>session.name={{session.name}}</div>
    <div>{{config.PREFERRED_URL_SCHEME}}://{{request.headers.Host}}{{url_for("set_session")}}</div>
</body>
</html>

pycharm中设置当前项目的模板语言:

files/settings/languages & frameworks/python template languages。

4、流程控制

主要包含两个:

- if/elif /else / endif
- for / endfor

1)if语句

Jinja2 语法中的if语句跟 Python 中的 if 语句相似,后面的布尔值或返回布尔值的表达式将决定代码中的哪个流程会被执行.

用 {%%} 定义的控制代码块,可以实现一些语言层次的功能,比如循环或者if语句

视图代码:

from flask import Flask,render_template,request
from settings.dev import Config
from flask_script import Manager
"""创建flask应用"""
app = Flask(__name__,template_folder='templates')
"""使用脚手架[终端脚本]启动项目"""
manage = Manager(app)
"""加载配置"""
app.config.from_object(Config)

@app.route("/list")
def list_page():
    data = {}
    data["book_list"] = [
        {"id":1,"price":78.50,"title":"javascript入门"},
        {"id":2,"price":78.50,"title":"python入门"},
        {"id":3,"price":78.50,"title":"django项目实战"}
    ]
    data["name"] = int( request.args.get("name") )
    return render_template("list.html",**data)

if __name__ == '__main__':
    manage.run()

list.html,模板代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <table border="1" align="center" width="680">
        <tr>
            <th>id</th>
            <th>标题</th>
            <th>价格</th>
        </tr>
        {# for循环 #}
        {% for book in book_list %}
        <tr>
            <td>{{ book.id }}</td>
            <td>{{ book.title }}</td>
            <td>{{ book.price }}</td>
        </tr>
        {% endfor %}
    </table>

    {# 判断一个参数是否是奇数 #}
    {% if name % 2 == 0 %}
        偶数<br>
    {% else %}
        奇数<br>
    {% endif %}
</body>
</html>

flask中也有过滤器,并且也可以被用在 if 语句或者for语句中:

视图代码:

from flask import Flask,render_template,request
from settings.dev import Config
from flask_script import Manager
"""创建flask应用"""
app = Flask(__name__,template_folder='templates')
"""使用脚手架[终端脚本]启动项目"""
manage = Manager(app)
"""加载配置"""
app.config.from_object(Config)

@app.route("/")
def index():
    data = {}
    data["title"] = "我的项目"
    data["data_list"] = ["a","b","c"]
    data["data_dict"] = {
        "name":"xiaoming",
        "id":100,
    }
    # return render_template("index.html",
    #                        title="我的flask项目",
    #                        data_list=data_list,
    #                        data_dict=data_dict
    #                        )
    return render_template("index.html",**data)

@app.route("/list")
def list_page():
    data = {}
    data["book_list"] = [
        {"id":1,"price":78.50,"title":"javascript入门"},
        {"id":2,"price":78.50,"title":"python入门"},
        {"id":3,"price":78.50,"title":"django项目实战"}
    ]
    data["name"] = int( request.args.get("name") )
    return render_template("list.html",**data)

@app.route("/filter")
def filter():
    data = {}
    data["text"] = "hello flask"
    data["img_url"] = '<img width="300px" src="https://github.githubassets.com/images/modules/site/heroes/octocat-paper.svg">'
    return render_template("fitler.html",**data)

if __name__ == '__main__':
    manage.run()

filter.html,模板代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <p>{{ text }}</p>
    <p>{{ text|upper }}</p>
    <p>{{ text|length }}</p>
    <p>{{ img_url }}</p>
    <p>{{ img_url|safe }}</p>
    {% if request.args.get("name")| int % 2 == 0 %}
    <p>偶数</p>
    {% else %}
    <p>奇数</p>
    {% endif %}
</body>
</html>

2)循环语句

  • 我们可以在 Jinja2 中使用循环来迭代任何列表或者生成器函数
{% for post in posts %}
    <div>
        <h1>{{ post.title }}</h1>
        <p>{{ post.text | safe }}</p>
    </div>
{% endfor %}
  • 循环和if语句可以组合使用,以模拟 Python 循环中的 continue 功能,下面这个循环将只会渲染post.text不为None的那些post:
{% for post in posts if post.text %}
    <div>
        <h1>{{ post.title }}</h1>
        <p>{{ post.text | safe }}</p>
    </div>
{% endfor %}
  • 在一个 for 循环块中你可以访问这些特殊的变量:
变量 描述
loop.index 当前循环迭代的次数(从 1 开始)
loop.index0 当前循环迭代的次数(从 0 开始)
loop.revindex 到循环结束需要迭代的次数(从 1 开始)
loop.revindex0 到循环结束需要迭代的次数(从 0 开始)
loop.first 如果是第一次迭代,为 True 。
loop.last 如果是最后一次迭代,为 True 。
loop.length 序列中的项目数。
loop.cycle 在一串序列间期取值的辅助函数。见下面示例程序。
  • 在循环内部,你可以使用一个叫做loop的特殊变量来获得关于for循环的一些信息
    • 比如:要是我们想知道当前被迭代的元素序号,并模拟Python中的enumerate函数做的事情,则可以使用loop变量的index属性,例如:
{% for post in posts%}
{{loop.index}}, {{post.title}}
{% endfor %}
  • 会输出这样的结果
1, Post title
2, Second Post
  • cycle函数会在每次循环的时候,返回其参数中的下一个元素,可以拿上面的例子来说明:
{% for post in posts%}
	{{loop.cycle('odd','even')}} {{post.title}}
{% endfor %}
  • 会输出这样的结果:
odd Post Title
even Second Post

代码示例,视图代码:

from flask import Flask, render_template
# 初始化
app = Flask(import_name=__name__,template_folder='templates')

# 配置终端脚本运行项目
from flask_script import Manager
manager = Manager(app)

# 声明和加载配置
class Config():
    DEBUG = True
    SECRET_KEY = "abc"
app.config.from_object(Config)

# 编写路由视图
@app.route(rule='/')
def index():
    data={}
    data["title"] = "我的项目"
    data["book_list"] = [
        {"id":10,"price":78.50,"title":"javascript入门"},
        {"id":21,"price":78.50,"title":"python入门"},
        {"id":33,"price":78.50,"title":"django项目实战"},
        {"id":34,"price":78.50,"title":"django项目实战"},
        {"id":33,"price":78.50,"title":"django项目实战"},
    ]
    return render_template("index.html",**data)

if __name__ == '__main__':
    # 运行flask
    manager.run()

continue.html,模板代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{{title}}</title>
</head>
<body>
<p>判断</p>
<!--{% if request.args.name %}-->
<!--    <p>欢迎回来,{{request.args.name}}</p>-->
<!--{% endif %}-->

<!--{% if request.args.name %}-->
<!--    <p>欢迎回来,{{request.args.name}}</p>-->
<!--{% else %}-->
<!--    <p>对不起,您尚未登录</p>-->
<!--{% endif %}-->

<!--{% if request.args.name=="root" %}-->
<!--    <p>欢迎回来,您是当前网站的超级管理员~</p>-->
<!--{% elif request.args.name %}-->
<!--    <p>尊敬的用户{{request.args.name}},欢迎回来</p>-->
<!--{% else %}-->
<!--    <p>对不起,您尚未登录</p>-->
<!--{% endif %}-->


<p>循环</p>
<table border="1" align="center" width="600">
    <tr>
        <th>序号</th>
        <th>ID</th>
        <th>price</th>
        <th>title</th>
    </tr>
    {% for book in book_list %}
        {% if loop.index %2 == 0 %}
            <tr bgcolor="#add8e6">
        {% else %}
            <tr>
        {% endif %}
            <td>{{ loop.index }}</td>
            <td>{{ book.id }}</td>
            <td>{{ book.price }}</td>
            <td>{{ book.title }}</td>
    </tr>
    {% endfor %}
</table>

</body>
</html>

5、过滤器

1)常见的内建滤器

  1. 字符串操作

    • safe:禁用转义

      <p>{{ '<em>hello</em>' | safe }}</p>
      
    • capitalize:把变量值的首字母转成是大写,其语字母转校小写

      <p>{{ 'hello' | capitalize }}</p>
      
    • lower:把值转成小写

      <p>{{ 'HELLO' | lower }}</p>
      
    • upper:把值转成小写

      <p>{{ 'HELLO' | lower }}</p>
      
    • title:把之中的每个单词的首字母都转成大写

      <p>{{ 'hello' | title }}</p>
      
    • reverse:字符串反转

      <p>{{ 'olleh' | reverse }}</p>
      
    • format:格式化输出

      <p>{{ '%s is %d' | format('name',17) }}</p>
      
    • striptags:渲染之前把值中所有的HTML标签都删掉

      如果内容中,存在大小于号的情况,则不要使用这个过滤器,容易误删内容。

      <p>{{ '<em>hello</em>' | striptags }}</p>
      <p>{{ "如果x<y,z>x,那么x和z之间是否相等?" | striptags }}</p>
      
    • truncate:字符串截断

      <p>{{ 'hello every one' | truncate(9)}}</p>
      
  2. 列表操作

    • first:取第一个元素

      <p>{{ [1,2,3,4,5,6] | first }}</p>
      
    • last:取最后一个元素

      <p>{{ [1,2,3,4,5,6] | last }}</p>
      
    • length:获取列表长度

      <p>{{ [1,2,3,4,5,6] | length }}</p>
      
    • sum:列表求和

      <p>{{ [1,2,3,4,5,6] | sum }}</p>
      
    • sort:列表排序

      <p>{{ [6,2,3,1,5,4] | sort }}</p>
      
  3. 语句块过滤

    {% filter upper %}
        #一大堆文字#
    {% endfilter %}
    

2)自定义过滤器

​ 过滤器的本质是函数。当模板内置的过滤器不能满足需求,可以自定义过滤器。自定义过滤器有两种实现方式:

  • 一种是通过Flask应用对象的 add_template_filter 方法
  • 通过装饰器来实现自定义过滤器

重要:自定义的过滤器名称如果和内置的过滤器重名,会覆盖内置的过滤器。

需求:添加列表的过滤器

方式一:

​ 通过调用应用程序实例的add_template_filter方法实现系定义的过滤器。该方法第一个参数是函数名,第二个参数是自定义的过滤器名称:

# 自定义过滤器
# 自定义过滤器
def do_list_reverse(old_list):
    # 因为字典/列表是属于复合类型的数据,所以改动数据的结构,也会应该能影响到原来的变量
    # 通过list新建一个列表进行操作,就不会影响到原来的数据
    new_list = list(old_list)
    new_list.reverse()
    return new_list

# 注册过滤器
app.add_template_filter(do_list_reverse, "lrev")

方式二

用装饰器来实现自定义过滤器。装饰器传入的参数是自定义的过滤器名称。

  • 主程序中创建和注册过滤器

    @app.template_filter('lrev')
    def do_list_reverse(old_list):
        # 因为字典/列表是属于复合类型的数据,所以改动数据的结构,也会应该能影响到原来的变量
        # 通过list新建一个列表进行操作,就不会影响到原来的数据
        new_list = list(old_list)
        new_list.reverse()
        return new_list
    
    @app.route(rule='/')
    def index():
        data={}
        data["user_list"] = ["xiaoming","小黑白","小红"]
        return render_template("index.html",**data)
    
  • html调用过滤器

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>title</title>
    </head>
    <body>
        <p>{{ user_list }}</p>
        <p>{{ user_list | lrev }}</p>
        <p>{{ user_list }}</p>
    </body>
    </html>
    

手机进行部分屏蔽示例:

# 后端代码
from flask import Flask,render_template,request
from settings.dev import Config
from flask_script import Manager
"""创建flask应用"""
app = Flask(__name__,template_folder='templates')
"""使用脚手架[终端脚本]启动项目"""
manage = Manager(app)
"""加载配置"""
app.config.from_object(Config)

@app.template_filter("mobile")
def do_mobile(data,string):
    return data[:3]+string+data[7:]

@app.route("/")
def index():
    data = {}
    data["user_list"] = [
        {"id":1,"name":"张三","mobile":"13112345678"},
        {"id":2,"name":"张三","mobile":"13112345678"},
        {"id":3,"name":"张三","mobile":"13112345678"},
        {"id":4,"name":"张三","mobile":"13112345678"},
    ]
    return render_template("index2.html",**data)

if __name__ == '__main__':
    manage.run()
<!-- 前端模板代码 --> 
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<table border="1" align="center" width="680">
    <tr>
        <th>ID</th>
        <th>姓名</th>
        <th>手机</th>
    </tr>
    {% for user in user_list %}
    <tr>
        <td>{{ user.id }}</td>
        <td>{{ user.name }}</td>
        <td>{{ user.mobile | mobile(string="****") }}</td>
    </tr>
    {% endfor %}

</table>
</body>
</html>

6、模板继承

在模板中,可能会遇到以下情况:

  • 多个模板具有完全相同的顶部和底部内容
  • 多个模板中具有相同的模板代码内容,但是内容中部分值不一样
  • 多个模板中具有相同的Html代码块内容

遇到这种情况,可以使用jinjia2模板中的继承来实现

模板继承是为了重用模板中的工共内容。一般Web开法中,继承主要使用在网站的顶部菜单、底部。这些内容可以定义在父模板中,子模版直接继承,而不需要重复书写

  • 标签定义的内容

    {% block top %} {% endblock %}
    
  • 相当于在父模板中挖个坑,当子模版继承父模板时,可以进行填充

  • 子模版使用extends指令声明这个模板继承自那个模板

  • 父膜板中定义的块在子模版中被重新定义,在子模版中调用父模板的内容时可以使用super()

父模板代码:

base.html

{% block top %}
  顶部菜单
{% endblock top %}

{% block content %}
{% endblock content %}

{% block bottom %}
  底部
{% endblock bottom %}

子模板代码:

  • extends指令声明这个模板继承自哪
{% extends 'base.html' %}
{% block content %}
 需要填充的内容
{% endblock content %}

模板继承使用时需要注意:

  1. 不支持多继承,支持多级继承
  2. 为了便于阅读,在子模版中使用extends时,尽量写在模板的第一行。
  3. 不能在一个模版文件中定义多个相同名字的block标签。
  4. 当在页面中使用多个block标签时,建议给结束标签起个名字,当多个block嵌套时,阅读性更好。

7、CSRF跨站脚本攻击

pip install flask_wtf

在Flask中,Flask-wtf扩展有一套完善的csrf防护体系,对于我们开发者来说,使用起来非常简单

  1. 设置应用程序的secret_key,用于加密生成的csrf_token的值

    # 1. session加密的时候已经配置过了.如果没有在配置项中设置,则如下:
    app.secret_key = "#此处可以写随机字符串#"
    
    # 2. 也可以写在配置类中。
    class Config(object):
        DEBUG = True
        SECRET_KEY = "dsad32DASSLD*13%^32"
        
    """加载配置"""
    app.config.from_object(Config)
    
  2. 导入flask_wtf.csrf中的CSRFProtect类,进行初始化,并在初始化的时候关联app

    from flask.ext.wtf import CSRFProtect
    CSRFProtect(app)
    
  3. 在表单中使用CSRF令牌

    <form method="post" action="/">
        <input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
    </form>
    

视图代码;

from flask import Flask,render_template
from settings.dev import Config
from flask_script import Manager
from flask_wtf.csrf import CSRFProtect
# from flask.ext.wtf import CSRFProtect  # 低版本的flask直接可以引入
"""创建flask应用"""
app = Flask(__name__,template_folder='templates')
"""使用脚手架[终端脚本]启动项目"""
manage = Manager(app)
"""加载配置"""
app.config.from_object(Config)

"""初始化csrf防范机制"""
CSRFProtect(app)

@app.route("/login",methods=["get"])
def loginform():
    return render_template("login.html")


@app.route("/dologin",methods=["post"])
def login():
    return "ok"

if __name__ == '__main__':
    manage.run()

模板代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="{{ url_for('login') }}" method="post">
        <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
        <input type="submit" value="登录">
    </form>
</body>
</html>

八、ORM

1、数据库操作

1)ORM

ORM 全拼Object-Relation Mapping,中文意为 对象-关系映射。主要实现模型对象到关系数据库数据的映射

优点:

  • 只需要面向对象编程,不需要面向数据库编写代码
    • 对数据库的操作都转化成对类属性和方法的操作
    • 不用编写各种数据库的sql语句
  • 实现了数据模型与数据库的解耦,屏蔽了不同数据库操作上的差异
    • 不再需要关注当前项目是那种数据库
    • 通过简单的配置就可以轻松更换数据库,而不需要修改代码

缺点

  • 相比较直接使用SOL语句操作数据库,有性能上的损失
  • 根据对象的操作转换成SQL语句,根据查询的结果转化成对象时,在映射过程中都有修女功能的损失

2)Flask-SQLAlchemy

Flask默认提供模型操作,但是并没有提供ORM,所以一般开发的时候我们会采取Flask-SQLAlchemy是一个简化了SQLAlchemy操作的flask扩展

SQLAlchemy: 地址链接

  • 安装flask-sqlalchemy【清华源】

    pip install flask-sqlalchemy -i https://pypi.tuna.tsinghua.edu.cn/simple
    
  • 连接MySQL数据库需要下载mysqldb驱动

    pip install flask-mysqldb -i https://pypi.tuna.tsinghua.edu.cn/simple
    
  • 安装flask-mysqldb时,注意

    安装 flask-mysqldb的时候,python底层依赖于一个底层的模块 mysql-client模块
    如果没有这个模块,则会报错如下:
    
    Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-install-21hysnd4/mysqlclient/
    
  • 解决方案

    sudo apt-get install libmysqlclient-dev python3-dev
    
    运行上面的安装命令如果再次报错如下:
       dpkg 被中断,您必须手工运行 ‘sudo dpkg --configure -a’ 解决此问题。
    
    则根据提示执行命令以下命令,再次安装mysqlclient
    	sudo dpkg --configure -a
    	apt-get install libmysqlclient-dev python3-dev
    
    解决了mysqlclient问题以后,重新安装 flask-mysqldb即可。
    pip install flask-mysqldb -i https://pypi.tuna.tsinghua.edu.cn/simple
    
(1)数据库连接设置
  • 在Flask-SQLAlchemy中,数据库使用URL指定,而且程序使用的数据库必须保存到Flask配置对象的SQLALCHEMY_DATABASE_URI 键中
    config.py配置文件代码:

    class Config(object):
        DEBUG = True
        SECRET_KEY = "*(%#4sxcz(^(#$#8423"
        # 数据库链接配置 = 数据库名称://登录账号:登录密码@数据库主机IP:数据库访问端口/数据库名称?charset=编码类型
        SQLALCHEMY_DATABASE_URI = "mysql://root:123@127.0.0.1:3306/students?charset=utf8mb4"
    
  • 其他设置

    # 动态追踪修改设置,如未设置只会提示警告
    SQLALCHEMY_TRACK_MODIFICATIONS = True
    #查询时会显示原始SQL语句
    SQLALCHEMY_ECHO = True
    
  • 配置完成需要去MySQL中创建项目所使用的数据库

    $ mysql -uroot -p123
    mysql > create database students charset=utf8mb4;
    
(2)常用的SQLAlchemy字段类型
模型字段类型名 python中数据类型 说明
Integer int 普通整数,一般是32位
SmallInteger int 取值范围小的整数,一般是16位
BigInteger int或long 不限制精度的整数
Float float 浮点数
Numeric decimal.Decimal 普通数值,一般是32位
String str 变长字符串
Text str 变长字符串,对较长或不限长度的字符串做了优化
Unicode unicode 变长Unicode字符串
UnicodeText unicode 变长Unicode字符串,对较长或不限长度的字符串做了优化
Boolean bool 布尔值
Date datetime.date 日期
Time datetime.datetime 日期和时间
LargeBinary str 二进制文件内容
(3)常用的SQLAlchemy列约束
选项名 说明
primary_key 如果为True,代表表的主键
unique 如果为True,代表这列不允许出现重复的值
index 如果为True,为这列创建索引,提高查询效率
nullable 如果为True,允许有空值,如果为False,不允许有空值
default 为这列定义默认值
(4)数据库的基本操作
  • 在Flask-SQLAlchemy中,添加、修改、删除操作,均由数据库会话管理。

    • 会话用 db.session 表示。在准备把数据写入数据库前,要先将数据添加到会话中然后调用 db.commit() 方法提交会话。
  • 在 Flask-SQLAlchemy 中,查询操作是通过 query 对象操作数据。

    • 最基本的查询是返回表中所有数据,可以通过过滤器进行更精确的数据库查询。
  • 定义模型类

    我们后面会把模型创建到单独的文件中,但是现在先把模型类写在main.py文件中。

    from flask import Flask
    # 初始化
    app = Flask(import_name=__name__)
    
    # 声明和加载配置
    class Config():
        DEBUG = True
        # 数据库链接配置 = 数据库名称://登录账号:登录密码@数据库主机IP:数据库访问端口/数据库名称?charset=编码类型
        SQLALCHEMY_DATABASE_URI = "mysql://root:123@127.0.0.1:3306/students?charset=utf8"
        # 动态追踪修改设置,如未设置只会提示警告
        SQLALCHEMY_TRACK_MODIFICATIONS = False
        # 显示原始SQL语句
        SQLALCHEMY_ECHO = True
    
    app.config.from_object(Config)
    
    # 初始化SQLAlchemy
    from flask_sqlalchemy import SQLAlchemy
    db = SQLAlchemy() # 初始化数据库操作对象
    db.init_app(app)  # 初始化数据库链接
    
    class Student(db.Model):
        # 表结构声明
        __tablename__ = "tb_student"
    
        # 字段声明
        id   = db.Column(db.Integer, primary_key=True, comment="主键")
        name = db.Column(db.String(64), index=True, comment="姓名")
        sex  = db.Column(db.Boolean, default=True, comment="性别")
        age  = db.Column(db.SmallInteger, nullable=True, comment="年龄")
        email = db.Column(db.String(128), unique=True, comment="邮箱地址")
        money = db.Column(db.Numeric(8,2), default=0, comment="钱包")
    
        # 自定义方法
        def __repr__(self):
            return 'Student:%s' % self.name
    
    class Teacher(db.Model):
        # 表结构声明
        __tablename__ = 'tb_teacher'
    
        id = db.Column(db.Integer, primary_key=True)
        name = db.Column(db.String(64), unique=True)
        option = db.Column(db.Enum("讲师","助教","班主任"), default="讲师")
        def __repr__(self):
            return 'Teacher:%s' % self.name
    
    class Course(db.Model):
        # 定义表名
        __tablename__ = 'tb_course'
        # 定义字段对象
        id = db.Column(db.Integer, primary_key=True)
        name = db.Column(db.String(64), unique=True)
        price = db.Column(db.Numeric(6,2))
        # repr()方法类似于django的__str__,用于打印模型对象时显示的字符串信息
        def __repr__(self):
            return 'Course:%s'% self.name
    
    @app.route(rule='/')
    def index():
        return "ok"
    
    if __name__ == '__main__':
        # 运行flask
        app.run(debug=True)
    

2、表和数据操作

1)数据表操作
  • 创建和删除表

    # 创建表
    db.create_all()  # 注意,create_all()方法执行的时候,需要放在模型的后面
    # 上面这段语句,后面我们需要转移代码到flask-script的自定义命令中。
    # 执行了一次以后,需要注释掉。
    
    # 删除表
    db.drop_all()
    

    代码示例

    from flask import Flask
    # 初始化
    app = Flask(import_name=__name__)
    
    # 声明和加载配置
    class Config():
        DEBUG = True
        # 数据库链接配置 = 数据库名称://登录账号:登录密码@数据库主机IP:数据库访问端口/数据库名称?charset=编码类型
        SQLALCHEMY_DATABASE_URI = "mysql://root:123@127.0.0.1:3306/students?charset=utf8"
        # 动态追踪修改设置,如未设置只会提示警告
        SQLALCHEMY_TRACK_MODIFICATIONS = False
        # 显示原始SQL语句
        SQLALCHEMY_ECHO = True
    
    app.config.from_object(Config)
    
    # 初始化SQLAlchemy
    from flask_sqlalchemy import SQLAlchemy
    db = SQLAlchemy() # 初始化数据库操作对象
    db.init_app(app)  # 初始化数据库链接
    
    class Student(db.Model):
        # 表结构声明
        __tablename__ = "tb_student"
    
        # 字段声明
        id   = db.Column(db.Integer, primary_key=True, comment="主键")
        name = db.Column(db.String(64), index=True, comment="姓名")
        sex  = db.Column(db.Boolean, default=True, comment="性别")
        age  = db.Column(db.SmallInteger, nullable=True, comment="年龄")
        email = db.Column(db.String(128), unique=True, comment="邮箱地址")
        money = db.Column(db.Numeric(8,2), default=0, comment="钱包")
    
        # 自定义方法
        def __repr__(self):
            return 'Student:%s' % self.name
    
    class Teacher(db.Model):
        # 表结构声明
        __tablename__ = 'tb_teacher'
    
        id = db.Column(db.Integer, primary_key=True)
        name = db.Column(db.String(64), unique=True)
        option = db.Column(db.Enum("讲师","助教","班主任"), default="讲师")
        def __repr__(self):
            return 'Teacher:%s' % self.name
    
    class Course(db.Model):
        # 定义表名
        __tablename__ = 'tb_course'
        # 定义字段对象
        id = db.Column(db.Integer, primary_key=True)
        name = db.Column(db.String(64), unique=True)
        price = db.Column(db.Numeric(6,2))
        # repr()方法类似于django的__str__,用于打印模型对象时显示的字符串信息
        def __repr__(self):
            return 'Course:%s'% self.name
    
    @app.route(rule='/')
    def index():
        return "ok"
    
    if __name__ == '__main__':
        with app.app_context():
            # db.drop_all()   # 删除所有的数据表
            db.create_all() # 创建所有的数据表
        # 运行flask
        app.run(debug=True)
    
2)数据操作
  • 添加一条数据

    student1 = Student(name="小明", sex=True, age=17, email="123456@qq.com", money=100)
    db.session.add(student1)
    db.session.commit()
    
    #再次插入 一条数据
    student2 = Student(name='小红', sex=False, age=13, email="16565666@qq.com", money=600)
    db.session.add(student2)
    db.session.commit()
    
  • 插入多条数据

    st1 = Student(name='wang',email='wang@163.com',age=22)
    st2 = Student(name='zhang',email='zhang@189.com',age=22)
    st3 = Student(name='chen',email='chen@126.com',age=22)
    st4 = Student(name='zhou',email='zhou@163.com',age=22)
    st5 = Student(name='tang',email='tang@163.com',age=22)
    st6 = Student(name='wu',email='wu@gmail.com',age=22)
    st7 = Student(name='qian',email='qian@gmail.com',age=22)
    st8 = Student(name='liu',email='liu@163.com',age=22)
    st9 = Student(name='li',email='li@163.com',age=22)
    st10 = Student(name='sun',email='sun@163.com',age=22)
    db.session.add_all([st1,st2,st3,st4,st5,st6,st7,st8,st9,st10])
    db.session.commit()
    
  • 删除数据

    # 方法1
    student = Student.query.first()
    db.session.delete(student)
    db.session.commit()
    
    # 方法2【事务中使用,就是乐观锁】
    ret = Student.query.filter(Student.name=='sun').delete()
    db.session.commit()
    
  • 更新数据

    # 方法1
    student = Student.query.first()
    student.name = 'dong'
    db.session.commit()
    
    # 方法2【事务中使用,就是乐观锁】
    ret = Student.query.filter(Student.name == 'liu').update({'money': 1000})
    db.session.commit()
    
    # 方法3【批量操作, 实现类似django里面F函数的效果】
    ret = Student.query.filter(Student.age == 22).update({Student.money: Student.money+'200'})
    db.session.commit()
    

3、数据库迁移

Message

message是一个基于Session实现的用于保存数据的集合,其特点是:使用一次就删除

from flask import Flask, flash, render_template, request,get_flashed_messages
app = Flask(__name__)


@app.route('/')
def index1():
    # 12. 获取消息
    v = get_flashed_messages()
    print(v)
    return render_template('s4.html')


@app.route('/set')
def index2():
    v = request.args.get('p')
    # 13. 设置消息
    flash('kkkk')
    return 'ok'


if __name__ == "__main__":
    app.run()
阅读(2411) 评论(0)