Flask是一个基于Python开发并且依赖jinja2模板和Werkzeug WSGI服务的一个微型框架,对于Werkzeug本质是Socket服务端,其用于接收http请求并对请求进行预处理,然后触发Flask框架,开发人员基于Flask框架提供的功能对请求进行相应的处理,并返回给用户,如果要返回给用户复杂的内容时,需要借助jinja2模板来实现对模板的处理,即:将模板和数据进行渲染,将渲染后的字符串返回给用户浏览器。
“微”(micro) 并不表示你需要把整个 Web 应用塞进单个 Python 文件(虽然确实可以 ),也不意味着 Flask 在功能上有所欠缺。微框架中的“微”意味着 Flask 旨在保持核心简单而易于扩展。Flask 不会替你做出太多决策——比如使用何种数据库。而那些 Flask 所选择的——比如使用何种模板引擎——则很容易替换。除此之外的一切都由可由你掌握。如此,Flask 可以与您珠联璧合。
默认情况下,Flask 不包含数据库抽象层、表单验证,或是其它任何已有多种库可以胜任的功能。然而,Flask 支持用扩展来给应用添加这些功能,如同是 Flask 本身实现的一样。众多的扩展提供了数据库集成、表单验证、上传处理、各种各样的开放认证技术等功能。Flask 也许是“微小”的,但它已准备好在需求繁杂的生产环境中投入使用。
一. 基本使用
from flask import Flask app = Flask(__name__) @app.route('/') def hello_world(): return 'Hello World!' if __name__ == '__main__': app.run()
Flask实例化时候参数配置
static_folder = 'static', # 静态文件目录的路径 默认当前项目中的static目录 static_host = None, # 远程静态文件所用的Host地址,默认为空 static_url_path = None, # 静态文件目录的url路径 默认不写是与static_folder同名,远程静态文件时复用 # host_matching是否开启host主机位匹配,是要与static_host一起使用,如果配置了static_host, 则必须赋值为True # 这里要说明一下,@app.route("/",host="localhost:5000") 就必须要这样写 # host="localhost:5000" 如果主机头不是 localhost:5000 则无法通过当前的路由 host_matching = False, # 如果不是特别需要的话,慎用,否则所有的route 都需要host=""的参数 subdomain_matching = False, # 理论上来说是用来限制SERVER_NAME子域名的,但是目前还没有感觉出来区别在哪里 template_folder = 'templates' # template模板目录, 默认当前项目中的 templates 目录 instance_path = None, # 指向另一个Flask实例的路径 instance_relative_config = False # 是否加载另一个实例的配置 root_path = None # 主模块所在的目录的绝对路径,默认项目目录
二、配置文件
flask中的配置文件是一个flask.config.Config对象(继承字典),默认配置为: { 'DEBUG': False, # 是否开启Debug模式,开启编辑时代码重启,LOG打印级别最低,错误信息透传 'TESTING': False, # 是否开启测试模式,无限接近生产环境,代码编辑时不会重启,LOG级别较高,错误信息不再透传 'PROPAGATE_EXCEPTIONS': None, # 异常传播(是否在控制台打印LOG) 当Debug或者testing开启后,自动为True 'PRESERVE_CONTEXT_ON_EXCEPTION': None, # 一两句话说不清楚,一般不用它 'SECRET_KEY': None, # 之前遇到过,在启用Session的时候,一定要有它 'PERMANENT_SESSION_LIFETIME': timedelta(days=31), # days , Session的生命周期(秒)默认31天的秒数 'USE_X_SENDFILE': False, # 是否弃用 x_sendfile 'LOGGER_NAME': None, # 日志记录器的名称 'LOGGER_HANDLER_POLICY': 'always', 'SERVER_NAME': None, # 服务访问域名 'APPLICATION_ROOT': None, # 项目的完整路径 'SESSION_COOKIE_NAME': 'session', # 在cookies中存放session加密字符串的名字 'SESSION_COOKIE_DOMAIN': None, # 在哪个域名下会产生session记录在cookies中 'SESSION_COOKIE_PATH': None, # cookies的路径 'SESSION_COOKIE_HTTPONLY': True, # 控制 cookie 是否应被设置 httponly 的标志, 'SESSION_COOKIE_SECURE': False, # 控制 cookie 是否应被设置安全标志 'SESSION_REFRESH_EACH_REQUEST': True, # 这个标志控制永久会话如何刷新 'MAX_CONTENT_LENGTH': None, # 如果设置为字节数, Flask 会拒绝内容长度大于此值的请求进入,并返回一个 413 状态码 'SEND_FILE_MAX_AGE_DEFAULT': 12, # hours 默认缓存控制的最大期限 'TRAP_BAD_REQUEST_ERRORS': False, # 如果这个值被设置为 True ,Flask不会执行 HTTP 异常的错误处理,而是像对待其它异常一样, # 通过异常栈让它冒泡地抛出。这对于需要找出 HTTP 异常源头的可怕调试情形是有用的。 'TRAP_HTTP_EXCEPTIONS': False, # Werkzeug 处理请求中的特定数据的内部数据结构会抛出同样也是“错误的请求”异常的特殊的 key errors 。 # 同样地,为了保持一致,许多操作可以显式地抛出 BadRequest 异常。 # 因为在调试中,你希望准确地找出异常的原因,这个设置用于在这些情形下调试。 # 如果这个值被设置为 True ,你只会得到常规的回溯。 'EXPLAIN_TEMPLATE_LOADING': False, 'PREFERRED_URL_SCHEME': 'http', # 生成URL的时候如果没有可用的 URL 模式话将使用这个值 'JSON_AS_ASCII': True, # 默认情况下 Flask 使用 ascii 编码来序列化对象。如果这个值被设置为 False , # Flask不会将其编码为 ASCII,并且按原样输出,返回它的 unicode 字符串。 # 比如 jsonfiy 会自动地采用 utf-8 来编码它然后才进行传输。 'JSON_SORT_KEYS': True, #默认情况下 Flask 按照 JSON 对象的键的顺序来序来序列化它。 # 这样做是为了确保键的顺序不会受到字典的哈希种子的影响,从而返回的值每次都是一致的,不会造成无用的额外 HTTP 缓存。 # 你可以通过修改这个配置的值来覆盖默认的操作。但这是不被推荐的做法因为这个默认的行为可能会给你在性能的代价上带来改善。 'JSONIFY_PRETTYPRINT_REGULAR': True, 'JSONIFY_MIMETYPE': 'application/json', 'TEMPLATES_AUTO_RELOAD': None, } 方式一: app.config['DEBUG'] = True 方式二: app.config.from_pyfile("python文件名称") 如: settings.py DEBUG = True app.config.from_pyfile("settings.py") app.config.from_envvar("环境变量名称") 环境变量的值为python文件名称名称,内部调用from_pyfile方法 app.config.from_json("json文件名称") JSON文件名称,必须是json格式,因为内部会执行json.loads app.config.from_mapping({'DEBUG':True}) 字典格式
三、路由系统
@app.route() 装饰器中的参数
methods : 当前 url 地址,允许访问的请求方式(当前装饰的视图函数支持的请求方式)
@app.route("/info", methods=["GET", "POST"]) def student_info(): stu_id = int(request.args["id"]) return f"Hello Old boy {stu_id}" # Python3.6的新特性 f"{变量名}"
endpoint : 反向url地址,默认为视图函数名 (url_for)
from flask import url_for @app.route("/info", methods=["GET", "POST"], endpoint="r_info") def student_info(): print(url_for("r_info")) # /info stu_id = int(request.args["id"]) return f"Hello {stu_id}"
strict_slashes:url地址结尾符"/“的控制 False : 无论结尾 “/” 是否存在均可以访问 , True : 结尾必须不能是 “/”,这一点不像Django会帮我们自动补全”/"
redirect_to: url地址重定向
subdomain:子域名前缀 subdomian=“Tom” 这样写可以得到 Tom.sayhello.com 前提是app.config[“SERVER_NAME”] = “sayhello.com”
app.config["SERVER_NAME"] = "sayhello.com" @app.route("/info",subdomain="Tom") def student_info(): return "Hello info" # 访问地址为: Tom.sayhello.com/info
动态参数路由:
from flask import url_for # 访问地址 : http://127.0.0.1:5000/info/1 @app.route("/info/<int:nid>", methods=["GET", "POST"], endpoint="r_info") def student_info(nid): print(url_for("r_info",nid=2)) # /info/2 return f"Hello {nid}" # Python3.6的新特性 f"{变量名}"
- @app.route('/user/<username>')
- @app.route('/post/<int:post_id>')
- @app.route('/post/<float:post_id>')
- @app.route('/post/<path:path>')
- @app.route('/login', methods=['GET', 'POST'])
常用路由系统有以上五种,所有的路由系统都是基于一下对应关系来处理:
DEFAULT_CONVERTERS = { 'default': UnicodeConverter, 'string': UnicodeConverter, 'any': AnyConverter, 'path': PathConverter, 'int': IntegerConverter, 'float': FloatConverter, 'uuid': UUIDConverter, }
四、模板
1、模板的使用
Flask使用的是Jinja2模板,所以其语法和Django几乎无差别,创建一个函数并通过参数的形式传入render_template
flask会默认在根目录下的templates目录下寻找模板文件,如果想更改模板文件地址,应该在创建app
的时候,给Flask
传递一个关键字参数template_folder
from flask import Flask, render_template, redirect, request, jsonify, make_response, Markup app = Flask(__name__) @app.template_global() def sb(a1, a2): """ 每个模板中可以调用的函数 :param a1: :param a2: :return: """ return a1 + a2 def gen_input(value): # return "<input value='%s'/>" %value return Markup("<input value='%s'/>" % value) @app.route('/x1', methods=['GET', 'POST']) def index(): context = { 'k1': 123, 'k2': [11, 22, 33], 'k3': {'name': 'oldboy', 'age': 84}, 'k4': lambda x: x + 1, 'k5': gen_input, # 当前模板才能调用的函数 } return render_template('index.html', **context) @app.route('/x2', methods=['GET', 'POST']) def order(): context = { 'k1': 123, 'k2': [11, 22, 33], } return render_template('order.html', **context) if __name__ == '__main__': app.run()
注意:Markup等价django的mark_safe,用于处理后端传入的html代码
2、jinjia2自带的过滤器
字符串操作 禁用转义: {{ '<em>hello</em>' | safe }} 删除标签: {{ '<em>hello</em>' | striptags }} 首字母大写: {{ 'hello' | capitalize }} 所有值小写: {{ 'HELLO' | lower }} 首字母大写: {{ 'hello world' | title }} 字符串反转: {{ 'hello' | reverse }} 字符串截断: {{ 'hello world' | truncate(5) }} 列表操作 获取列表长度: {{ [1,2,3,4,5,6] | length }} 列表求和: {{ [1,2,3,4,5,6] | sum }} 列表排序: {{ [6,2,3,1,5,4] | sort }}
五、请求和响应
from flask import Flask from flask import request from flask import render_template from flask import redirect from flask import make_response app = Flask(__name__) @app.route('/login.html', methods=['GET', "POST"]) def login(): # 请求相关信息 # request.method # request.args # request.form # request.values # 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 # request.files # obj = request.files['the_file_name'] # obj.save('/var/www/uploads/' + secure_filename(f.filename)) # 响应相关信息 # return "字符串" # return render_template('html模板路径',**{}) # return redirect('/index.html') # response = make_response(render_template('index.html')) # response是flask.wrappers.Response类型 # response.delete_cookie('key') # response.set_cookie('key', 'value') # response.headers['X-Something'] = 'A value' # return response return "内容" if __name__ == '__main__': app.run()
六、Session
除请求对象之外,还有一个 session 对象。它允许你在不同请求间存储特定用户的信息。它是在 Cookies 的基础上实现的,并且对 Cookies 进行密钥签名要使用会话,你需要设置一个 secret_key。
-
设置:session['username'] = 'xxx'
- 删除:session.pop('username', None)
from flask import Flask, session, redirect, url_for, escape, request app = Flask(__name__) @app.route('/') def index(): if 'username' in session: return 'Logged in as %s' % escape(session['username']) return 'You are not logged in' @app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': session['username'] = request.form['username'] return redirect(url_for('index')) return ''' <form action="" method="post"> <p><input type=text name=username> <p><input type=submit value=Login> </form> ''' @app.route('/logout') def logout(): # remove the username from the session if it's there session.pop('username', None) return redirect(url_for('index')) # set the secret key. keep this really secret: app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'
#!/usr/bin/env python # -*- coding:utf-8 -*- """ pip3 install redis pip3 install flask-session """ from flask import Flask, session, redirect from flask.ext.session import Session app = Flask(__name__) app.debug = True app.secret_key = 'asdfasdfasd' app.config['SESSION_TYPE'] = 'redis' from redis import Redis app.config['SESSION_REDIS'] = Redis(host='192.168.0.94',port='6379') Session(app) @app.route('/login') def login(): session['username'] = 'alex' return redirect('/index') @app.route('/index') def index(): name = session['username'] return name if __name__ == '__main__': app.run() 第三方session
七、蓝图
蓝图用于为应用提供目录划分:
小型应用程序:示例
大型应用程序:示例
其他:
- 蓝图URL前缀:xxx = Blueprint('account', __name__,url_prefix='/xxx')
- 蓝图子域名:xxx = Blueprint('account', __name__,subdomain='admin')
# 前提需要给配置SERVER_NAME: app.config['SERVER_NAME'] = 'wupeiqi.com:5000'
# 访问时:admin.wupeiqi.com:5000/login.html
八、闪现
message是一个基于Session实现的用于保存数据的集合,其特点是:使用一次就删除。
闪现有什么作用呢?
比如:假设在A页面做个操作,但该操作失败了,要跳转到B页面并显示这些错误信息
from flask import Flask, flash, get_flashed_messages, redirect app = Flask(__name__) app.debug = True app.secret_key = "dasfwfwfw" # 必须要加secret_key ''' 其实,它就是通过session做的,先把数据存在session里,数据如果在session里,只要不删,就永远在。 然后,如果值被拿走,就会通过session.pop()的方式给拿走。 ''' @app.route("/index") def index(): # 如果请求出错,需要将错误信息flash,也就是设置错误值到某个地方 # flash("前端输入参数错误") # A.flash不分类,直接设置值 flash("前端输入参数错误", category="x1") # B.flash还可以对错误信息,进行分类 return redirect("/error") @app.route("/error") def error(): ''' 显示错误信息 ''' # 跳转到error页面后,请求时获取错误信息 # A.flash没有分类时 # data = get_flashed_messages() # 获取的就是flash的值 # B. 对于有分类的,取值时可以通过 category_filter 根据分类取错误信息 data = get_flashed_messages(category_filter=['x1']) # 可能有很多的错误信息,也可以按照索引的方式取出错误值 if data: msg = data[0] else: msg = "..." return "错误信息:%s" % (msg) if __name__ == "__main__": app.run()
九、中间件
Flask的中间件一般感觉用处不大,不如装饰器方便
十、各种装饰器
@app.before_request:效果类似django的process_request的装饰器,可以结合session用作登录判断
@app.before_request def before(*args,**kwargs): print('请求之前') ''' 如果允许通过访问,可以return None 该装饰器装饰的函数如果有return其他内容则直接结束访问, 效果有点类似django的process_reqeust中间件方法。 比如通过这个装饰器写登陆验证,判断其是否有session,没有则不允许访问,有则继续访问 然后通过request.path判断访问的函数,如果是登陆(白名单)则通过。 request.url 是完整的url request.path是域名后面的url正则 ''' if request.path == '/login': return None user = session.get('user_info') if user: return None return redirect('/login')
@app.after_request:
@app.after_request def after(response): #效果和process_response是一样的,必须有返回值,没有则报错。 print('我走了') return response
@app.error_handlers(404):自定义错误页面
@app.template_global():前端直接调用后端函数的装饰器
@app.before_first_request:只有第一次请求时候才执行的函数装饰器
@app.template_filter():模板过滤器
@app.template_filter('md') # 加参数后md就是这个过滤器的别名,没有参数默认用函数名 def markdown_to_html(txt): from markdown import markdown # 在视图函数的返回字符中用md语法格式的文档 return markdown(txt) # 视图函数 return render_template('index.html',title='<h1>Hello World</h1>',body=' ## Header2')
<body> {{ title|safe }} {{ body|md|safe }} </body>
@app.template_filter()
def my_filter(args):
temp = list(args)
temp.reverse()
return temp
十一、Flask插件
- WTForms
- SQLAchemy
- 等... http://flask.pocoo.org/extensions/