Flask框架(下)
一、Flask-Script扩展命令使用
安装
pip install Flask-Script
导入Manager
from flask_script import Manager
创建Manager对象
manager = Manger(app)
使用manager对象的run()方法(而不使用app.run())
manager.run()
接下来就可以像django的manage.py一样,使用命令来操作
1、查看有哪些命令
python xxx.py --help
2、常用命令
启动服务器
python xx.py runserver
进入该环境(模块)下命令模式
python xx.py shell
注:flask2.0版本会报错
处理方法:在flask_script/__init__.py文件里面把from ._compat import text_type 改成 from flask_script._compat import text_type
二、模板(Jinja2模板)
1、Flask模板渲染函数
from flask import render_template
- render_template("模板文件",参数1= 值1, 参数2 = 值2, ...)方法
- return render_template("index.html", name = "fy", age = 18)
data = {参数1:值1, 参数2:值2, ...}
#通过拆包的方式也能使用字典
render_template("模板文件",**data)方法
2、模板变量的基本使用(与django模板变量使用类似)
3、过滤器
一、使用语法
{{模板变量或字符串 | 过滤器}}
{{模板变量或字符串 | 过滤器1 | 过滤器2 |...}}
从左到右执行过滤器二、字符串过滤器
safe:禁用转义
{{"<em>hello</em>" | safe}}
capitalize:把变量值的首字母转换成大写,其余字母转小写
lower:把值转换成小写
upper:把值转换成大写
title:把值中的每个单词的首字母都转成大写
trim:把值的首尾空格去掉
reverse:字符串反转
format:格式化输出
<p>{{"<%s is %d> | format("name", 17)"}}
striptags:渲染之前把值中所有的HTML标签都删掉
三、列表过滤器
first:取第一个元素
last:取最后一个元素
length:获取列表长度
sum:列表求和
sort:列表排序
四、自定义过滤器
方式一:
通过app.add_template_filter(过滤器函数, 模板中使用的过滤器名字)方法
例:
def filter_double_sort(ls):
return ls[::2]
app.add_template_filter(filter_double_sort, "double_2")
方式二:
通过装饰器app.template_filter(模板中使用的装饰器名字)
例:
@app.template_filter("double_3")
def filter_double_sort(ls):
return ls[::-3]
过滤器原理:模板中把参数值传递给过滤器函数,函数的返回值就是过滤后该参数的值
4、Flask-WTF表单扩展
帮助进行csrf验证,帮助我们快速定义表单模板,而且帮助我们在视图中验证表单数据
安装:pip install Flask-WTF
使用步骤:
1、通过类抽象出表单
from flask_wtf import FlaskForm #定义表单模型类 class RegisterForm(FlaskForm): """自定义注册表单模型类""" pass
2、类属性定义表单的字段
from flask_wtf import FlaskForm from wtfroms import StringField, PasswordField, SubmitField #导入表单字段-flask_wtf依赖包 from wtforms.validators import DataRequird, EqualTo #导入验证器-flask_wtf依赖包 #定义表单模型类 class RegisterForm(FlaskForm): """自定义注册表单模型类""" # label说明标签 validators验证器,可以有多个。 user_name = StringField(label="用户名", validators=[DataRequired("用户名不能为空")]) password = PasswordField(label="用户名", validators=[DataRequired("密码不能为空")]) rpassword = PasswordField(label="用户名", validators=[DataRequired("确认密码不能为空")],EqualTo("password", "两次密码不一致")) submit = SubmitField(label = "提交")
wtf支持的表单字段
wtf常用验证函数
验证失败提示错误,在模板中使用对应字段errors属性
3、定义视图函数,创建表单对象
@app.route("/register", methods=["GET", "POST"]) def register(): #创建表单对象,如果是post请求,前端发送的数据,flask会把数据在构造from对象的时候,存放到对象中 form = RegisterForm() #validate_on_submit()判断数据是否合理,如果from中的数据完全满足验证器返回真,否则返回假 if form.validate_on_submit(): #表示验证合格 #提取数据 uname = form.user_name.data pwd = form.user_name.data print("账号:%s\n密码:%s"%(uname, pwd)) return "注册成功" return render_template("register.html", form=form)
4、创建表单模板
<form method="post"> {{form.csrf_token}} {{form.user_name.label}} <p>{{form.user_name}} {%for error in form.user_name.errors%} <p>{{error}}</p> {%endfor%} {{form.password.label}} <p>{{form.password}} {%for error in form.user_name.errors%} <p>{{error}}</p> {%endfor%} {{form.rpassword.label}} <p>{{form.rpassword}} {%for error in form.user_name.errors%} <p>{{error}}</p> {%endfor%} {{form.submit}} </form>
注:
防止csrf攻击,需要添加{{form.csrf_token}}
防止CSRF攻击:配置SECRET_KEY
app.config["SECRET_KEY"] = "adadsdf12e5trtytyr"
5、控制/循环语句
和django里面类似
{%for item in samples%}{%endfor%}
{%if %}{%endif%}
6、宏的定义与使用
什么是宏?:相当于函数,提高代码复用
1、不带参数宏
定义
{%macro 宏名()%}
...
{%endmacro%}
{%macro input()%} <input type="text", name="username", value="", size="30"> {%endmacro%}
使用
{{ 宏名() }}
{{input()}}
2、带参数的宏
定义
{%macro 宏名(参数1,参数2,...)%}
...
{%endmacro%}
{%macro input(type, value, size)%} <input type="{{type}}", name="{{value}}", value="", size="{{size}}"> {%endmacro%}
带默认的值的宏
{%macro input(type="text", value="", size="30")%}
<input type="{{type}}", name="{{value}}", value="", size="{{size}}">
{%endmacro%}
使用
{{ 宏名(参数1,参数2,...) }}
{{input("password", "", "50")}}
3、宏定义在外部使用
(1)、创建一个HTML文件,结构可以全删掉,只写宏
xx.html
(2)、模板文件中导入xx.html
{%import "xx.html" as func%}
(3)、调用(使用)宏
{{func.input()}}
7、模板的继承
1、块定义
{%block 块名%}
块
{%endblock 块名%}
2、继承
{%extends "父模板文件名"%}例:
父模板:base.html{%block top%} 顶部菜单 {%endblock top%}
子模板:child.html
{%extends "base.html"%} {%block top%} 需要填充内容 {%endblock top%}
注:
不支持多继承
便于阅读extends尽量写在第一行
不能在一个模板文件中定义多个相同名字的block标签
页面中使用多个block标签时,建议给结束标签起名字,便于阅读
8、包含
作用:提高代码重用,将另一个模板整个加载到当前模板中,并直接渲染
使用:{\%include "xx.html"%}
模板文件不存在会抛出异常,加上ignore missing关键字,如果模板文件不存在,会忽略这条include语句
例:{\%include "xx.html" ignore missing%}
9、Flask中特殊变量和方法
实现闪现信息(第一次进入可见)
三、数据库操作
SQLAlchemy是一个关系型数据库框架,flask-sqlalchemy是一个简化了SQLAlchenmy操作的flask扩展(与django操作模型类类似)
数据库操作示意图:
安装:
pip install flask-sqlalchemy
需要连接数据库,还需要安装
pip install pymysql
启动服务器前执行
pymysql.install_as_mysqldb()
Flask数据库配置 :
SQLALCHEMY_DATABASE_URI:配置连接数据库相关参数
SQLALCHEMY_DATABASE_URI = "mysql://root:[email protected]:3306/flask_db"
SQLALCHEMY_COMMIT_ON_TEARDOWN:每次请求结束自动提交数据库中改动,一般不使用自动提交这个参数,默认不开启
SQLALCHEMY_TRACK_MODIFICATIONS:如果数据库表数据修改了那么模型类数据也应该修改,开启后,自动跟踪,同步修改。这个参数需要开启
SQLALCHEMY_ECHO:查询时会显示SQL语句,用于调式使用
SQLAlchemy工具使用 :
一、导入SQLAlchemy
from flask_sqlalchemy import SQLAlchemy二、创建SQLalchemy工具对象
db = SQLAlchemy(app)三、创建模型类
表名命令规范
数据库名缩写_表名:#ihome -> ih_user
tbl_表名:#tbl_userclass Role(db.Model): """用户角色/身份表""" __tablename__ = "tbl_roles" #指明数据库表名 id = db.Column(db.Integer, primary_key = True) name_id = db.Column(db.String,unique=True) #数据库中不存在的字段,用于查询关联的数据(模型类层面考虑),backref作用:User模型类里面,通过role属性可以拿到关联的模型类 users = db.relationship("User", backref="role") class User(db.Model): """用户表""" __tablename__ = "tbl_users" #指明数据库表名 #定义数据表字段及字段类型约束 id = db.Column(db.Integer, primary_key = True)#定义整型的主键,会默认设置为自增主键 name = db.Column(db.string(64), unique=True) email = db.Column(db.string(128), unique=True) password = db.Column(db.string(128)) role_id = db.Column(db.Integer, db.ForeignKey("tbl_roles.id") )#注:与关联字段的类型保持一致(数据库底层考虑)
模型类实现__repr__(self)方法 :改变print函数输出,打印时使这个对象看起来更加方便
四、创建数据表
通过db对象(db = SQLAlchemy(app))
if __name__ == "__main__": #清除数据库里面所有数据(谨慎使用,一般第一次执行时使用,清除一些垃圾数据) db.drop_all() #创建所有数据表 db.create_all()
五、数据的增删改查
1、添加数据
添加一条#创建对象 role1 = Role(name="admin") #session记录对象任务 db.session.add(role1) #提交任务到数据库中 db.session.commit()
添加多条
#创建对象 role1 = Role(name="admin") user1 = User(name="枫叶1", email="[email protected]", password="123456", role_id =role1.id ) user1 = User(name="枫叶2", email="[email protected]", password="123456", role_id =role1.id ) user1 = User(name="枫叶3", email="[email protected]", password="123456", role_id =role1.id ) #session记录对象任务 db.session.add_all([user1, user2, user3]) #提交任务到数据库中 db.session.commit()
2、查询数据
(1)取所有数据#类名.query.all(),返回所有数据对象的列表 li = Role.query.all() r = li[0] #取第一条数据对象 print(r.name)
(2)取第一条数据
#类名.query.first() Role.query.first #类名.query.get(主键的值) Role.query.get(2)
(3)过滤器
过滤器使用:类名.query.过滤器(过滤条件).all()
filter():#把过滤器添加到原查询上,返回一个新的查询 User.query.filter(User.name=="wang", User.role_id==1).first() #模糊查询 #查询以163.com结尾的数据 User.query.filter(User.email.endwith(163.com)).all()
filter_by():
把等值过滤器添加到原查询上,返回一个新查询User.query.filter_by(name="wang", role_id=1).first()
limit():
使用指定值限定原查询返回的结果
#取查询结果的前两条 User.query.offset(1).limit(2).all()
offset():
偏移原查询返回的结果(相当于跳过前几条数据),返回一个新查询
#从第三个数据开始查询(包括第三) li = User.query.offset(2).all()
order_by():
根据指定条件对原查询结果进行排序,返回一个新查询
#把查询结果倒序排序 #方式一 User.query.order_by(User.id.desc()).all()#推荐使用 #方式二 User.query.order_by("-id").all() #把查询结果正序排序 User.query.order_by(User.id.asc()).all()#推荐使用
group_by():
根据指定条件对原查询结果进行分组,返回一个新查询
需要使用db.session.query查询#把User表根据role_id进行分组 #分组后的数据 User.role_id 和统计分组个数 func.count(User.role_id) from sqlalchemy import func db.session.query(User.role_id, func.count(User.role_id)).group_by(User.role_id).all() #返回结果列表元组, [(1, 2), (2, 2)]
or_()函数:
from sqlalchemy import or_
#各条件之间或的关系
类名.query.过滤器(or_(条件1, 条件2,...)).all()关联查询:使用db.relationship()这个定义的类属性
ro = Role.query.get(1) user_liset = ro.users #获取Role对象关联的User对象,一个列表 user = User.query.get(1) role = user.role #获取User对象关联的Role对象
3、修改数据
方式一:
(1)获取到数据对象
user = User.query.get(1)
(2)修改对象
user.name = "风信子"
(3)重新保存该对象
db.session.add(user)
db.session.commit()方式二:
查询集的updata()方法,接收一个字典{"字段名":"值",...}User.query.filter_by(name = "枫叶").update("name":"风信子", "email":"[email protected]") db.session.commit()
4、删除数据
db.session的delete()方法user = User.query.get(1) db.session.delete(user) db.session.commit()
数据库migrate扩展命令简介:
作用:向Django一样使用命令的形式实现数据迁移
使用步骤:
(1)安装flask-migrate模块
pip install flask-migrate
注:依赖flask-script包
(2)导入
from flask_migrate import Migrate, MigrateCommand
from flask_script import Shell,Manager
(3)创建migrate对象
manager = Manager(app)
db = SQLAlchemy(app)
migrate = Migrate(app, db) #可以不用变量接受
manager.add_command("db", MigrateCommand)命令使用:
创建迁移仓库
python xx.py db init
这个命令会创建migrations文件夹,所有迁移文件都放在里面
创建迁移脚本
python xx.py db migrate -m "initial migration"
更新数据库
python xx.py db upgrade
查看历史版本的状态码
python xx.py db history
每次更新数据库都有个状态码(随机字符串),可以用于回退
回退到某个历史版本
python xx.py db downgrade 状态码
四、Flask-Mail
Flask扩展包,用于在Flask程序中发送邮件
使用步骤:
(1)安装flask-mail模块pip install flask-mail
(2)导入Mail, Message
form flask_mail import Mail, Message
(3)配置应用
app.config.update(
MAIL_SERVER = "smtp.qq.com",
MAIL_PORT = 456,
MAIL_USE_TLS = True,
MAIL_USERNAME = "[email protected]",
MAIL_PASSWORD = "153856fsw",
)
注:密码可能不是真实的密码,而是授权码(4)创建mail对象
mail = Mail(app)
(5)视图函数中发送邮件
#sender发送方, recipients接收方列表
msg = Message("this is a test", sender="[email protected]", recipients=[])
#邮件内容
msg.body = "Flask test mail"
#发送邮件
mail.send(msg)
五、功能模块的划分
(1)不使用蓝图
(1)使用循环导入:A模块导入B,B模块导入A
模块开头采用form xx import xx形式导入会锁死,报错;解决办法,一方让步。在函数里面导入
(2)使用装饰器:app.route("/xx")(func)例:
#goods.py def g_func(): from main import request print(request.url) return " goods index page"
#main.py from flask import Flask,request from goods import g_func app = Flask(__name__) app.route("/goods_index")(g_func) if __name__ == "__main__": print(app.url_map) app.run()
(2)使用蓝图Blueprint
蓝图使用步骤
1、创建蓝图对象
form flask import Blueprint
#Blueprint("蓝图名称", 蓝图所在模块)
admin = Blueprint("admin", __name__)
2、使用蓝图对象路由
@admin.route("/")
def admin_index():
return "admin_index_page"
3、程序实例中注册蓝图
#register_blueprint(蓝图对象, url_fix="访问蓝图路由前缀")
app.register_blueprint(admin, url_fix="/admin")
例:
#good.py from flask import Blueprint goods = Blueprint("goods", __name__) @goods.route("/index") def goods_index(): return "blueprint goods index page"
#main.py from flask import Flask, Blueprint from goods import goods app = Flask(__name__) app.register_blueprint(goods, url_prefix="/goods") if __name__ == "__main__": print(app.url_map) app.run()
(3)以目录形式定义蓝图
作用:实现单个应用的视图、模板、静态文件的集合
定义步骤
1、创建一个单个应用的包
包结构
--static文件夹
--templates文件夹
-- __init__.py
(1)可以把包理解成一个类,__init__.py文件就是初始化方法,导入包后就可以导入__init__.py文件里面初始化出的对象
(2)__init__.py文件内容
#在文件夹里面的__init__.py文件里面定义蓝图
from flask import Blueprint
#创建一个蓝图static_folder-指定蓝图静态文件目录,templates_folder-指定静态文件目录
#__main__作用:指定模块所在目录为根目录,蓝图没有默认静态文件目录,静态文件目录
app_cart = Blueprint("app_cart", __name__, static_folder="static", template_folder="templates")
#在__init__.py文件被执行的时候,把视图加载进来,让蓝图与应用程序知道有视图的存在
from .views import get_cart
--views.py
from . import app_cart
@app_cart.route("/get_cart")
def get_cart():
return "get cart page"
2、程序实例中注册蓝图
form cart import app_cart
#register_blueprint(蓝图对象, url_fix="访问蓝图路由前缀")
app.register_blueprint(app_cart, url_fix="/cart")
注:模板文件查找顺序,app对象模板文件目录 》 蓝图对应模板文件目录
六、单元测试
概念:记录测试的过程,把测试的逻辑写成一段代码,便于修改代码后测试是否修改正确
断言:assert的使用
后面接一个表达式,如果表达式返回真,则断言成功,程序能够继续往下执行,如果表达式为假,则断言失败,抛出一个异常AssertionError,终止程序继续往下执行
例:断言一个数是整数
assert isinstance(num, int)
单元测试的使用:
(1)创建一个.py文件一般命名为test用于写测试代码
(2)写测试类,继承unittest.TestCase
import unittest class TestClass(unittest.TestCase): #该方法会首先执行,相当于测试前的准备工作 def setUp(self): pass #该方法会测试代码完成后执行,相当于测试后的扫尾工作 def tearDown(self): pass #测试代码 def test_xx(): pass
unittest.TestCase类中自带测试的方法(不必使用断言)
注:写测试代码的方法,必须以test_开头,运行后python会自动帮我们执行测试代码
七、部署
概念区分:
WSGI
是一种规范,web服务器和web应用程序之间的接口
uwsgi
是一种传输协议,用于定义传输信息的类型
uWSGI
实现了uwsgi协议WSGI服务器
部署方式:
nginx + gunicorn + flask
采用Gunicorn做wsgi容器,来部署flask程序
图示: