配置文件
flask中的配置文件是一个flask.config.Config对象(继承字典),默认配置为: { 'DEBUG': get_debug_flag(default=False), 是否开启Debug模式 'TESTING': False, 是否开启测试模式 'PROPAGATE_EXCEPTIONS': None, 'PRESERVE_CONTEXT_ON_EXCEPTION': None, 'SECRET_KEY': None, 'PERMANENT_SESSION_LIFETIME': timedelta(days=31), 'USE_X_SENDFILE': False, 'LOGGER_NAME': None, 'LOGGER_HANDLER_POLICY': 'always', 'SERVER_NAME': None, 'APPLICATION_ROOT': None, 'SESSION_COOKIE_NAME': 'session', 'SESSION_COOKIE_DOMAIN': None, 'SESSION_COOKIE_PATH': None, 'SESSION_COOKIE_HTTPONLY': True, 'SESSION_COOKIE_SECURE': False, 'SESSION_REFRESH_EACH_REQUEST': True, 'MAX_CONTENT_LENGTH': None, 'SEND_FILE_MAX_AGE_DEFAULT': timedelta(hours=12), 'TRAP_BAD_REQUEST_ERRORS': False, 'TRAP_HTTP_EXCEPTIONS': False, 'EXPLAIN_TEMPLATE_LOADING': False, 'PREFERRED_URL_SCHEME': 'http', 'JSON_AS_ASCII': True, 'JSON_SORT_KEYS': True, 'JSONIFY_PRETTYPRINT_REGULAR': True, 'JSONIFY_MIMETYPE': 'application/json', 'TEMPLATES_AUTO_RELOAD': None, } 方式一: app.config['DEBUG'] = True PS: 由于Config对象本质上是字典,所以还可以使用app.config.update(...) 方式二: 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.config.from_object("python类或类的路径") app.config.from_object('pro_flask.settings.TestingConfig') settings.py class Config(object): DEBUG = False TESTING = False DATABASE_URI = 'sqlite://:memory:' class ProductionConfig(Config): DATABASE_URI = 'mysql://user@localhost/foo' class DevelopmentConfig(Config): DEBUG = True class TestingConfig(Config): TESTING = True PS: 从sys.path中已经存在路径开始写 PS: settings.py文件默认路径要放在程序root_path目录,如果instance_relative_config为True,则就是instance_path目录
我们常用的方法
from flask import Flask,render_template,redirect app = Flask(__name__) # 配置文件 app.config.from_object("settings.DevelopmentConfig") @app.route('/index',methods=['GET','POST']) def index(): return "" if __name__ == '__main__': app.run()
settings
class BaseConfig(object): DEBUG = True SECRET_KEY = "asudflkjdfadjfakdf" class ProductionConfig(BaseConfig): DEBUG = False class DevelopmentConfig(BaseConfig): pass class TestingConfig(BaseConfig): pass
settings中可以定义多种配置,我们根据不同的环境使用不同的配置
路由
两种添加路由的方式
方式一: @app.route('/xxxx') # @decorator def index(): return "Index" 方式二: def index(): return "Index" app.add_url_rule('/xxx', "n1", index) #n1是别名
执行@app.route('/xxx')时,首先执行app.route('/xxx'),这个方法的源码如下
所以decorator=app.route('/index',methods=['GET','POST']),然后再使用@decorator装饰函数,可以看到实际上就是执行了self.add_url_rule(rule, endpoint, f, **options),所以两种添加路由的方式本质是一样的
添加路由关系的本质:将url和视图函数封装成一个Rule对象)添加到Flask的url_map字段中
路由中传参
@app.route('/user/<username>') #常用的 不加参数的时候默认是字符串形式的 @app.route('/post/<int:post_id>') #常用的 #指定int,说明是整型的 @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, }
自定义正则匹配路由
from flask import Flask, views, url_for from werkzeug.routing import BaseConverter app = Flask(import_name=__name__) class RegexConverter(BaseConverter): """ 自定义URL匹配正则表达式 """ def __init__(self, map, regex): super(RegexConverter, self).__init__(map) self.regex = regex def to_python(self, value): """ 路由匹配时,匹配成功后传递给视图函数中参数的值 :param value: :return: """ return int(value) def to_url(self, value): """ 使用url_for反向生成URL时,传递的参数经过该方法处理,返回的值用于生成URL中的参数 :param value: :return: """ val = super(RegexConverter, self).to_url(value) return val # 添加到flask中 app.url_map.converters['regex'] = RegexConverter @app.route('/index/<regex("\d+"):nid>') def index(nid): print(url_for('index', nid='888')) return 'Index' if __name__ == '__main__': app.run()
反向生成URL: url_for
endpoint("name") #别名,相当于django中的name,如果没有定义endpoint,默认为视图函数的函数名
反向解析需要导入:
from flask import Flask, url_for
示例
from flask import Flask,render_template,redirect,url_for app = Flask(__name__) @app.route('/index',methods=['GET','POST'],endpoint='n1') def index(): v1 = url_for('n1') v2 = url_for('login') # 没有定义endpoint默认为视图函数名 v3 = url_for('logout') print(v1,v2,v3) return "Index" @app.route('/login',methods=['GET','POST']) def login(): return "login" @app.route('/logout',methods=['GET','POST']) def logout(): return "logout" if __name__ == '__main__': app.run()
@app.route和app.add_url_rule参数
@app.route和app.add_url_rule参数:
rule, URL规则
view_func, 视图函数名称
defaults=None, 默认值,当URL中无参数,函数需要参数时,使用defaults={'k':'v'}为函数提供参数
endpoint=None, 名称,用于反向生成URL,即: url_for('名称')
methods=None, 允许的请求方式,如:["GET","POST"]
strict_slashes=None, 对URL最后的 / 符号是否严格要求,
如:
@app.route('/index',strict_slashes=False),
访问 http://www.xx.com/index/ 或 http://www.xx.com/index均可
@app.route('/index',strict_slashes=True)
仅访问 http://www.xx.com/index
redirect_to=None, 重定向到指定地址
如:
@app.route('/index/<int:nid>', redirect_to='/home/<nid>')
或
def func(adapter, nid):
return "/home/888"
@app.route('/index/<int:nid>', redirect_to=func)
subdomain=None, 子域名访问
from flask import Flask, views, url_for
app = Flask(import_name=__name__)
app.config['SERVER_NAME'] = 'haiyan.com:5000'
@app.route("/", subdomain="admin")
def static_index():
"""Flask supports static subdomains
This is available at static.your-domain.tld"""
return "admin.xxx.com"
#动态生成
@app.route("/dynamic", subdomain="<username>")
def username_index(username):
"""Dynamic subdomains are also supported
Try going to user1.your-domain.tld/dynamic"""
return username + ".your-domain.tld"
if __name__ == '__main__':
app.run()
所有的域名都得与IP做一个域名解析:
如果你想通过域名去访问,有两种解决方式:
方式一:
1、租一个域名 haiyan.lalala
2、租一个公网IP 49.8.5.62
3、域名解析:
haiyan.com 49.8.5.62
4、吧代码放在49.8.5.62这个服务器上,程序运行起来
用户可以通过IP进行访问
方式二:如果是自己测试用的就可以用这种方式。先在自己本地的文件中找
C:\Windows\System32\drivers\etc 找到HOST,修改配置
然后吧域名修改成自己的本地服务器127.0.0.1
加上配置:app.config["SERVER_NAME"] = "haiyan.com:5000"
重定向示例
from flask import Flask,render_template,redirect app = Flask(__name__) @app.route('/index',methods=['GET','POST'],redirect_to='/new') def index(): return "老功能" @app.route('/new',methods=['GET','POST']) def new(): return '新功能' if __name__ == '__main__': app.run()
当用户访问/index时会自动重定向到/new
给多个视图添加装饰器
from flask import Flask,render_template,redirect app = Flask(__name__) import functools def wapper(func): @functools.wraps(func) def inner(*args,**kwargs): print('before') return func(*args,**kwargs) return inner @app.route('/xxxx',methods=['GET','POST']) @wapper def index(): return "Index" @app.route('/order',methods=['GET','POST']) @wapper def order(): return "order" if __name__ == '__main__': app.run()
首先要注意的是,装饰器要添加在路由下面,还有就是给多个视图添加同一个装饰器时,由于装饰器装饰后就相当于在执行inner函数,如果视图没有定义endpoint那么都会默认使用函数名,也就是inner,这时多个函数都是inner,会报错,所以要使用
@functools.wraps(func)来保留原函数的函数名等信息
视图函数
Flask中的CBV模式
方式一: @app.route('/index',endpoint='xx') def index(nid): url_for('xx',nid=123) return "Index" 方式二: def index(nid): url_for('xx',nid=123) return "Index" app.add_url_rule('/index',index)
请求和响应
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()
from flask import Flask,url_for,request,redirect,render_template,jsonify,make_response from urllib.parse import urlencode,quote,unquote app = Flask(__name__) @app.route('/index',endpoint='xx') def index(): from werkzeug.datastructures import ImmutableMultiDict ================= # get_data = request.args # get_dict = get_data.to_dict() # get_dict['xx'] = '18' # url = urlencode(get_dict) # print(url) ==================== # print(request.query_string) # print(request.args) ========================== # val = "%E6%8A%8A%E5%87%A0%E4%B8%AA" # print(unquote(val)) #吧上面这样的数据转换成中文 # # return "Index" # return "Index" # return redirect() # return render_template() # return jsonify(name='alex',age='18') #相当于JsonResponse ======================= response = make_response('xxxxx') ##如果是返回更多的值,cookie,headers,或者其他的就可用它 response.headers['xxx'] = '123123' return response if __name__ == '__main__': # app.__call__ app.run()
返回json格式数据
from flask import Flask,render_template,redirect,request,jsonify,make_response return json.dumps({}) # return jsonify({})
模板
Flask使用的是Jinja2模板,所以其语法和Django基本无差别
自定义方法
from flask import Flask,render_template,redirect,request,jsonify,make_response,Markup app = Flask(__name__) 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)
当函数返回的是字符串时,为了防止xss攻击,加了验证,所以页面上显示字符串的形式,解决办法,有两种方式
在后端Markup
v5 = Markup("<input type='text' />")
在前端
{{ v4|safe }}
将这些参数传给前端后,前端页面可以采用类似与django的方式渲染
<h1>{{k1}}</h1> <h1>{{k2.0}} {{k2[0]}} </h1> <h1>{{k3.name}} {{k3['name']}} {{k3.get('name',888)}}</h1> <h1>{{k4(66)}}</h1> <h1>{{k5(99)}}</h1>
这里可以看出flask的渲染方式更贴近python,很多python的语法可以使用,执行函数时也不像django,可以自己加括号执行,还可以传参数
写一个函数在所有的页面都使用
template_global和template_filter
@app.template_global() def sb(a1, a2): return a1 + a2 @app.template_filter() def db(a1, a2, a3): return a1 + a2 + a3
这么定义后在前端所有的页面都能调用这两个函数
调用方式:{{sb(1,2)}} {{ 1|db(2,3)}}
filter方法的第一个参数就是|的值
模板继承:和django的一样,extends
先定义一个母板
<!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Title</title> </head> <body> <div >头部</div> <div> {% block content %} {% endblock %} </div> <div >底部</div> </body> </html>
然后继承它
{% extends 'layout.html'%} {% block content %} <h1>{{k1}}</h1> <h1>{{k2.0}} {{k2[0]}} </h1> <h1>{{k3.name}} {{k3['name']}} {{k3.get('name',888)}}</h1> <h1>{{k4(66)}}</h1> <h1>{{k5(99)}}</h1> {% endblock%}
session
除请求对象之外,还有一个 session 对象。它允许你在不同请求间存储特定用户的信息。它是在 Cookies 的基础上实现的,并且对 Cookies 进行密钥签名要使用会话,你需要设置一个密钥。
-
设置:session['username'] = 'xxx'
- 删除:session.pop('username', None)
"""
1. 请求刚刚达到
ctx = RequestContext(...)
- request
- session=None
ctx.push()
ctx.session = SecureCookieSessionInterface.open_session
2. 视图函数
3. 请求结束
SecureCookieSessionInterface.save_session()
"""
from flask import Flask,session
app = Flask(__name__)
app.secret_key = 'sadfasdfasdf'
@app.route('/x1')
def index():
# 去ctx中获取session
session['k1'] = 123
session['k2'] = 123
del session['k2']
return "Index"
@app.route('/x2')
def order():
print(session['k1'])
return "Order"
if __name__ == '__main__':
app.run()
# 1. 请求一旦到来
# app.__call__
# app.wsgi_app
# app.open_session
session的操作和字典相同,默认情况下session存在COOKIE中
源码简单剖析,之前我们已经知道在执行app.run时实际上执行的就是run_simple方法,也就是app(),这时会调用Flask类中的__call__方法
这个方法实际上在执行app.wsgi_app
在wsgi_app方法中首先定义了一个ctx,我们看看self.request_context干了什么
可以看到这个方法实际上就是返回了RequestContext类的对象,而ctx就是这个类的对象,那么这个类中都定义了些什么呢
这里首先定义了request,然后也定义了session,所以这个ctx对象包含了request和session,定义request时,又用到了app中的request_class
这其实是一个类,所以request就是这个类的对象
定义玩ctx后又执行了ctx.push方法
在这个方法中定义了self.session,我们来看看self.app.open_session干了什么
这里实际上执行了self.session_interface.open_session,而session_interface又是一个新的类的对象
这里其实执行的是SecureCookieSessionInterface()的open_session方法
可以看到在这个方法中首先从cookies中获取session,如果是第一次访问,取不到val,则直接返回self.session_class(),如果有值,则将值反序列化,再返回self.session_class(data),我们再看看self.session_class做了什么
我们发现这又是一个类,所以上面返回的是这个类的对象
这个类继承了CallbackDict,而CallbackDict又继承了dict
所以我们知道了self.session_class返回的对象其实是一个特殊的字典,所以我们能像使用字典一样使用session
执行完ctx.push后接着要执行
这个方法其实会去执行我们的视图函数,然后返回
返回时执行了self.finalize_request
这个方法中又执行了self.process_response,在这个方法中又执行了一步save_session的操作
其实是在执行
而session_interface = SecureCookieSessionInterface(),所以我们又要找到SecureCookieSessionInterface中的save_session方法
它实际上就是将数据序列化后写入了cookie
所以session的整体流程就是先从cookie中读取,然后返回一个特殊的字典对象,我们可以修改,最后执行完视图函数后,我们又将修改后的session序列化,然后写入cookie
特殊的装饰器
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from flask import Flask, Request, render_template
app = Flask(__name__, template_folder='templates')
app.debug = True
@app.before_first_request # 只有第一次请求来时执行,后面的都不执行
def before_first_request1():
print('before_first_request1')
@app.before_first_request
def before_first_request2():
print('before_first_request2')
@app.before_request # 类似与django中间件中的process_request
def before_request1():
Request.nnn = 123
print('before_request1')
@app.before_request
def before_request2():
print('before_request2')
@app.after_request
def after_request1(response):
print('before_request1', response)
return response
@app.after_request # 类似与django中间件中的process_response
def after_request2(response):
print('before_request2', response)
return response
@app.errorhandler(404) # 当报404错误时返回的内容
def page_not_found(error):
return 'This page does not exist', 404
@app.template_global() # 定义全局的函数,每个页面都能使用
def sb(a1, a2):
return a1 + a2
@app.template_filter() # 定义过滤函数
def db(a1, a2, a3):
return a1 + a2 + a3
@app.route('/')
def hello_world():
return render_template('hello.html')
if __name__ == '__main__':
app.run()
before_request和after_request
before_request是请求在执行视图函数之前会执行的,after_request是请求执行完视图函数会执行的
before_request如果返回None则会继续向后执行,当有多个时会按顺序执行,如果有返回值,那么后面的将不会执行,视图函数也不会执行,而是会按倒序执行after_request(这里和django的中间件有些不同)
有多个before_request时会按顺序执行
有多个after_request时会按倒序执行
闪现
session存在在服务端的一个字典里面,session保存起来,取一次里面还是有的,直到你删除之后才没有了
1、本质:flash是基于session创建的,flash支持往里边放值,只要你取一下就没有了,相当于pop了一下。不仅把值取走,而且把session里的东西去掉
2、闪现有什么用?
from flask import Flask,session,Session,flash,get_flashed_messages,redirect,render_template,request
app = Flask(__name__)
app.secret_key ='sdfsdfsdf'
@app.route('/users')
def users():
# 方式一
# msg = request.args.get('msg','')
# 方式二
# msg = session.get('msg')
# if msg:
# del session['msg']
# 方式三
v = get_flashed_messages()
print(v)
return render_template('users.html',msg=v)
@app.route('/useradd')
def user_add():
# 在数据库中添加一条数据
# 假设添加成功,在跳转到列表页面时,显示添加成功
# 方式一
# return redirect('/users?msg=添加成功')
# 方式二
# session['msg'] = '添加成功'
# 方式三
flash('添加成功')
return redirect('/users')
if __name__ == '__main__':
app.run(debug=True)
flash中的值被取一次就会删除,不会保留
flash还可以分类设置
from flask import Flask, session, flash, get_flashed_messages
app = Flask(__name__)
app.secret_key = 'asdfasdfasdf'
@app.route('/x1', methods=['GET', 'POST'])
def login():
flash('我要上向龙1', category='x1')
flash('我要上向龙2', category='x2')
return "视图函数x1"
@app.route('/x2', methods=['GET', 'POST'])
def index():
data = get_flashed_messages(category_filter=['x1'])
print(data)
return "视图函数x2"
if __name__ == '__main__':
app.run()
中间件
通过上面的分析我们知道当请求过来时会先执行app.run-->run.simple-->app()-->__call__-->app.wsgi_app
如果我们自己定义了wsgi_app方法,那么就不会执行原来的,而会执行我们自己定义的了,利用这一点我们可以自己定义一个,让请求来了在执行app.wsgi_app之前和之后能执行一些我们自己的逻辑
from flask import Flask
app = Flask(__name__)
app.secret_key = 'asdfasdfasdf'
@app.route('/x2',methods=['GET','POST'])
def index():
return "x2"
class Middleware(object):
def __init__(self,old_wsgi_app):
"""
服务端启动时,自动执行
:param old_wsgi_app:
"""
self.old_wsgi_app =old_wsgi_app
def __call__(self, environ, start_response):
"""
每次有用户请求道来时
:param args:
:param kwargs:
:return:
"""
print('before')
obj = self.old_wsgi_app(environ, start_response)
print('after')
return obj
if __name__ == '__main__':
app.wsgi_app = Middleware(app.wsgi_app)
app.run()
"""
1.执行app.__call__
2.在调用app.wsgi_app方法
"""
这里要注意在执行app.wsgi_app之前我们无法使用request和session,只能使用最原始的数据
蓝图
在之前我们使用flask时所有的内容都是写在一个python文件中的,如果项目规模大了的话,这样做显然是不行的,所以我们需要创建一些项目的目录
目录结构类似django,manage.py是启动文件,内容如下
from s8pro import app if __name__ == '__main__': app.run()
在导入s8pro时会执行该目录下的__init__文件,这个文件的内容
from flask import Flask from .views import account from .views import admin from .views import user app = Flask(__name__) app.register_blueprint(account.ac) app.register_blueprint(admin.ad) app.register_blueprint(user.us)
这里我们实例化了一个app对象,然后将它和其它的蓝图关联
accout.py
from flask import Blueprint,render_template import redis ac = Blueprint('ac',__name__) @ac.route('/login') def login(): conn = redis.Redis() return render_template('login.html') @ac.route('/logout') def logout(): return '123'
admin.py
from flask import Blueprint ad = Blueprint('ad',__name__,url_prefix='/admin') @ad.before_request def bf(): print('before_request') @ad.route('/home') def home(): return 'home' @ad.route('/xxxx') def xxxx(): return 'xxxx'
user.py
from flask import Blueprint us = Blueprint('us',__name__) @us.route('/info') def info(): return 'info'
在每个视图文件中我们都可以示例化一个蓝图对象,它的使用和app一样,最后只要把app和蓝图对象关联就可以访问到了
蓝图还可以定义访问url的前缀,类似于django中的include
ad = Blueprint('ad',__name__,url_prefix='/admin')
这样访问时url必须是/admin/***
蓝图中还可以定义before_request等方法,访问时只有访问到该蓝图中的内容时才会执行该方法,不是全局的
蓝图用于为应用提供目录划分:
小型应用程序:示例
大型应用程序:示例
其他:
- 蓝图URL前缀:xxx = Blueprint('account', __name__,url_prefix='/xxx')
- 蓝图子域名:xxx = Blueprint('account', __name__,subdomain='admin')
# 前提需要给配置SERVER_NAME: app.config['SERVER_NAME'] = 'xxx.com:5000'
# 访问时:admin.xxx.com:5000/login.html