Flask基础

一 概述

Flask是一个基于Python开发并且依赖jinja2模板Werkzeug WSGI服务的一个微型框架,对于Werkzeug本质是Socket服务端,其用于接收http请求并对请求进行预处理,然后触发Flask框架,开发人员基于Flask框架提供的功能对请求进行相应的处理,并返回给用户,如果要返回给用户复杂的内容时,需要借助jinja2模板来实现对模板的处理,即:将模板和数据进行渲染,将渲染后的字符串返回给用户浏览器。

“微”(micro) 并不表示我们需要把整个 Web 应用塞进单个 Python 文件(虽然确实可以 ),也不意味着 Flask 在功能上有所欠缺。微框架中的“微”意味着 Flask 旨在保持核心简单而易于扩展。Flask 不会替你做出太多决策——比如使用何种数据库。而那些 Flask 所选择的——比如使用何种模板引擎——则很容易替换。除此之外的一切都由可由你掌握。

默认情况下,Flask 不包含数据库抽象层、表单验证,或是其它任何已有多种库可以胜任的功能。然而,Flask 支持用扩展来给应用添加这些功能,如同是 Flask 本身实现的一样。众多的扩展提供了数据库集成、表单验证、上传处理、各种各样的开放认证技术等功能。Flask 也许是“微小”的,但它已准备好在需求繁杂的生产环境中投入使用。

补充--Flask和Django的比较:

Django:无socket,依赖第三方模块wsgi,中间件,路由系统(CBV,FBV),视图函数,ORM,cookie,session,Admin,Form,缓存,信号,序列化

Flask:无socket,中间件(扩展),路由系统,视图(CBV),第三方模块(依赖jinja2),cookie,session功能比较差

二 入门

2.1 安装

pip3 install flask

2.2 werkzeug模块

# Flask依赖一个实现wsgi协议的模块:werkzeug
from werkzeug.wrappers import Request, Response

@Request.application
def hello(request):
    return Response('Hello World!')

if __name__ == '__main__':
    from werkzeug.serving import run_simple
    run_simple('localhost', 4000, hello)

2.3 基本使用

from flask import Flask
app = Flask(__name__)  # 名称可以自定义,一般写__name__
 
@app.route('/')
def hello():
    return 'Hello World!'
 
if __name__ == '__main__':
    app.run()

提醒:实例化Flask对象,里面是有参数的

app = Flask(__name__,template_folder='templates',static_url_path='/xxxxxx')

为什么是__name__?

这样两种方式调用本模块时都能使代码运行

1.直接运行本模块时,__name__的值为__main__;

2.通过其他模块调用时,__name__的值为本模块的名称

2.4 实例

from flask import Flask,render_template,request,redirect,session
app = Flask(__name__)
app.secret_key = "abdfadaff"   # 加盐

@app.route('/login',methods=["GET","POST"])
def login():
    if request.method == "GET":
        return render_template('login.html')
    # request.args   # 对应Django中request.get()
    # request.form   # 对应Django中request.post()
    user = request.form.get("user")
    pwd = request.form.get("pwd")
    if user == 'joe' and pwd =='123':
        session['user'] = user  # session.pop('user') 删除session
        return redirect("/index")
    return render_template('login.html',error="用户名或密码错误!!!")
    # return render_template('login.html',**{"error":"用户名或密码错误!!!"})

@app.route('/index')
def index():
    user = session.get('user')
    if not user:
        return redirect('/login')
    return render_template('index.html')


if __name__ == '__main__':
    app.run()
View Code

注意:保存session的数据存到了浏览器上:

  • 优点:减轻了服务端的压力
  • 缺点:不安全

2.5 常见异常

1. POST提交数据时报错:


默认访问方式为GET,当通过POST方式提交数据时,会报错。解决方法:

@app.route("/login",methods=["GET","POST"])

2.设置session时报错:

解决方法:设置秘钥

app.secret_key = "abdfadaff"

三 配置文件

3.1 默认配置

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,
}
View Code

3.2 修改方式

# 方式一:
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("环境变量名称")  # 我们可以通过os.environ.get(‘环境变量名称’)获取环境变量的值
    环境变量的值为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目录
View Code
import os


class Config(object):
	XXX = ooo


    @staticmethod
    def init_app(app):
        pass


