flask汇总

flask框架

蓝图

随着flask程序越来越复杂,我们需要对程序进行模块化的处理,之前学习过python的模块化管理,于是针对一个简单的flask程序进行模块化处理

Blueprint概念

  • 简单来说,Blueprint 是一个存储操作方法的容器,
    这些操作在这个Blueprint 被注册到一个应用之后就可以被调用,
    Flask 可以通过Blueprint来组织URL以及处理请求。

在Flask中Blueprint具有的属性

  • Flask使用Blueprint让应用实现模块化
  • 一个应用可以具有多个Blueprint
  • 在一个应用中,一个模块可以注册多次
  • Blueprint可以单独具有自己的模板、静态文件或者其它的通用操作方法
  • 在一个应用初始化时,就应该要注册需要使用的Blueprint
  • 一个Blueprint并不是一个完整的应用,它不能独立于应用运行,而必须要注册到某一个应用中。

使用蓝图的步骤

蓝图/Blueprint对象用起来和一个应用/Flask对象差不多,最大的区别在于一个 蓝图对象没有办法独立运行,必须将它注册到一个应用对象上才能生效

  • 1,创建一个蓝图对象
    admin=Blueprint('admin',name)
    • 蓝图对象中的参数
      • 蓝图的url前缀
        • 当我们在应用对象上注册一个蓝图时,可以指定一个url_prefix关键字参数(这个参数默认是/)
        • 在应用最终的路由表 url_map中,在蓝图上注册的路由URL自动被加上了这个前缀,这个可以保证在多个蓝图中使用相同的URL规则而不会最终引起冲突,只要在注册蓝图时将不同的蓝图挂接到不同的自路径即可
        • url_for

url_for('admin.index') # /admin/
* 注册静态路由
* 和应用对象不同,蓝图对象创建时不会默认注册静态目录的路由。需要我们在 创建时指定 static_folder 参数。
* 下面的示例将蓝图所在目录下的static_admin目录设置为静态目录

admin = Blueprint("admin",name,static_folder='static_admin')
app.register_blueprint(admin,url_prefix='/admin')
* 现在就可以使用/admin/static_admin/ 访问static_admin目录下的静态文件了
* 定制静态目录URL规则 :可以在创建蓝图对象时使用 static_url_path 来改变静态目录的路由。下面的示例将为 static_admin 文件夹的路由设置为 /lib
* admin = Blueprint("admin",name,static_folder='static_admin',static_url_path='/lib')
app.register_blueprint(admin,url_prefix='/admin')
* 设置模版目录
* 蓝图对象默认的模板目录为系统的模版目录,可以在创建蓝图对象时使用 template_folder 关键字参数设置模板目录

admin = Blueprint('admin',name,template_folder='my_templates')
* 如果在 templates 中存在和 my_templates 同名文件,则系统会优先使用 templates 中的文件

  • 2,在这个蓝图对象上进行操作,注册路由,指定静态文件夹,注册模版过滤器
    @admin.route('/')
    def admin_home():
    return 'admin_home'
  • 3,在应用对象上注册这个蓝图对象
    app.register_blueprint(admin,url_prefix='/admin')
    • 这里的前缀和蓝图对象的前缀一样的作用
  • 当这个应用启动后,通过/admin/可以访问到蓝图中定义的视图函数

虚拟环境的

为甚麽搭建虚拟环境

在实际开发中,多个程序的运行可以能需要调试各种版本的不同环境,比如不同的python解释器,不同的flask版本
问题描述:如果直接进行多个版本安装,会导致后安装的内容覆盖先安装的.
解决办法:使用虚拟环境,不同的程序选择不同的环境,互不影响

搭建步骤

  • 1.先查看当前电脑中是否有虚拟环境命令
    virtualenv --version

  • 2.[可选]安装虚拟环境的命令:
    sudo pip install virtualenv
    sudo pip install virtualenvwrapper

  • 3.查看是否有mkvirtualenv创建虚拟环境指令
    mkvirtualenv --version

  • 4.[可选]安装完虚拟环境后,如果提示找不到mkvirtualenv命令,须配置环境变量

    • 4.1、创建目录用来存放虚拟环境
      mkdir $HOME/.virtualenvs
    • 4.2、打开~/.bashrc文件,并添加如下:
      export WORKON_HOME=$HOME/.virtualenvs
      source /usr/local/bin/virtualenvwrapper.sh
    • 4.3、运行
      source ~/.bashrc
  • 5.创建虚拟环境的命令 :
    mkvirtualenv 虚拟环境名称(默认python2.x)
    例: mkvirtualenv py_flask

    • mkvirtualenv -p python3 虚拟环境名称(指定python3.x)
      例 :mkvirtualenv -p python3 py3_flask

如何使用虚拟环境

  • 1.查看虚拟环境的命令 :

    workon 两次tab键 或者 workon 回车

  • 2.使用虚拟环境的命令 :

    workon 虚拟环境名称
    例 :workon py_flask
    例 :workon py3_flask

  • 3.退出虚拟环境的命令 :

    deactivate

  • 4.删除虚拟环境的命令(需要先退出):

    rmvirtualenv 虚拟环境名称

    例 :删除虚拟环境py3_flask

    先退出:deactivate

    再删除:rmvirtualenv py3_flask

