flask没有django中的中间件,可是却有相似的机制(在请求以前作点事,请求完成以后再作点事)。flask给咱们预留的钩子能完成这些事。对于钩子的简单理解:flask预留了一些占位的空白空间,当咱们往这段空间放代码的时候,那么流程在走的时候就会通过咱们的代码。钩子的形象意义就是一段代码执行的时候,会顺带着执行钩子上的一系列代码,而不是单纯的那一段代码。html
before_first_request:在处理第一个请求前运行。 before_request:在每次请求前运行。 after_request(response):若是没有未处理的异常抛出,在每次请求后运行。 teardown_request(response):在每次请求后运行,即便有未处理的异常抛出,在这个钩子里并不能捕获异常进行处理,并且一旦发生异常页面总会是定制的错误页,而不是这个函数的返回值。须要运行在debug=False的状况才生效,并且teardown_request是运行在after_request以后的。
before系列的钩子若是有return,那么视图函数就不会被执行。after系列钩子必须有return,这是最终返回给浏览器的内容前端
from flask import Flask, request app = Flask(__name__) @app.route('/index') def index(): print('执行视图函数') return 'index' @app.route('/login') def login(): print('执行视图函数') return 'login' @app.before_first_request def hanlde_before_first_request(): print('before_first_request') @app.before_request def handel_before_request(): print('before_request') @app.after_request def handle_after_request(response): print('handle_after_request') if request.path == '/login': print('login') if request.path == '/index': print('index') return response @app.teardown_request def handle_teardown_request(response): print('teardown_request') return 'hahah' if __name__ == '__main__': app.run()
有了钩子,一次完成的http请求的后台处理就不单纯是视图函数了,一次完整的请求流程还包括钩子函数html5
安装:pip3 install Flask-Script
python
from flask import Flask, request from flask_script import Manager app = Flask(__name__) # 建立一个管理者用来管理app manager = Manager(app) @app.route('/index') def index(): print('执行视图函数') return 'index' if __name__ == '__main__': manager.run()
执行脚本python flask_test.py
的结果为mysql
usage: flask_test.py [-?] {shell,runserver} ... positional arguments: {shell,runserver} shell Runs a Python shell inside Flask application context. runserver Runs the Flask development server i.e. app.run() optional arguments: -?, --help show this help message and exit
因此用了flask_script,原先的脚本就不是单纯可直接执行的脚本了,而是须要接受命令并按照命令去执行flask程序。Manager默认只提供了两条命令:shell 和 runserver,咱们能够对其进行扩展。nginx
在开发过程当中,须要修改数据库模型,并且还要在修改以后更新数据库。最直接的方式就是删除旧表,但这样会丢失数据。更好的解决办法是使用数据库迁移框架,它能够追踪数据库模式的变化,而后把变更应用到数据库中。
在Flask中可使用Flask-Migrate扩展,来实现数据迁移。而且集成到Flask-Script中,全部操做经过命令就能完成。为了导出数据库迁移命令,Flask-Migrate提供了一个MigrateCommand类,能够附加到flask-script的manager对象上。
安装Flask-Migrate:pip3 install flask-migrate
web
from flask_script import Manager from flask import Flask from flask_migrate import Migrate,MigrateCommand from flask_sqlalchemy import SQLAlchemy app = Flask(__name__) manager = Manager(app) app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:mysql@127.0.0.1:3306/test' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True db = SQLAlchemy(app) # 绑定数据库和app Migrate(app, db) # 增长一条管理命令 manager.add_command('db', MigrateCommand) @app.route('/index') def index(): return 'index' #定义模型Role class Role(db.Model): # 定义表名 __tablename__ = 'roles' # 定义列对象 id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(64), unique=True) def __repr__(self): return 'Role:'.format(self.name) #定义用户 class User(db.Model): __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(64), unique=True, index=True) def __repr__(self): return 'User:'.format(self.username) if __name__ == '__main__': manager.run()
如今愈来愈多都是先后端分离,日常使用模板方式进行开发的场景愈来愈少。基本了解便可。redis
flask_test.pysql
from flask import Flask,render_template app = Flask(__name__) @app.route('/') def index(): mydict = {'key':'silence is gold'} mylist = ['Speech', 'is','silver'] myintvar = 0 return render_template('vars.html', mydict=mydict, mylist=mylist, myintvar=myintvar ) if __name__ == '__main__': app.run(debug=True)
vars.htmlshell
<p>{{mydict['key']}}</p> <p>{{mydict.key}}</p> <p>{{mylist[1]}}</p> <p>{{mylist[myvariable]}}</p>
safe:禁用转义; <p>{{ '<em>hello</em>' | safe }}</p> capitalize:把变量值的首字母转成大写,其他字母转小写; <p>{{ 'hello' | capitalize }}</p> lower:把值转成小写; <p>{{ 'HELLO' | lower }}</p> upper:把值转成大写; <p>{{ 'hello' | upper }}</p> title:把值中的每一个单词的首字母都转成大写; <p>{{ 'hello' | title }}</p> trim:把值的首尾空格去掉; <p>{{ ' hello world ' | trim }}</p> reverse:字符串反转; <p>{{ 'olleh' | reverse }}</p> format:格式化输出; <p>{{ '%s is %d' | format('name',17) }}</p> striptags:渲染以前把值中全部的HTML标签都删掉; <p>{{ '<em>hello</em>' | striptags }}</p>
对于xss攻击在后端也能够作:v = Markup("<input type='text' />")
<p>{{ “ hello world “ | trim | upper }}</p>
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>
@app.template_filter() def add(x, y, z): return x + y + z @app.template_global() def sub(x, y): return x - y
调用方式是{{ 1|add(2,3)}}
和 {{ sub(3,2) }}
。和django不一样,flask的filter能够接受不止两个参数。template_filter和template_global后面都须要加括号
macro.html
{% macro input(name,value='',type='text',size=20) %} <input type="{{ type }}" name="{{ name }}" value="{{ value }}" size="{{ size }}"/> {% endmacro %}
在其它模板文件中先导入,再调用
{% import 'macro.html' as func %} {% func.input('name', 10) %}
request和url_for能够在前端模版直接使用,至关于全局变量
使用Flask-WTF表单扩展,能够帮助进行CSRF验证,帮助咱们快速定义表单模板,并且能够帮助咱们在视图中验证表的数据。说白了,其角色就至关因而django的forms表单验证。
flask_test.py
from flask import Flask, render_template, request, redirect from wtforms import Form from wtforms.fields import core from wtforms.fields import html5 from wtforms.fields import simple from wtforms import validators from wtforms import widgets app = Flask(__name__, template_folder='templates') app.debug = True class RegisterForm(Form): name = simple.StringField( label='用户名', validators=[ validators.DataRequired() ], widget=widgets.TextInput(), render_kw={'class': 'form-control'}, default='alex' ) pwd = simple.PasswordField( label='密码', validators=[ validators.DataRequired(message='密码不能为空.') ], widget=widgets.PasswordInput(), render_kw={'class': 'form-control'} ) pwd_confirm = simple.PasswordField( label='重复密码', validators=[ validators.DataRequired(message='重复密码不能为空.'), validators.EqualTo('pwd', message="两次密码输入不一致") ], widget=widgets.PasswordInput(), render_kw={'class': 'form-control'} ) email = html5.EmailField( label='邮箱', validators=[ validators.DataRequired(message='邮箱不能为空.'), validators.Email(message='邮箱格式错误') ], widget=widgets.TextInput(input_type='email'), render_kw={'class': 'form-control'} ) gender = core.RadioField( label='性别', choices=( (1, '男'), (2, '女'), ), coerce=int ) city = core.SelectField( label='城市', choices=( ('bj', '北京'), ('sh', '上海'), ) ) hobby = core.SelectMultipleField( label='爱好', choices=( (1, '篮球'), (2, '足球'), ), coerce=int ) favor = core.SelectMultipleField( label='喜爱', choices=( (1, '篮球'), (2, '足球'), ), widget=widgets.ListWidget(prefix_label=False), option_widget=widgets.CheckboxInput(), coerce=int, default=[1, 2] ) def __init__(self, *args, **kwargs): super(RegisterForm, self).__init__(*args, **kwargs) self.favor.choices = ((1, '篮球'), (2, '足球'), (3, '羽毛球')) def validate_pwd_confirm(self, field): """ 自定义pwd_confirm字段规则,例:与pwd字段是否一致 :param field: :return: """ # 最开始初始化时,self.data中已经有全部的值 if field.data != self.data['pwd']: # raise validators.ValidationError("密码不一致") # 继续后续验证 raise validators.StopValidation("密码不一致") # 再也不继续后续验证 @app.route('/register', methods=['GET', 'POST']) def register(): if request.method == 'GET': form = RegisterForm(data={'gender': 1}) return render_template('register.html', form=form) else: form = RegisterForm(formdata=request.form) if form.validate(): print('用户提交数据经过格式验证,提交的值为:', form.data) else: print(form.errors) return render_template('register.html', form=form) if __name__ == '__main__': app.run()
register.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>用户注册</h1> <form method="post" novalidate style="padding:0 50px"> {% for item in form %} <p>{{item.label}}: {{item}} {{item.errors[0] }}</p> {% endfor %} <input type="submit" value="提交"> </form> </body> </html>
在没有蓝图以前,咱们考虑一个问题:django的目录都是有必定规范的,flask的代码都在一个py文件里面,这确定不能用于生产。因此,咱们的想法是把一个py文件的代码分开,有些代码写到别的py文件,这样看起来也更加清晰。最直白的就是,咱们把处理不一样业务逻辑的视图放到不一样的py文件中,好比处理订单相关的放到一个文件,处理用户相关的放到另一个文件等等。那么咱们能够这样作
flask_test.py
from flask import Flask # 引入order from order import order app = Flask(__name__) @app.route('/index') def index(): return 'index' if __name__ == '__main__': print(app.url_map) app.run()
order.py
from flask_test import app @app.route('/order') def order(): return 'order' print('in order')
运行flask_test.py出错:
Traceback (most recent call last): File "C:/Users/Administrator/Desktop/flask_test/flask_test.py", line 4, in <module> from order import order File "C:\Users\Administrator\Desktop\flask_test\order.py", line 4, in <module> from flask_test import app File "C:\Users\Administrator\Desktop\flask_test\flask_test.py", line 4, in <module> from order import order ImportError: cannot import name 'order'
解决办法之一是延迟导入
解决办法之二是在order.py里面只定义函数,在flask_test.py对函数绑定路由
解决办法之三就是蓝图,也是推荐的方式。
在flask应用程序中国,app就相似于django的工程,蓝图就相似于django的app。
蓝图是保存了一组未来能够在应用对象上执行的操做。注册路由就是一种操做,当在程序实例上调用route装饰器注册路由时,这个操做将修改对象的url_map路由映射列表。当咱们在蓝图对象上调用route装饰器注册路由时,它只是在内部的一个延迟操做记录列表defered_functions中添加了一个项。当执行应用对象的 register_blueprint() 方法时,应用对象从蓝图对象的 defered_functions 列表中取出每一项,即调用应用对象的 add_url_rule() 方法,这将会修改程序实例的路由映射列表。
flask_test.py
from flask import Flask from order import order app = Flask(__name__) app.register_blueprint(order, url_prefix='/order') @app.route('/index') def index(): return 'index' if __name__ == '__main__': print(app.url_map) app.run()
order.py
from flask import Blueprint # 'order' 是蓝图的名字,体如今url_map上,静态目录和模版目录都须要手动指定 # 和app不一样,蓝图的模版和静态目录都没有默认值,,找模版文件先从app目录找,随后到蓝图目录找 order = Blueprint('order', __name__, template_folder='templates', static_folder='static') @order.route('/get_order') def get_order(): return 'get order'
在Web开发过程当中,单元测试实际上就是一些“断言”(assert)代码。
断言就是判断一个函数或对象的一个方法所产生的结果是否符合你指望的那个结果。 python中assert断言是声明布尔值为真的断定,若是表达式为假会发生异常。单元测试中,通常使用assert来断言结果。
经常使用的断言方法
assertEqual 若是两个值相等,则pass assertNotEqual 若是两个值不相等,则pass assertTrue 判断bool值为True,则pass assertFalse 判断bool值为False,则pass assertIsNone 不存在,则pass assertIsNotNone 存在,则pass
import unittest class TestClass(unittest.TestCase): #该方法会首先执行,至关于作测试前的准备工做 def setUp(self): pass #该方法会在测试代码执行完后执行,至关于作测试后的扫尾工做 def tearDown(self): pass #测试代码,必须以test开头 def test_xxx(self): pass
# coding:utf-8 import unittest from login import app import json class TestLogin(unittest.TestCase): """定义测试案例""" def setUp(self): """在执行具体的测试方法前,先被调用""" # 可使用python的http标准客户端进行测试 # urllib urllib2 requests # 开启测试模式,这样flask程序报的错就会彻底在测试程序显示出来,而不是显示测试程序自己的错误 app.config['TESTING'] = True # 使用flask提供的测试客户端进行测试 self.client = app.test_client() def test_empty_name_password(self): """测试模拟场景,用户名或密码不完整""" # 使用客户端向后端发送post请求, data指明发送的数据,会返回一个响应对象 response = self.client.post("/login", data={}) # respoonse.data是响应体数据 resp_json = response.data # 按照json解析 resp_dict = json.loads(resp_json) # 使用断言进行验证 self.assertIn("code", resp_dict) code = resp_dict.get("code") self.assertEqual(code, 1) # 测试只传name response = self.client.post("/login", data={"name": "admin"}) # respoonse.data是响应体数据 resp_json = response.data # 按照json解析 resp_dict = json.loads(resp_json) # 使用断言进行验证 self.assertIn("code", resp_dict) code = resp_dict.get("code") self.assertEqual(code, 1) def test_wrong_name_password(self): """测试用户名或密码错误""" # 使用客户端向后端发送post请求, data指明发送的数据,会返回一个响应对象 response = self.client.post("/login", data={"name": "admin", "password": "itcast"}) # respoonse.data是响应体数据 resp_json = response.data # 按照json解析 resp_dict = json.loads(resp_json) # 使用断言进行验证 self.assertIn("code", resp_dict) code = resp_dict.get("code") self.assertEqual(code, 2) if __name__ == '__main__': unittest.main()
login.py
from flask import Flask, request, jsonify app = Flask(__name__) @app.route("/login", methods=["POST"]) def login(): """登陆""" name = request.form.get("name") password = request.form.get("password") # "" 0 [] () {} None 在逻辑判断时都是假 if not all([name, password]): # 表示name或password中有一个为空或者都为空 return jsonify(code=1, message=u"参数不完整") if name == "admin" and password =="python": return jsonify(code=0, message=u"OK") else: return jsonify(code=2, message=u"用户名或密码错误") if __name__ == '__main__': app.run()
当咱们执行下面的hello.py时,使用的flask自带的服务器,完成了web服务的启动。在生产环境中,flask自带的服务器,没法知足性能要求,咱们这里采用Gunicorn作wsgi容器,来部署flask程序。Gunicorn(绿色独角兽)是一个Python WSGI的HTTP服务器。从Ruby的独角兽(Unicorn )项目移植。该Gunicorn服务器与各类Web框架兼容,实现很是简单,轻量级的资源消耗。Gunicorn直接用命令启动,不须要编写配置文件,相对uWSGI要容易不少。
web开发中,部署方式大体相似。简单来讲,前端代理使用Nginx主要是为了实现分流、转发、负载均衡,以及分担服务器的压力。Nginx部署简单,内存消耗少,成本低。Nginx既能够作正向代理,也能够作反向代理。
正向代理:请求通过代理服务器从局域网发出,而后到达互联网上的服务器。
特色:服务端并不知道真正的客户端是谁。
反向代理:请求从互联网发出,先进入代理服务器,再转发给局域网内的服务器。
特色:客户端并不知道真正的服务端是谁。
区别:正向代理的对象是客户端。反向代理的对象是服务端。
通常部署的时候不是用一台服务器进行部署,业务服务器好比说有两台,mysql服务器有一台,redis服务器有一台,其中业务服务器部署看gunicorn和flask程序,nginx就会把请求均衡地分发到这两台部署环境同样的服务器,而且nginx设计之初就是为了提供静态文件的支持,因此让nginx帮忙处理静态资源。
安装gunicorn: pip3 install gunicorn
启动:gunicorn -w 4 -b 127.0.0.1:5001 -D --access_log='/tmp/log' 运行文件名称:Flask程序实例名
此时共有5个进程,一个父进程,一个子进程
upstream flask { server 10.20.1.11:5000; server 10.20.1.12:6000; } server { # 监听80端口 listen 80; # 本机 server_name localhost; # 默认请求的url location / { #请求转发到gunicorn服务器 proxy_pass http://flask; #设置请求头,并将头信息传递给服务器端 proxy_set_header Host $host; } }