class DevelopmentConfig(Config):
    DEBUG = True
    DATABASE_URI = os.environ.get('DEV_DATABASE_URL') or '指定数据库URL'


class TestingConfig(Config):
    TESTING = True
    DATABASE_URI = os.environ.get('TEST_DATABASE_URL') or '指定数据库URL'



class ProductionConfig(Config):
    SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or '指定数据库URL'


config = {
    'development': DevelopmentConfig,
    'testing': TestingConfig,
    'production': ProductionConfig,
    'default': DevelopmentConfig
}


# 使用
def create_app(config_name):
    app = Flask(__name__)
    app.config.from_object(config[config_name])
    config[config_name].init_app(app)
	return app
View Code

知识点回顾--指定路径找到相应的类

import importlib

path = "settings.Foo"

p,c = path.rsplit('.',maxsplit=1)
m = importlib.import_module(p)
cls = getattr(m,c)  # 获取到类
View Code

四 路由系统

4.1 路由传参

@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,
}

4.2 反向生成URL:url_for

from flask import Flask, url_for

@app.route('/index',endpoint='n1') # endpoint:类似Django中的name,不写则默认为函数名,我们可以从源码中找到原因
def index():
    print(url_for('n1'))  # url_for反向生成url,结果为:/index,有参数加nid
    return 'Index'

@app.route('/func1')
def func1():
    print(url_for('func1'))  # /func1
    return 'Func1'

@app.route('/func2/<int:nid>')
def func2(nid):
    print(url_for('func2',nid=nid))  # /func2/nid
    return 'Func2'

4.3 设置路由的两种方式

#方式一(推荐使用)
@app.route('/xxx')  
def index():
	return "Index"
 
#方式二
def index():
	return "Index"
	
app.add_url_rule('/xxx', None, index)  # None代表别名为None 

为什么存在方式二?我们先看一下app.route()函数源码:

def route(self, rule, **options):
	def decorator(f):
		endpoint = options.pop('endpoint', None)
		self.add_url_rule(rule, endpoint, f, **options)
		return f
	return decorator

分析方式一执行步骤(原理):

  1. 先执行decorator = app.route('/index')
  2. 实际上给index函数加了一个装饰器:@decorator
  3. 再执行decorator(index),实际执行的就是self.add_url_rule(rule, endpoint, f, **options)。self—>app;rule—>/xxx;endpoint—>None;f—>index

注意事项:

  • 不用让endpoint重名
  • 如果endpoint重名,函数也一定要相同
def auth(func):
	def inner(*args, **kwargs):
		print('before')
		result = func(*args, **kwargs)
		print('after')
		return result
	return inner

@app.route('/index.html',methods=['GET','POST'],endpoint='index')
@auth
def index():
	return 'Index'

或

def index():
	return "Index"

self.add_url_rule(rule='/index.html', endpoint="index", view_func=index, methods=["GET","POST"])
or
app.add_url_rule(rule='/index.html', endpoint="index", view_func=index, methods=["GET","POST"])
app.view_functions['index'] = indexdef auth(func):
	def inner(*args, **kwargs):
		print('before')
		result = func(*args, **kwargs)
		print('after')
		return result
	return inner

class IndexView(views.View):
	methods = ['GET']
	decorators = [auth, ]

	def dispatch_request(self):
		print('Index')
		return 'Index!'

app.add_url_rule('/index', view_func=IndexView.as_view(name='index'))  # name=endpoint

或

class IndexView(views.MethodView):
	methods = ['GET']
	decorators = [auth, ]

	def get(self):
		return 'Index.GET'

	def post(self):
		return 'Index.POST'

app.add_url_rule('/index', view_func=IndexView.as_view(name='index'))  # name=endpoint
注册路由原理

4.4 @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'] = 'joe1991.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、租一个域名    joe1991.com
2、租一个公网IP  127.xxx.xxx.xxx
3、域名解析      joe1991.com    127.xxx.xxx.xxx
4、把代码放在127.xxx.xxx.xxx这个服务器上,程序运行起来用户可以通过IP进行访问

方式二:如果是自己测试用的就可以用这种方式。先在自己本地的文件中找
C:\Windows\System32\drivers\etc  找到hosts,修改配置
然后把域名修改成自己的本地服务器127.0.0.1
加上配置:app.config["SERVER_NAME"] = "joe1991.com:5000"
View Code
from flask import Flask, views, url_for