如何在虚拟环境中安装工具包

  • 1.安装flask-0.10.1的包:
    pip install 包名称
    例 : 安装flask-0.10.1的包
    pip install flask==0.10.1
  • 2.查看虚拟环境中安装的包 :
    pip freeze
  • 工具包安装的位置
    • python2版本下:
      ~/.virtualenvs/py_flask/lib/python2.7/site-packages/
    • python3版本下:
      ~/.virtualenvs/py3_flask/lib/python3.5/site-packages

web应用程序交互流程

flask

核心

  • jinja2
    • 模板
  • 拓展包
    • flask_script

      • 命令行方式启动
        python hello.py runserver -host ip地址
      • from flask import Flask

      • 1.从flask_script中导入Manager类

      • from flask_script import Manager

      • app = Flask(name)

      • 2.使用Manager管理app对象

      • manager = Manager(app)

      • @app.route('/')

      • def hello_world():

      • return "helloworld"
        
      • if name == 'main':

      • manager.run()
        
    • flask_wtf

      • 后台CSRF校验机制

      • from flask import Flask,render_template

      • from flask_wtf import CSRFProtect

      • app = Flask(name)

      • 设置SECRET_KEY

      • app.config["SECRET_KEY"] = "fjkdjfkdfjdk"

      • 保护应用程序

      • CSRFProtect(app)

      • @app.route('/')

      • def show_page():

      • return render_template('file01csrf.html')
        
      • @app.route('/add_data',methods=["POST"])

      • def add_data():

      • return "登陆成功"
        
      • if name == 'main':

      • app.run(debug=True)
        
      • 前端隐藏表单

      • {#设置隐藏的csrf_token,使用了CSRFProtect保护app之后,即可使用csrf_token()方法#}
        
      • <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
        
      • <label>用户名:</label> <input type="text" name="username"><br>
        
      • <label>密码:</label> <input type="text" name="username"><br>
        
      • <input type="submit" value="登陆">
        
    • flask_migrate

      • 数据库迁移
    • flask_sqlalchemy

      • flask操作数据库
  • werkzeug
    • 路由模块
      • from werkzeug.route import Converter
        自定义转换器转换器

最简单的flaskweb

  • 新建文件helloworld.py文件

1.导入Flask类

from flask import Flask

2.创建Flask对象接收一个参数__name__,它会指向程序所在的包

app = Flask(name)

3.装饰器的作用是将路由映射到视图函数index

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

4.Flask应用程序实例的run方法,启动WEB服务器

if name == 'main':
app.run()

加载应用程序的配置方式

  • from_pyfile

    • 从配置文件中添加
      在同级创建配置文件.ini
      配置内容每行一个
    • 从配置文件中加载配置

    • app.config.from_pyfile('config.ini')

  • from_object

    • 从配置对象中添加
    • 配置对象,里面定义需要给 APP 添加的一系列配置

    • class Config(object):

    • DEBUG = True
      
    • 从配置对象中加载配置

    • app.config.from_object(Config)

  • from_enver

    • 从环境变量中添加
    • 加载指定环境变量名称所对应的相关配置

    • app.config.from_envvar('FLASKCONFIG')

Flask()创建时的参数

  • name

    • 当前程序运行__main__
    • Flask程序所在的包(模块),传 name 就可以

    • 其可以决定 Flask 在访问静态文件时查找的路径

  • static_folder

    • 静态文件存储的文件夹,默认为static
  • static_url_path

    • 静态文件访问路径,默认为 /static
  • template_folder

    • 模板文件存储的文件夹,默认为 template

app.run()传递的参数

  • host
  • port
  • debug

路由的定义

指定请求方式

  • methods

  • @app.route('/demo2', methods=['GET', 'POST'])

  • def demo2():

  • # 直接从请求中取到请求方式并返回
    
  • return request.method
    
  • 配合postman软件开发

  • Postman功能(https://www.getpostman.com/features)

  • 主要用于模拟网络请求包

  • 快速创建请求

  • 回放、管理请求

  • 快速设置网络代理

指定请求参数

路由传递参数,整数

@app.route('/user/int:user_id')
def user_info(user_id):
return 'the num is %d' % user_id

  • 不填转换器名称默认是字符串

    • 默认的就是UnicodeConverter, 所以不填转换器名称, 或者使用default、string都是一样的. - 默认将参数当做普通字符串对待. DEFAULT_CONVERTERS = { 'default': UnicodeConverter, 'string': UnicodeConverter, 'any': AnyConverter, 'path': PathConverter, 'int': IntegerConverter, 'float': FloatConverter, 'uuid': UUIDConverter, }
  • 自定义转换器

  • from flask import Flask

  • 导入基类转换器

  • from werkzeug.routing import BaseConverter

  • app = Flask(name)

  • 1.自定义类,继承自BaseConverter

  • class MyRegexConverter(BaseConverter):

  • # 2.编写初始化方法, init方法, 接收两个参数, url_map, regex, 并初始化父类空间和子类空间
    
  • def __init__(self,url_map,regex):
    
  •     super(MyRegexConverter, self).__init__(url_map)
    
  •     self.regex = regex
    
  • 3.将自定义转换器类,添加到默认的转换列表中

  • app.url_map.converters['re'] = MyRegexConverter

  • 使用自定义转换器

  • 接收3位整数

  • @app.route('/<re("d{3}"):num>')

  • def hello_world(num):

  • print("num = %s"%num)
    
  • return "the num is %s"%num
    
  • 接收一个手机号

  • @app.route('/<re("1[345678]d{9}"):mobile>')

  • def get_phone_number(mobile):

  • return "the mobile is %s"%mobile
    
  • if name == 'main':

  • app.run()
    
    • 继承与BaseCoverter

    • 可以直接指定regex值,重写init方法,传递正则规则

    • class BaseConverter(object):

    • """Base class for all converters."""
      
    • regex = '[^/]+'
      
    • weight = 100
      
    • def __init__(self, map):
      
    •     self.map = map
      
    • 也可以不直接指定regex值,在使用自定义转换器的时候指定正则规则

    • class MyConverter(BaseConverter):

    • def __init__(self,url_map,regex):
      
    •     super(MyConverter, self).__init__(url_map)
      
    •     self.regex = regex
      
    • app.url_map.converters['re'] = MyConverter

    • @app.route('/<re("d{3}"):num>')

    • def index(num):

    • return num
      
    • 自定义转换器类中的其他方法

    • class MyConverter(BaseConverter):

    • def __init__(self,url_map,regex):
      
    •     super(MyConverter, self).__init__(url_map)
      
    •     self.regex = regex
      
    • def to_python(self, value):  
      
    •     return value
      
    • def to_url(self, value):  
      
    •     return value
      
    • app.url_map.converters['re'] = MyConverter

      • to_python
        • 匹配成功后被调用,可以对匹配到的参数进行处理,比如转换匹配到的数据的类型,在正则匹配完成之后,调用视图函数之前,可以对数据进行最后的处理
      • to_url
        • 在正则匹配之前调用执行,并传入参数,调用完成之后才开始真正的路由匹配,不关心是否匹配成功,可以通过此函数修正要传入路由中的参数,方便更好的正则匹配

重定向

重定向

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

  • redirect('路径')
    • 通过指定路径重定向到函数
  • url_for('函数名',key=value)
    • 通过指定函数,返回路径,可以附带附带请求参数

返回json

  • 注意contentType

  • contentType: 告诉服务器,我要发什么类型的数据

  • dataType:告诉服务器,我要想什么类型的数据,如果没有指定,那么会自动推断是返回 XML,还是JSON,还是script,还是String。

  • json和dict转换

    • dict->json
      • json = json.dumps(dict)
      • json = jsonity(key=value)
    • json->dict
      • dict = json.loads(json)
  • jsonify

  • 自定义状态码

    • 手动指定(响应体——状态码+响应头)

    • make_response()

    • @app.route('/')

    • def hello_world():

    • # 1.直接返回, 响应体 + 状态码
      
    • # return "hello",666
      
    • # return "hello","666 BIGERROR"
      
    • # 2.手动创建响应体对象
      
    • response = make_response("hello")
      
    • response.status = "888 XIAGAO"
      
    • return response
      
      • status
      • headers

上下文

相当于一个容器,保存了 Flask 程序运行过程中的一些信息。

请求上下文

保存了客户端和服务器交互的数据

  • request
  • session

应用上下文

flask 应用程序运行过程中,保存的一些配置信息,比如程序名、数据库连接、应用信息等

  • current_app

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

  • 应用的启动脚本是哪个文件,启动时指定了哪些参数

  • 加载了哪些配置文件,导入了哪些配置

  • 连了哪个数据库

  • 有哪些public的工具类、常量

  • 应用跑再哪个机器上,IP多少,内存多大

  • current_app.name

  • current_app.test_value='value'

  • g

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

  • g.name='abc'

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

状态保持

http是一种无状态协议,浏览器请求服务器是无状态的。
无状态:指一次用户请求时,浏览器、服务器不知道之前这个用户做过什么,每次请求都是一次新的请求。
无状态原因:浏览器与服务器是使用 socket 套接字进行通信的,服务器将请求结果返回给浏览器之后,会关闭当前的 socket 连接,而且服务器也会在处理页面完毕之后销毁页面对象。
有时需要保持下来用户浏览的状态,比如用户是否登录过,浏览过哪些商品等
实现状态保持主要有两种方式:
在客户端存储信息使用Cookie
在服务器端存储信息使用Session
二.生活状态举例:

Cookie是存储在浏览器中的一段纯文本信息,建议不要存储敏感信息如密码,因为电脑上的浏览器可能被其它人使用
Cookie基于域名安全,不同域名的Cookie是不能互相访问的
应用场景:广告推送;商城结账提取商品信息

  • 获取与设置cookie
  • from flask import Flask, make_response, request

  • app = Flask(name)

  • 设置cookie值

  • @app.route('/set_cookie')

  • def set_cookie():

  • response = make_response("set cookie")
    
  • response.set_cookie("name","zhangsan")
    
  • response.set_cookie("age","13",10) #10秒有效期
    
  • return response
    
  • 获取cookie

  • @app.route('/get_cookie')

  • def get_cookie():

  • #获取cookie,可以根据cookie的内容来推荐商品信息
    
  • # name = request.cookies['haha']
    
  • name = request.cookies.get('name')
    
  • age = request.cookies.get('age')
    
  • return "获取cookie,name is %s, age is %s"%(name,age)
    
  • if name == 'main':

  • app.run(debug=True)
    

Session

对于敏感、重要的信息,建议要存储在服务器端,不能存储在浏览器中,如用户名、余额、等级、验证码等信息,所以可以使用session进行保存
在服务器端进行状态保持的方案就是Session

  • 依赖cookie

  • 获取和设置session

  • from flask import Flask,session

  • app = Flask(name)

  • 设置SECRET_KEY

  • app.config["SECRET_KEY"] = "fhdk^fk#djefkj&&&"

  • 设置session

  • @app.route('/set_session/path:name')

  • def set_session(name):

  • session["name"] = name
    
  • session["age"] = "13"
    
  • return "set session"
    
  • 获取session内容

  • @app.route('/get_session')

  • def get_session():

  • name = session.get('name')
    
  • age = session.get('age')
    
  • return "name is %s, age is %s"%(name,age)
    
  • if name == 'main':

  • app.run(debug=True)
    
  • SECRET_KEY

请求钩子

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

在请求开始时,建立数据库连接;
在请求开始时,根据需求进行权限校验;
在请求结束时,指定数据的交互格式;
为了让每个视图函数避免编写重复功能的代码,Flask提供了通用设施的功能,即请求钩子。

before_first_request

在第一次请求之前调用,可以在此方法内部做一些初始化操作
如:数据库的连接

before_request

在每次请求之前调用,这时候已经有请求了,可能在这个方法里面做请求的校验

如果请求的校验不成功,可以直接在此方法中进行响应,直接return之后那么就不会执行视图函数

@app.before_request
def before_request():
print("before_request")
# if 请求不符合条件:
# return "laowang"

after_request

在执行完视图函数之后会调用,并且会把视图函数所生成的响应传入,可以在此方法中对响应做最后一步统一的处理

@app.after_request
def after_request(response):
print("after_request")
response.headers["Content-Type"] = "application/json"
return response

teardown_request

请每一次请求之后都会调用,会接受一个参数,参数是服务器出现的错误信息

@app.teardown_request
def teardown_request(e):
print("teardown_request")

当前Request对象

request 就是flask中代表当前请求的 request 对象,其中一个请求上下文变量(理解成全局变量,在视图函数中直接使用可以取到当前本次请求)

args

  • 地址栏数据,问号后面
    www.baidu.com?key=value&key=value

data

  • json/xml,post提交的请求

form

  • 表单
  • 记录请求中的cookie信息

files

  • 文件内容

headers

  • 记录请求中的报文头

method

  • 记录请求中使用的HTTP方法

url

  • 记录请求的url地址

路由和视图函数在定义和匹配过程中关键类

BaseConverter

  • 记录匹配规则

Rule

  • 记录视图函数与路由映射关系

Map

  • 路由与视图函数关系列表

MapAdapter

  • 做具体匹配工作

Flask_script

属于flask的扩展包,通过使用Flask-Script扩展,我们可以在Flask服务器启动的时候,通过命令行的方式传入参数。而不仅仅通过app.run()方法中传参,比如我们可以通过:

继承脚本扩展,动态运行程序,指定IP,PORT

python hello.py runserver -host ip地址

Manager(app)

from flask import Flask

1.从flask_script中导入Manager类

from flask_script import Manager

app = Flask(name)

2.使用Manager管理app对象

manager = Manager(app)

@app.route('/')
def hello_world():
return "helloworld"

if name == 'main':
manager.run()

数据库

  • ORM

  • ORM 全拼Object-Relation Mapping. 称为对象-关系映射

  • 主要实现模型对象到关系数据库数据的映射.

    • 优点
      • 对数据库的操作都转化成对类,属性和方法的操作.
        不用编写各种数据库的sql语句.
        不在关注,使用的是mysql、oracle...等数据库
    • 缺点
      • 相比较直接使用SQL语句操作数据库,有性能损失.
  • Flask-SQLAlchemy

  • SQLALchemy 实际上是对数据库的抽象,让开发者不用直接和 SQL 语句打交道,而是通过 Python 对象来操作数据库,在舍弃一些性能开销的同时,换来的是开发效率的较大提升

  • SQLAlchemy是一个关系型数据库框架,它提供了高层的 ORM 和底层的原生数据库的操作。flask-sqlalchemy 是一个简化了 SQLAlchemy 操作的flask扩展。

    • 安装
      • pip install flask-sqlalchemy
      • 如果连接的是 mysql 数据库,需要安装 mysqldb
        pip install flask-mysqldb
      • 提示:如果flask-mysqldb安装不上,安装, pip install pymysql
    • 数据库连接设置
      • 数据库链接地址
        app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:mysql@127.0.0.1:3306/test'
        • 格式:mysql://<用户名>:<密码>@:<端口>/数据库名称
      • 动态追踪修改设置,如未设置只会提示警告
        app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
      • 有时候需要查看映射的sql语句
        • app.config['SQLALCHEMY_ECHO'] = True
      • 配置完成需要去 MySQL 中创建项目所使用的数据库
        $ mysql -uroot -pmysql
        $ create database test charset utf8;
      • 配置信息汇总
        • SQLALCHEMY_BINDS 一个映射 binds 到连接 URI 的字典。更多 binds 的信息见用 Binds 操作多个数据库。
        • SQLALCHEMY_ECHO 如果设置为Ture, SQLAlchemy 会记录所有 发给 stderr 的语句,这对调试有用。(打印sql语句)
        • SQLALCHEMY_RECORD_QUERIES 可以用于显式地禁用或启用查询记录。查询记录 在调试或测试模式自动启用。更多信息见get_debug_queries()。
        • SQLALCHEMY_NATIVE_UNICODE 可以用于显式禁用原生 unicode 支持。当使用 不合适的指定无编码的数据库默认值时,这对于 一些数据库适配器是必须的(比如 Ubuntu 上 某些版本的 PostgreSQL )。
        • SQLALCHEMY_POOL_SIZE 数据库连接池的大小。默认是引擎默认值(通常 是 5 )
        • SQLALCHEMY_POOL_TIMEOUT 设定连接池的连接超时时间。默认是 10 。
        • SQLALCHEMY_POOL_RECYCLE 多少秒后自动回收连接。这对 MySQL 是必要的, 它默认移除闲置多于 8 小时的连接。注意如果 使用了 MySQL , Flask-SQLALchemy 自动设定 这个值为 2 小时。
      • 连接其他数据库
        • Postgres:
          postgresql://scott:tiger@localhost/mydatabase
        • MySQL:
          mysql://scott:tiger@localhost/mydatabase
        • Oracle:
  • oracle://scott:tiger@127.0.0.1:1521/sidname
    * SQLite (注意开头的四个斜线):
    sqlite:////absolute/path/to/foo.db
    • 创建sqlalchemy对象
      • 创建SQLAlchemy对象

db = SQLAlchemy(app)

* 使用模型类创建数据表
    * 一对多关系
        * 一方
            * 创建模型类(继承自db.Model)
             * > class Student(db.Model):
             * >     __tablename__ = "students"
             * >     id = db.Column(db.Integer,primary_key=True)
             * >     name = db.Column(db.String(64),unique=True,nullable=False)
             * > 
             * >     #关系属性
             * >     courses = db.relationship("Course",backref="students",secondary=tb_student_course)

                * 默认表名为模型类的名字

__tablename__指定表名
* class Student(db.Model):
tablename = "students"
* 类属性名表示的字段名
* > 定义列对象
* > 变量作为字段名
* > 括号内是字段类型和字段约束

                    *  id = db.Column(db.Integer,primary_key=True)
name = db.Column(db.String(64),unique=True,nullable=False)
                * 关系属性
                 * > 关系属性方便查询使用
                 * > 通过 table_name.courses可以访问到‘Course’模型类定义的数据表
                 * > ‘Course’模型类定义的数据表,通过Course.students可以访问到本数据表

                    *  courses = db.relationship("Course",backref="students")
                    * lazy='dynamic'
                     * > 第三个参数lazy决定了什么时候SQLALchemy从数据库中加载数据
                     * > 如果设置为子查询方式(subquery),则会在加载完Role对象后,就立即加载与其关联的对象,这样会让总查询数量减少,但如果返回的条目数量很多,就会比较慢
                     * > 设置为 subquery 的话,role.users 返回所有数据列表
                     * > 另外,也可以设置为动态方式(dynamic),这样关联对象会在被使用的时候再进行加载,并且在返回前进行过滤,如果返回的对象数很多,或者未来会变得很多,那最好采用这种方式

                    * 关系属性在任何一方设置都行
        * 多方
            * 设置外键
                * role_id = db.Column(db.Integer,db.ForeignKey(Role.id))
    * 多对多关系
        * 创建一张关联表

关联表数据分别和两张表设置外键
* tb_student_course = db.Table('tb_student_course',
db.Column('student_id', db.Integer, db.ForeignKey('students.id')),
db.Column('course_id', db.Integer, db.ForeignKey('courses.id'))
)
* 关系属性设置在任何一方都行
* courses = db.relationship('Course', secondary=tb_student_course,
backref='student',
lazy='dynamic')
* 向数据表中添加数据
* 在
if name == main:
之后
* 一对多
* 创建模型类对象
* stu1 = Student(name='张三')
* cou1 = Course(name='物理')
* 将模型类对象添加进会话
* db.session.add(obj) 添加对象
db.session.add_all([obj1,obj2,..]) 添加多个对象
* db.session.delete(obj) 删除对象
* 提交会话
* db.session.commit() 提交会话
* db.session.rollback() 回滚
* db.session.remove() 移除会话
* 多对多
* 创建模型类对象
* stu1 = Student(name='张三')
stu2 = Student(name='李四')
stu3 = Student(name='王五')
* cou1 = Course(name='物理')
cou2 = Course(name='化学')
cou3 = Course(name='生物')
* 关联表添加关联数据
* stu1.courses = [cou2, cou3]
stu2.courses = [cou2]
stu3.courses = [cou1, cou2, cou3]
* 将模型类对象添加进会话
* db.session.add(obj) 添加对象
db.session.add_all([obj1,obj2,..]) 添加多个对象
* db.session.delete(obj) 删除对象
* 提交会话
* db.session.commit() 提交会话
* db.session.rollback() 回滚
* db.session.remove() 移除会话
* app.run()

  • 数据库的迁移

  • 在Flask中可以使用Flask-Migrate扩展,来实现数据迁移。并且集成到Flask-Script中,所有操作通过命令就能完成。

  • 为了导出数据库迁移命令,Flask-Migrate提供了一个MigrateCommand类,可以附加到flask-script的manager对象上。

    • 虚拟环境中安装Flask-Migrate
      • pip install flask-migrate
    • 导入相应的包
      • from flask_sqlalchemy import SQLAlchemy
        from flask_migrate import Migrate,MigrateCommand
        from flask_script import Shell,Manager
    • 创建Flask对象
      • app = Flask(name)
    • 使用flask-script的manager接管app对象
      • manager = Manager(app)
    • 设置app连接数据库的配置信息
    • 创建SQLAlchemy对象,读取app中的配置信息
      • db = SQLAlchemy(app)
    • 使用数据库迁移框架
      • migrate = Migrate(app,db)
        第一个参数是Flask实例,第二个参数是sqlalchemy数据库实例
    • 使用Flask_script中的manager动态运行程序
      • manager是Flask-Script的实例,这条语句在flask-Script中添加一个db命令

manager.add_command('db',MigrateCommand)

数据库迁移

  • 配合flask_migrate
    pip install flask-migrate

  • from flask import Flask

  • from flask_sqlalchemy import SQLAlchemy

  • from flask_migrate import Migrate,MigrateCommand

  • from flask_script import Shell,Manager

  • app = Flask(name)

  • manager = Manager(app)

  • app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:mysql@127.0.0.1:3306/Flask_test'

  • app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True

  • app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True

  • db = SQLAlchemy(app)

  • 第一个参数是Flask的实例,第二个参数是Sqlalchemy数据库实例

  • migrate = Migrate(app,db)

  • manager是Flask-Script的实例,这条语句在flask-Script中添加一个db命令

  • manager.add_command('db',MigrateCommand)

  • 定义模型Role

  • class Role(db.Model):

  • # 定义表名
    
  • __tablename__ = 'roles'
    
  • # 定义列对象
    
  • id = db.Column(db.Integer, primary_key=True)
    
  • name = db.Column(db.String(64), unique=True)
    
  • user = db.relationship('User', backref='role')
    
  • #repr()方法显示一个可读字符串,
    
  • def __repr__(self):
    
  •     return 'Role:'.format(self.name)
    
  • 定义用户

  • class User(db.Model):

  • __talbe__ = 'users'
    
  • id = db.Column(db.Integer, primary_key=True)
    
  • username = db.Column(db.String(64), unique=True, index=True)
    
  • #设置外键
    
  • role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
    
  • def __repr__(self):
    
  •     return 'User:'.format(self.username)
    
  • if name == 'main':

  • manager.run()
    
    • app = Flask(name)

    • manager = Manager(app)

    • db = SQLAlchemy(app)

    • migrate = Migrate(app,db)

    • manager.add_command('db',MigrateCommand)

    • manager.run()

  • 创建迁移仓库

    • 这个命令会创建migrations文件夹,所有迁移文件都放在里面。

python database.py db init

  • 创建迁移脚本
    • 自动创建迁移脚本有两个函数
      upgrade():函数把迁移中的改动应用到数据库中。
      downgrade():函数则将改动删除。
      自动创建的迁移脚本会根据模型定义和数据库当前状态的差异,生成upgrade()和downgrade()函数的内容。
      对比不一定完全正确,有可能会遗漏一些细节,需要进行检查
      python database.py db migrate -m 'initial migration'
  • 更新数据库
    • python database.py db upgrade
  • 返回以前的版本
    • 可以根据history命令找到版本号,然后传给downgrade命令:

python app.py db history

输出格式: -> 版本号 (head), initial migration
* 回滚到指定版本
python app.py db downgrade 版本号

  • 实际操作顺序
    • 实际操作顺序:
      1.python 文件 db init
      2.python 文件 db migrate -m"版本名(注释)"
      3.python 文件 db upgrade 然后观察表结构
      4.根据需求修改模型
      5.python 文件 db migrate -m"新版本名(注释)"
      6.python 文件 db upgrade 然后观察表结构
      7.若返回版本,则利用 python 文件 db history查看版本号
      8.python 文件 db downgrade(upgrade) 版本号

模板

Jinja2模板

1.获取各种变量的值

整数: {{ my_num + 20}}

字符串: {{ my_str + " python" }}

元组: {{ my_tuple }}, 分开获取:{{ my_tuple[0] }}, {{ my_tuple[1] }}

列表: {{ my_list }}, 分开获取:{{ my_list[0] }}, {{ my_list[1] }}

字典: {{ my_dict }},分开获取:{{ my_dict.name }}, {{ my_dict[age] }}

2.遍历元祖中所有的元素

{% for item in my_tuple %}
  • {{ item }}
  • {% endfor %}
    <h2>3.取出列表中所有偶数</h2>
    {% for item in my_list %}
        {% if item %2 == 0 %}
            {{ item }}
        {% endif %}
    {% endfor %}
    
    <h2>4.遍历字典内容</h2>
    {% for key in my_dict %}
        {# 如果直接是mydict.key ,那么这个key是一个字符串, 如果是 mydict[key], 那么key当成变量 #}
        <li>{{ key }} = {{ my_dict[key] }}</li>
    {% endfor %}
    
    • Jinja2模板概述

    • 用来展示数据的html页面,这个过程也通常称为渲染,属于Jinja2的功能 使用模板的好处:

    • 视图函数只负责业务逻辑和数据处理(业务逻辑方面)

    • 而模板则取到视图函数的数据结果进行展示(视图展示方面)

    • 代码结构清晰,耦合度低

    • Jinja2特点

    • Jinja2:是 Python 下一个被广泛应用的模板引擎,是由Python实现的模板语言,他的设计思想来源于 Django 的模板引擎,并扩展了其语法和一系列强大的功能,其是Flask内置的模板语言。

    • 模板语言:是一种被设计来自动生成文档的简单文本格式,在模板语言中,一般都会把一些变量传给模板,替换模板的特定位置上预先定义好的占位变量名。

    • 使用render_template函数封装模板引擎

      • python代码实现
      • 仿照django实现模板语言
      • 过滤器支持链式调用{{
        str|lower|reverse }}
      • 直接在模板中支持运算
    • Jinja2模板语法

      • 获取变量值
        • 整数:{ {number} }

      元祖:{ {tuple[0]} }

      列表:{ { list[0] } }

      字典:{ { dict['key'] } }

      * 分支语句 * { % if 条件 % } 语句1

    { % else % }
    语句2
    { % endif % }
    * for循环
    * {% for 变量 in 容器 %}
    语句
    {% endfor%}
    * for循环中可以访问i的特殊变量
    * loop.index 当前循环迭代的次数(从 1 开始)
    * loop.index0 当前循环迭代的次数(从 0 开始)
    * loop.revindex 到循环结束需要迭代的次数(从 1 开始)
    * loop.revindex0 到循环结束需要迭代的次数(从 0 开始)
    * loop.first 如果是第一次迭代,为 True 。
    * loop.last 如果是最后一次迭代,为 True 。
    * loop.length 序列中的项目数。
    * loop.cycle 在一串序列间期取值的辅助函数。见下面示例程序。
    * 注释
    * {# 注释内容 #}

    • Jinja2模板过滤器
      • Jinja2模板自带的两种过滤器

      • 过滤器的本质就是函数。有时候我们不仅仅只是需要输出变量的值,我们还需要修改变量的显示,甚至格式化、运算等等,而在模板中是不能直接调用 Python 中的某些方法,那么这就用到了过滤器。

        • 字符串过滤器
    • 使用格式:{{ 字符串 | 字符串过滤器 }}
      * safe:禁用转义

    {{ 'hello' | safe }}

    * capitalize:把变量值的首字母转成大写,其余字母转小写

    {{ 'hello' | capitalize }}

    * lower:把值转成小写

    {{ 'HELLO' | lower }}

    * upper:把值转成大写

    {{ 'hello' | upper }}

    * title:把值中的每个单词的首字母都转成大写

    {{ 'hello' | title }}

    * reverse:字符串反转

    {{ 'olleh' | reverse }}

    * format:格式化输出

    {{ '%s is %d' | format('name',17) }}

    * striptags:渲染之前把值中所有的HTML标签都删掉

    {{ 'hello' | striptags }}

    * 列表过滤器 * 使用格式:{{ 列表 | 列表过滤器 }} * first:取第一个元素

    {{ [1,2,3,4,5,6] | first }}

    * last:取最后一个元素

    {{ [1,2,3,4,5,6] | last }}

    * length:获取列表长度

    {{ [1,2,3,4,5,6] | length }}

    * sum:列表求和

    {{ [1,2,3,4,5,6] | sum }}

    * sort:列表排序

    {{ [6,2,3,1,5,4] | sort }}

    * 使用过滤器的其他操作语句 * 语句块操作 {% filter upper %} #一大堆文字# {% endfilter %} * 链式调用 {{ "hello world" | reverse | upper }} * Jinja2自定义过滤器的两种方式 * 先定义函数(一个形参接受原值) * 添加到过滤器列表,app.add_template_filter('函数名',‘过滤器名称’) * > def do_listreverse(li): * > # 通过原列表创建一个新列表 * > temp_li = list(li) * > # 将新列表进行返转 * > temp_li.reverse() * > return temp_li * > * > app.add_template_filter(do_listreverse,'lireverse')
        * 定义函数,直接使用过滤器列表装饰@app.teemplate_filter('过滤器名称')
         * > @app.template_filter('lireverse')
         * > def do_listreverse(li):
         * >   # 通过原列表创建一个新列表
         * >   temp_li = list(li)
         * >   # 将新列表进行返转
         * >   temp_li.reverse()
         * >   return temp_li
    
    • 模板代码的复用

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

    • 多个模板具有完全相同的顶部和底部内容

    • 多个模板中具有相同的模板代码内容,但是内容中部分值不一样

    • 多个模板中具有完全相同的 html 代码块内容

    • 像遇到这种情况,可以使用 JinJa2 模板中的 宏、继承、包含来进行实现

      • 宏是Jinja2中的函数,调用后,直接返回一个模板,或者字符串

      • 当模板中出现大量重复功能代码的时候,可以使用宏来进行封装

        • 定义宏
          可以在当前文件,也可以在其他文件

        • {% macro input(name,value='',type='text') %}

        • <input type="{{type}}" name="{{name}}" value="{{value}}">
          
        • {% endmacro %}

          • 使用当前文件的宏

          • {{ input('name' value='zs')}}

          • 使用其他文件的宏

          • {%import 'filename.html' as 别名%}

          • {%别名.函数名(参数)%}
            
      • 继承

      • 将公共的内容抽取到父类模板,共子类使用的形式称为继承.

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

        • 父模板

        • 父模板中使用多个block组成

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

    {% block content %}
    正文内容
    {% endblock content %}

    {% block bottom %}
    底部
    {% endblock bottom %}
    * 子模版
    * 继承后,子类完全拥有父类内容,并且子类可以进行重写,如果写保留父类内容使用: super()

            * {% extends 'base.html' %}
    

    {% block content %}
    需要填充的内容
    {% endblock content %}
    * 模板继承的注意点
    * 不支持多继承
    为了便于阅读,在子模板中使用extends时,尽量写在模板的第一行。
    不能在一个模板文件中定义多个相同名字的block标签。
    定义block模板的时候,一定要加上endblock结束标记
    * 包含
    * > Jinja2模板中,除了宏和继承,还支持一种代码重用的功能,叫包含(Include)。它的功能是将另一个模板整个加载到当前模板中,并直接渲染。

        * 格式:
    

    {% include 'hello.html' %}
    或者
    {% include 'hello.html' ignore missing %}
    提示: ignore missing 加上后如果文件不存在,不会报错
    * 总结
    * 宏(Macro)、继承(Block)、包含(include)均能实现代码的复用。
    继承(Block)的本质是代码替换,一般用来实现多个页面中重复不变的区域。
    宏(Macro)的功能类似函数,可以传入参数,需要定义、调用。
    包含(include)是直接将目标模板文件整个渲染出来。

    • 模板特有的变量和函数

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

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

    {{config.DEBUG}}
    输出:True
    * request
    * 就是flask中代表当前请求的request对象:

    {{request.url}}
    输出:http://127.0.0.1
    * g变量
    * 在视图函数中设置g变量的 name 属性的值,然后在模板中直接可以取出

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

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

    {{ url_for('post', post_id=1)}}
    /post/1
    * get_flashed_messages()
    * 这个函数会返回之前在flask中通过flask()传入的消息的列表,flash函数的作用很简单,可以把由Python字符串表示的消息加入一个消息队列中,再使用get_flashed_message()函数取出它们并消费掉:

    {%for message in get_flashed_messages()%}
    {{message}}
    {%endfor%}

    • Flask_WTF表单
      • 使用代码定义表单
      • 在模板中进行表单渲染
      • 提供一系列的验证器,对表单提交的数据进行验证
      • 流程
        • 定义类继承子FlaskForm
        • 在类中编写字段内容,验证函数
        • 创建,渲染到页面
    • csrf
      • flask_wtf模块提供了csrf攻击的保护
      • 使用流程:

    from flask_wtf.csrf import CSRFProtect
    CSRFProtect(app)
    * CSRFProtect(app)保护原理:
    * > 对应用程序app中的post,put,dispatch,delete, 4种类型的请求做保护,因为这些类型的请求是用于更改服务器的资源
    * > 当以上面4种类型的请求,操作服务器资源的时候,会校验cookie中的csrf_token, 表单中的csrf_token信息
    * > 只有上面二者的值相等的时候,那么校验则通过,可以操作服务器资源

    * csrf_token值的生成需要加密, 所以设置SECRET_KEY
    * 前后端代码
        * 后端代码
         * > from flask_wtf import CSRFProtect
         * > app.config["SECRET_KEY"] = "fjkdjfkdfjdk"
         * > CSRFProtect(app)
    
        * 前端代码
         * > {#设置隐藏的csrf_token,使用了CSRFProtect保护app之后,即可使用csrf_token()方法#}
         * >     <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
    

    render_template()函数

    from flask import Flask,render_template
    app = Flask(name) #默认省略了三个参数,static_url_path, static_folder, template_folders

    @app.route('/')
    def hello_world():
    #定义数据,整数,字符串,元祖,列表,字典
    num = 10
    str = "hello"
    tuple = (1,2,3,4)
    list = [5,6,7,8]
    dict = {
    "name":"张三",
    "age":13
    }

    return render_template('file01.html',my_num=num,my_str=str,my_tuple=tuple,my_list=list,my_dict=dict)
    

    if name == 'main':
    app.run(debug=True)

    • 将数据传递给模板
      return render_template('模板文件名',key=value)

    XMind: ZEN - Trial Version

    原文地址:https://www.cnblogs.com/serpent/p/9614628.html