app = Flask(__name__)
app.config['SERVER_NAME'] = 'joe1991.com:5000'   # 必须进行设置

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

# 静态子域名
@app.route("/", subdomain="admin")
def static_domain():
    return "Static Domain"

# 动态子域名
@app.route("/dynamic", subdomain="<subdomain>")
def dynamic_subdomain(subdomain):
    return subdomain + ".domain"

if __name__ == '__main__':
    app.run()



# C:\Windows\System32\drivers\etc\hosts文件添加上以下设置
127.0.0.1 joe1991.com
127.0.0.1 admin.joe1991.com
子域名示例

访问效果:

image

4.5 自定制正则路由匹配

from flask import Flask,url_for
from werkzeug.routing import BaseConverter

app = Flask(import_name=__name__)

# 步骤一:定制类
class RegexConverter(BaseConverter):  # 必须继承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

# 步骤二:添加到转换器
app.url_map.converters['reg'] = RegexConverter
"""
1. 用户发送请求
2. flask内部进行正则匹配
3. 调用to_python(正则匹配的结果)方法
4. to_python方法的返回值会交给视图函数的参数
5. 如果需要反向生成,再执行to_url()方法

"""

# 步骤三:使用自定义正则
@app.route('/index/<reg("\d+"):nid>',endpoint='xxx')     # <reg("\d+")可以指定参数类型
def index(nid):
    print(url_for('xxx', nid=nid))
    return 'Index'


if __name__ == '__main__':
    app.run()
View Code

4.6 其他补充

添加路由关系的本质:将url和视图函数封装成一个Rule对象,添加到Flask的url_map字段中

五 视图

Flask中的视图也存在FBV和CBV两种方式。

5.1 CBV

我们先简单回顾以下Django中的CBV模式:

# urls.py
url(r^login_cvb/$, views.Login_cbv.as_view()),  # Login_cbv是类名,必须在后面调用as_views()
	

# views.py
from django.views import View


class Login_cbv(View):
	def get(self, request):   # get请求执行代码
		return render(request,'login_cbv.html')
		
	def post(self, request):   # post请求执行代码
		return HttpReSponse('...')
	
	def delete(self,request):
		pass

Flask中的CBV与其类似,具体如下:

import functools
from flask import Flask,views,url_for


app = Flask(__name__)

def auth(func):
    @functools.wraps(func)
    def inner(*args, **kwargs):
        print('before')
        result = func(*args, **kwargs)
        print('after')
        return result
    return inner


class IndexView(views.MethodView):
    # methods = ['GET']  # 只允许GET请求访问
    decorators = [auth, ]  # 如果想给所有的请求加装饰器,就可以这样来写,也可以单个指定

    def get(self,*args,**kwargs):  # 如果是get请求需要执行的代码
        v = url_for('xxx')
        print(v)
        return "GET"

    def post(self,*args,**kwargs):  # 如果是post请求执行的代码
        return "POST"


app.add_url_rule('/index',None, view_func=IndexView.as_view(name='xxx'))  # name指定的是别名,会当做endpoint使用

if __name__ == '__main__':
    app.run()

5.2 FBV

参考第四节

六 请求和响应

from flask import Flask
from flask import request
from flask import render_template
from flask import redirect
from flask import jsonify
from flask import make_response

app = Flask(__name__)

@app.route('/index.html', methods=['GET', "POST"])
def index():
    # 请求相关信息
    # request.method  # HTTP请求方法,例如:GET或POST
    # request.args  # GET请求,字典形式,存储通过URL查询字符串传递的所有参数   request.querystring这个也是GET请求,bytes形式
    # request.form  # POST请求,字典形式,存储请求提交的所有表单字段
    # request.values  # 字典形式,form和args的合集
    # request.cookies  # 字典形式,存储请求的所有cookie
    # request.headers  # 字典形式,存储请求的所有HTTP首部
    # request.path  # URL的路径部分  http://localhost/app/index.html?x=y返回/index.html
    # request.full_path  # URL的路径和查询字符串部分   http://localhost/app/index.html?x=y返回/index.html?x=y
    # request.script_root  # http://localhost/app/index.html?x=y返回/app
    # request.url  # 请求的完整URL  http://localhost/app/index.html?x=y返回http://localhost/app/index.html?x=y
    # request.base_url  # 同url,但没有查询字符串部分
    # request.url_root  # 同host_url
    # request.host_url  # http://127.0.0.1:5000/index?x=y返回http://127.0.0.1:5000/
    # request.host  # 请求定义的主机名,如果客户端定义了的端口号,还包括端口号  http://127.0.0.1:5000/index?x=y返回127.0.0.1:5000
    # 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')
    # return jsonify({'k1': 'v1'})  相当于Django中的JsonResponse

    # 定制响应头
    # response = make_response("字符串")    # response是flask.wrappers.Response类型
    # response.delete_cookie('key')
    # response.set_cookie('key', 'value')
    # response.headers['X-Something'] = 'value'
    # return response


if __name__ == '__main__':
    app.run()
View Code

补充1:错误处理

如果仅仅想终止一个请求并返回错误,而不是重定向到其他地址,我们可以使用abort()函数。

from flask import Flask,abort


app = Flask(__name__)

@app.route("/")
def index():
    abort(403)
View Code

补充2:自定义错误页面

from flask import Flask,abort


app = Flask(__name__)

@app.route("/")
def index():
    abort(404)

# errorhandler装饰器添加了自定义的错误处理器,当程序中返回404错误时,系统会自动执行page_not_found()函数。
@app.errorhandler(404)
def page_not_found(e):  # 必须接受参数,错误信息
    return render_template('404.html'), 404
View Code

补充说明:如果使用了蓝图,则装饰器为@蓝图名称.app_errorhandler(xxx)

七  模板

模板是包含响应文本的文件,其中包含用占位变量表示的动态部分,其具体值只在请求的上下文中才能知道。使用真实值替换变量,再返回最终得到的响应字符串,这一过程被称为渲染。为了渲染模板,Flask使用一个名为Jinja2的强大模板引擎。

7.1 渲染模板

hello.py

from flask import Flask, render_template


app = Flask(__name__)


@app.route('/')
def index():
    return render_template('index.html')


@app.route('/user/<name>')
def user(name):
    return render_template('user.html', name=name)

默认情况下,Flask在应用目录中的templates子目录里寻找模板,所以我们得新建一个templates子目录,然后在里面定义模板文件。

Jinja2模板:index.html

<h1>Hello World!</h1>

Jinja2模板:user.html

<h1>Hello {{ name }}!</h1>

视图函数user()返回的响应包含一个使用变量表示的动态部分,user.html使用模板实现了这个响应。

Flask提供的render_template()函数把Jinja2模板引擎集成到了应用中。这个函数的第一个参数是模板的文件名,随后的参数都是键值对,表示模板中的变量对应的具体值。

上例中的name=name是经常使用的关键字参数,左边的name表示参数名,就是模板中使用的占位符;右边的name是当前作用域中的变量,表示同名参数的值。两侧使用相同的变量名很常见,但不是强制要求。

7.2 变量

在上面user.html模板中{{ name }}结构表示一个变量,这是一种特使的占位符,告诉模板引擎这个位置的值从渲染模板时使用的数据中获取。

Jinja2能识别所有类型的变量,甚至一些复杂的类型,比如:列表、字典和对象。可以执行Python语法,如:dict.get(),dict[‘xx’], list[index]。简单示例如下:

#后端
def index():
    context = {
	'l1':['joe1', 'joe2', 'joe3'],
        'd1':{'name': 'joe', 'age': 18},
    }
    return render_template('index.html', **context)

#前端index.html
{{l1.0}}
{{l1[0]}}

{{d1.name}}
{{d1.get('name')}}
{{d1['name']}}
View Code

变量的值可以使用过滤器修改。过滤器添加在变量名之后,二者之间用竖线分割。例如:需要将name变量的值变成首字母大写的形式:

<h1>Hello {{ name|capitalize }}!</h1>

Jinja2常见过滤器列表如下:

过滤器名

说明

safe

渲染值时不转义

capitalize

把值的首字母转换为大写,其他字母转换为小写 

lower

把值转换为小写形式

upper

把值转换为大写形式

title

把值中每个单词的首字母转换为大写

trim

把值的首尾空格删掉 

striptags

 渲染之前把值的所有的HTML标签都删掉

7.3 控制结构

Jinja2提供了多种控制结构,可以用来改变模板的渲染流程。简单介绍入下:

7.3.1 条件判断语句

{% if user %}
    Hello,{{ name }}!
{% else %}
    Hello,Stranger!
{% endif %}

7.3.2 for循环

<ul>
    {% for comment in comments %}
        <li{{ comment }}></li>
    {% endfor %}
</ul>

7.3.3 宏

宏类似于Python代码中的函数。应用场景:定义的东西需要在很多地方使用时。例如:

{% macro render_comment(comment) %}  <!--render_comment指宏名,可以自定义-->
    <li>{{ comment }}</li>
{% endmacro %}

<ul>
    {% for comment in comments %}
        {{ render_comment(comment) }}>
    {% endfor %}
</ul>

为了重复使用宏,可以把宏保存在单独的文件中,然后在需要使用的模板中导入:

{% import 'macros.html' as macros %}

<ul>
    {% for comment in comments %}
        {{ macros.render_comment(comment) }}>
    {% endfor %}
</ul>

7.4 include

需要在多处重复使用的模板代码片段可以写入单独的文件,再引入所有模板中,以避免重复,这时我们需要使用include。

# form.html
<form>
	asdfasdf
	asdfasdf
	asdf
	asdf
</form>


# index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
	{{l1.0}}
	{{l1[0]}}

	{{d1.name}}
	{{d1.get('name')}}
	{{d1['name']}}

    {% include "form.html" %}
</body>
</html>
View Code

7.5 模板继承

处理Include外,还有一种重复使用代码的强大方式——模板继承。

创建一个基模板:base.html

<html>
<head>
    {% block head %}
    <title>{% block title %}{% endblock %} - My Application</title>
    {% endblock %}
</head>
<body>
    {% block body %}
    {% endblock %}
</body>
</html>
View Code

基模板中定义的区块可在衍生模板中覆盖。jinja2使用block和endblock指令在基模板中定义内容区块。在本示例中,我们定义了head、title和body区块。其中,title包含在head内。下面展示基模板的衍生模板:

{% extends "base.html" %}

{% block title %}
    Index
{% endblock %}
{% block head %}
    {{ super() }}
    <style></style>
{% endblock %}
{% block body %}
    <h1>Hello World!</h1>
{% endblock %}
View Code

extends指令声明这个模板衍生自base.html。在extends指令之后,基模板中的3个区块被重新定义,模板引擎会将其插入适当的位置。如果基模板和衍生模板中的同名区块中都有内容,衍生模板中的内容将显示出来。在衍生模板的区块里可以调用super(),引用基模板中同名区块中的内容。

7.6 自定义方法

#后端
def func(a,b):
	return a+b

@app.route('/index')
def index():
    return render_template("index.html",func=func)


#前端index.html
<h1>{{ func(1,2)}}</h1>
View Code

Flask和Django中传入函数的区别:

  • Django,自动执行
  • Flask,不自动执行

7.7 全局函数(template_global和template_filter)

@app.template_global()
def func1(a1, a2):
    return a1 + a2

@app.template_filter()
def func2(a1, a2, a3):
    return a1 + a2 + a3
	
#调用方式:{{func1(1,2)}}  {{ 1|func2(2,3)}}

补充:可用于if判断

7.8 安全

为了防止xss攻击,有以下两种解决办法:

#后端解决
from flask import Markup
t1 = Markup("<input type='text' />")
{{t1}}

#前端解决
t2 = "<input type='text'/>",
{{ t2|safe }}

注意:Markup等价django的mark_safe

简单说明:向前端传递的不仅可以是单纯的字符串,还可以包含HTML特殊字符(比如:<、>、空格、/等),这给模板参数提供了更好的灵活性。同时,由于这些特殊字符会被HTML客户端解释成特殊含义,因此会给网站带来一定程度的安全隐患。Flask允许我们自己控制jinja2是否需要解释这些特殊字符。如果这些字符应该被解释成特殊含义,则将这些参数直接传递给前端,如果这些参数应该被解释成字符串,则应该通过Markup()函数将这些字符串做转义处理,再传递给前端。

八 Session

除请求对象之外,还有一个 session 对象。它允许在不同请求间存储特定用户的信息。它是在 Cookies 的基础上实现的,并且对 Cookies 进行密钥签名要使用会话,需要设置一个密钥。

  • 设置:session['username'] = 'xxx'
  • 删除:session.pop('username', None)
from flask import Flask,url_for,session

app = Flask(__name__)
app.secret_key = "abcdefg"  # 重点

@app.route('/ses')
def ses():
    # session本质上操作的是字典。session是否还有其他方法?答案是与字典方法相同
    # ession的原理:当请求到来时,flask读取cookie中session对应的值:xxx,将该值解密并反序列化成字典,放入内存以便视图函数使用
    # 假设session保存在数据库,每执行一次链接一次数据库,每次都要时时更新的话会非常损耗内存
    print(session)  # <SecureCookieSession {}>
    print(type(session))  # <class 'werkzeug.local.LocalProxy'>
    session["k1"] = 123
    session["k2"] = 456
    session["k3"] = 789
    del session["k2"]  # 在这删除后,真正存储的时候是没有k2的
    return "Session"
    # 当请求结束时,flask会读取内存中字典的值,进行序列化+加密,写入到用户cookie中。

if __name__ == '__main__':
    app.run()

原理简图如下:

image

关于session的配置

'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,  		#是否每次访问更新超时时间
'PERMANENT_SESSION_LIFETIME':           timedelta(days=31)  # 超时时间
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'
基本使用
pip3 install Flask-Session

        run.py
            from flask import Flask
            from flask import session
            from pro_flask.utils.session import MySessionInterface
            app = Flask(__name__)

            app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'
            app.session_interface = MySessionInterface()

            @app.route('/login.html', methods=['GET', "POST"])
            def login():
                print(session)
                session['user1'] = 'alex'
                session['user2'] = 'alex'
                del session['user2']

                return "内容"

            if __name__ == '__main__':
                app.run()

        session.py
            #!/usr/bin/env python
            # -*- coding:utf-8 -*-
            import uuid
            import json
            from flask.sessions import SessionInterface
            from flask.sessions import SessionMixin
            from itsdangerous import Signer, BadSignature, want_bytes


            class MySession(dict, SessionMixin):
                def __init__(self, initial=None, sid=None):
                    self.sid = sid
                    self.initial = initial
                    super(MySession, self).__init__(initial or ())


                def __setitem__(self, key, value):
                    super(MySession, self).__setitem__(key, value)

                def __getitem__(self, item):
                    return super(MySession, self).__getitem__(item)

                def __delitem__(self, key):
                    super(MySession, self).__delitem__(key)



            class MySessionInterface(SessionInterface):
                session_class = MySession
                container = {}

                def __init__(self):
                    import redis
                    self.redis = redis.Redis()

                def _generate_sid(self):
                    return str(uuid.uuid4())

                def _get_signer(self, app):
                    if not app.secret_key:
                        return None
                    return Signer(app.secret_key, salt='flask-session',
                                  key_derivation='hmac')

                def open_session(self, app, request):
                    """
                    程序刚启动时执行,需要返回一个session对象
                    """
                    sid = request.cookies.get(app.session_cookie_name)
                    if not sid:
                        sid = self._generate_sid()
                        return self.session_class(sid=sid)

                    signer = self._get_signer(app)
                    try:
                        sid_as_bytes = signer.unsign(sid)
                        sid = sid_as_bytes.decode()
                    except BadSignature:
                        sid = self._generate_sid()
                        return self.session_class(sid=sid)

                    # session保存在redis中
                    # val = self.redis.get(sid)
                    # session保存在内存中
                    val = self.container.get(sid)

                    if val is not None:
                        try:
                            data = json.loads(val)
                            return self.session_class(data, sid=sid)
                        except:
                            return self.session_class(sid=sid)
                    return self.session_class(sid=sid)

                def save_session(self, app, session, response):
                    """
                    程序结束前执行,可以保存session中所有的值
                    如:
                        保存到resit
                        写入到用户cookie
                    """
                    domain = self.get_cookie_domain(app)
                    path = self.get_cookie_path(app)
                    httponly = self.get_cookie_httponly(app)
                    secure = self.get_cookie_secure(app)
                    expires = self.get_expiration_time(app, session)

                    val = json.dumps(dict(session))

                    # session保存在redis中
                    # self.redis.setex(name=session.sid, value=val, time=app.permanent_session_lifetime)
                    # session保存在内存中
                    self.container.setdefault(session.sid, val)

                    session_id = self._get_signer(app).sign(want_bytes(session.sid))

                    response.set_cookie(app.session_cookie_name, session_id,
                                        expires=expires, httponly=httponly,
                                        domain=domain, path=path, secure=secure)
自定义Session
#!/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

九 闪现(flash)

session存在在一个字典里面,session保存起来,取一次里面还是有的,直到删除之后才没有。

闪现flash的本质:flash是基于session创建的,在session中存储一个数据,读取时通过pop将数据移除,也就是说,只能取一次值。

仅调用flash()函数并不能把消息显示出来,必须要结合get_flash_message()函数。

from flask import Flask, flash, get_flashed_messages


app = Flask(__name__)
app.secret_key = 'abcdefg'  # 重要,否则报错


@app.route('/page1')
def page1():
    flash('临时数据存储')
    return 'Session'


@app.route('/page2')
def page2():
    print(get_flashed_messages())
    return 'Session'


if __name__ == '__main__':
    app.run()
简单示例
from flask import Flask, flash, get_flashed_messages


app = Flask(__name__)
app.secret_key = 'abcdefg'  # 重要,否则报错

@app.route('/page1')
def page1():
    flash('xxxxx',category='xxx')
    flash('xxxxxxx',category='xxx')
    flash('ooooo',category='ooo')
    return 'Session'


@app.route('/page2')
def page2():
    print(get_flashed_messages(category_filter=['xxx']))   # ['xxxxx', 'xxxxxxx']
    return 'Session'


if __name__ == '__main__':
    app.run()
简单示例--分类
# test.py
from flask import Flask, flash, get_flashed_messages,render_template,url_for


app = Flask(__name__)
app.secret_key = 'abcdefg'  # 重要,否则报错

@app.route('/page1')
def page1():
    flash('xxxxx',category='xxx')
    flash('xxxxxxx',category='xxx')
    flash('ooooo',category='ooo')
    return render_template('test.html')


if __name__ == '__main__':
    app.run()

# test.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>flash测试</h1>
    {% for message in get_flashed_messages() %}
{#    {% for message in get_flashed_messages(category_filter=['xxx']) %}#}
    <P>{{ message }}</P>
    {% endfor %}
</body>
</html>
简单示例--前端使用

补充说明:我们最好在基模板中渲染闪现消息,这样所有页面都能显示需要闪现的消息。

十 中间件

首先,我们知Flask的运行基于werkzeug模块中的run_simple()。先看一个简单的例子:

from flask import Flask

app = Flask(__name__)

@app.route('/index')
def index():
    print('index')
    return 'Index'

if __name__ == '__main__':
    app.run()   # 实际执行run_simple(host, port, self, **options)  self指当前的Flask对象(即为app),最终执行的该对象的__call__方法(用户端访问时,第三个参数加括号运行)

    # __call__方法什么时候执行?
    # 当有用户访问时执行,实际上执行的是:
    '''
    def __call__(self, environ, start_response):
        """The WSGI server calls the Flask application object as the
        WSGI application. This calls :meth:`wsgi_app` which can be
        wrapped to applying middleware."""
        return self.wsgi_app(environ, start_response)
    '''
    # 所以程序的开始是wsgi_app()

所以,如果我们需要在执行__call__方法前后加入其它操作,我们可以修改源码中的__call__方法:

def __call__(self, environ, start_response):
	print('before')
	ret = self.wsgi_app(environ, start_response)
	print('after')
	return ret

但是不建议这么做,我们可以这样做:

from flask import Flask

app = Flask(__name__)

@app.route('/index')
def index():
    print('index')
    return 'Index'

class Middleware(object):
    def __init__(self,old):
        self.old = old

    def __call__(self, *args, **kwargs):  # 参数本质上就是environ,start_response
        print('before')
        ret = self.old(*args, **kwargs)
        print('after')
        return ret

if __name__ == '__main__':
    app.wsgi_app = Middleware(app.wsgi_app)
    app.run()  # 依旧先执行__call__()方法,再执行app.wsgi_app(),但此时的app.wsgi_app=Middleware(app.wsgi_app),即是执行Middleware(app.wsgi_app)(),实则执行Middleware类中的__call__方法,当然得先实例化。

最后说明一点:Flask中的中间件和Django中的中间件是完全不同的。

十一 扩展(特殊的装饰器)

11.1 before_request&after_request(***)

from flask import Flask

app = Flask(__name__)

@app.before_request  # 先定义,先执行
def before1():
    print('before1')
    # return '在执行第一个before_request返回'

@app.before_request
def before2():
    print('before2')

@app.after_request  # 先定义,后执行
def after1(response):    # 有参数,且必须返回值
    print('after1')
    return response

@app.after_request
def after2(response):    # 有参数,且必须返回值
    print('after2')
    return response

@app.route('/index')
def index():
    print('index')
    return 'Index'

if __name__ == '__main__':
    app.run()
View Code

上述代码执行顺序为:before1—>before2—>index—>after2—>after1

当在执行before1时出现return,执行效果为:before1—>after2—>after1

重点说明:这个功能才是类似Django中的中间件,图示一下Django与Flask的执行:

image

11.2 template_global&template_filter

请参考:7.7 全局函数

11.3 before_first_request

from flask import Flask

app = Flask(__name__)

@app.before_first_request   # 当程序运行起来,第一次请求来的时候执行,只执行一次
def x1():
    print('before_first_request1')

@app.route('/index')
def index():
    print('index')
    return 'Index'

if __name__ == '__main__':
    app.run()
View Code

11.4 errorhandler

请参考:第六节补充二

十二 其他

12.1 实现验证用户是否登录的几种方式

方式一:普通方式
@app.route('/index')
def index():
	if not session.get('user'):
		return redirect(url_for('login'))
	return render_template('index.html')


方式二:装饰器方式
import functools
def auth(func):
	@functools.wraps(func)  # 不加该语句,会报错,因为名称重合
	def inner(*args,**kwargs):
		if not session.get('user'):
			return redirect(url_for('login'))
		ret = func(*args,**kwargs)
		return ret
	return inner

@app.route('/index')
@auth
def index():
	return render_template('index.html')

应用场景:比较少的函数中需要额外添加功能


版本三:before_request方式
@app.before_request
def xxx():
	if request.path == '/login':
		return None  # 代表通过
	if session.get('user'):
		return None
	return redirect('/login')
View Code

12.2 wsgi补充

wsgi即为web服务网关接口,它就是一个协议,目前接触到的实现该协议的模块:

  • wsgiref
  • werkzeug

实现该协议模块的本质上就是socket服务端用于接收用户请求,并处理。一般web框架基于wsgi实现,这样可以很方便的实现关注点分离(Tornado除外)。

from wsgiref.simple_server import make_server

def run_server(environ, start_response):
	start_response('200 OK', [('Content-Type', 'text/html')])
	return [bytes('<h1>Hello, web!</h1>', encoding='utf-8'), ]


if __name__ == '__main__':
	httpd = make_server('127.0.0.1', 8000, run_server)    # 请求一进来,run_server函数加()运行
	httpd.serve_forever()
wsgiref示例
from werkzeug.wrappers import Response
from werkzeug.serving import run_simple

def run_server(environ, start_response):
	response = Response('hello')
	return response(environ, start_response)  #必须作为对象返回

if __name__ == '__main__':
	run_simple('127.0.0.1', 8000, run_server)  # 当客户端访问时,run_server加括号运行
werkzeug示例
from werkzeug.wrappers import Response
from werkzeug.serving import run_simple

class Flask(object):
	def __call__(self,environ, start_response):
		response = Response('hello')
		return response(environ, start_response)

	def run(self):
		run_simple('127.0.0.1', 8000, self)  # self指app这个对象,加()运行,即执行Flask中的__call__方法

app = Flask()

if __name__ == '__main__':
	app.run()  # 执行run()
Flask源码入口

12.3 静态文件

Web应用不是仅由Python代码和模板组成。多数应用还会使用静态文件,例如:模板中HTML代码应用的图像、JavaScript源码文件和CSS。示例如下:

<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">  <!--结果是:http://localhost:5000/static/favicon.ico-->
<!--<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/styles.css') }}">-->
<link rel="stylesheet" type="text/css" href="/static/css/styles.css/"> 

十三 Flask插件

 

参考:https://www.cnblogs.com/wupeiqi/articles/7552008.html

posted @ 2019-05-15 16:40  Joe1991  阅读(130)  评论(0)    收藏  举报