flask

image.png

image.png

Flask是Python编写的一款轻量级Web应用框架。其 WSGI 工具箱采用 Werkzeug ,模板引擎则使用 Jinja2。Flask使用 BSD 授权。其中两个环境依赖是Werkzeug和jinja2,这意味着它不需要依赖外部库。正因如此,我们将其称为轻量级框架。

Flask会话使用签名cookie让用户查看和修改会话内容。它会记录从一个请求到另一个请求的信息。不过,要想修改会话,用户必须有密钥Flask.secret_key。

基本流程

from flask import Flask
image.png
image.png
image.png
image.png

In [5]: app.url_map
Out[5]: 

Map([<Rule '/' (HEAD, OPTIONS, GET) -> hello_world>,
 <Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>])

In [4]: app.url_map.converters
Out[4]: 
{'any': werkzeug.routing.AnyConverter,
 'default': werkzeug.routing.UnicodeConverter,
 'float': werkzeug.routing.FloatConverter,
 'int': werkzeug.routing.IntegerConverter,
 'path': werkzeug.routing.PathConverter,
 'string': werkzeug.routing.UnicodeConverter,
 'uuid': werkzeug.routing.UUIDConverter}

路由视图函数

正则匹配路由

image.png

int
@app.route('/<int:age>')
def age(age):
    return 'here age is %d'%age

float
@app.route('/<float:hight>')
def age(hight):
    return 'here hight is %f'%hight

path
@app.route('/index/<path:name>')
def str(name):
    return 'welcome %s'%name

image.png

1 自定义转换器类,继承自BaseConverter
class MyRegexConverter(BaseConverter):
 2 重写init方法
    def __init__(self,map,regex):
 3 初始化父类空间,子类空间(规则)
	super(MyRegexConverter, self).__init__(map)    在父类的init中就有map参数,
	     完成了父类初始化成员变量

	self.regex = regex

4 将自定义的转换器添加到系统默认的转换器列表中,创建一个键值对添加
app.url_map.converters["re"] = MyRegexConverter


#url_map当成第一个参数map传入到init中

#输出系统默认的转换器列表
print(app.url_map.converters)

#使用自定义的转换器
#接收三位整数

#使用re(规则),调用init的时候,实际上是传递了两个参数, 
参数1: app.url_map, 
参数2: \d{3}规则

@app.route('/<re("\d{3}"):number>')
def get_three_number(number):

    return "the three number is %s"%number

@app.route('/',methods=['POST'])
def main():
    return '<h1>this is main page</h1>'

视图函数

image.png

make_response()函数可以接受1/2/3个参数,跟视图函数的返回值一样, (响应内容,状态码,首部字典)
并返回一个response对象。

from flask import make_respoons
@app.route('/')
def main():
    se = make_response("hello world")
    se.status = "888"

    # se.headers是一个字典
    se.headers["Content-Type"]="application/json"
    return se

image.png

    dict = {
        "name":"laowang",
        "age":13
    }
se = jsonify(dict)
 return se

image.png

重定向

@app.route('/demo5')
defdemo5():
return redirect('http://www.itheima.com')
@app.route('/'):
	return redirect(url_for('index'))

url_for
from flask import Flask,url_for,redirect

app = Flask(__name__)

@app.route('/jingdong')
def jingdong():

    print(url_for("taobao",token=100)) #得到是taobao视图函数的地址
    return redirect(url_for("taobao",token=100))


@app.route('/suning')
def suning():

    print(url_for("taobao",token=200)) #得到是taobao视图函数的地址
    return redirect(url_for("taobao",token=200))


@app.route('/taobao/<int:token>')
def taobao(token):

    #判断重定向过来的token值
    if token == 100:
        return "欢迎京东客户,给你打骨折"
    elif token == 200:
        return "欢迎苏宁客户,给你打9折"
    else:
        return "欢迎其他网站客户,给你9.9折"


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

生成连接程序被不同路由的链接时,使用 相对地址足够
如果要生成在浏览器之外使用的链接,必须使用绝对路径。

image.png

from flask import Flask, abort

app = Flask(__name__)

@app.route('/<int:age>')
def play_game(age):

    #判断玩家年龄
    if age > 18:
        return "英雄联盟"
    elif age > 16:
        return "暴力摩托"
    else:
 abort(404)

#捕捉404异常
@app.errorhandler(404)
def page_not_found(e):
    print(e)
    return "<h1 style='color:red'>游戏服务器搬家了</h1>"

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


自定义错误页面
@app.errorhandle(404):
    return render_templtate('404.html'),404

@app.errorhandle(500):
    return render_templtate('500.html'),500

如统一处理状态码为500的错误
@app.errorhandler(500)
def internal_server_error(e):
  return'服务器搬家了'


捕获指定异常
@app.errorhandler(ZeroDivisionError)
def zero_division_error(e):
  return'除数不能为0'

request对象

from flask import request

请求钩子

@app.before_first_request
def xxx():
    在第一次访问时做的准备工作


@app.before_request
def before_request():
    age = request.args.get('age')
    if int(age) >18:
        return 'welcome'
    else:
        return 'age not enough'

对于页面请求发送的拼接数据,进行判断和后续操作,
一旦在这里有return,后面的@app.route()将不会执行,页面显示在判断中的return值

@app.route('/')
def hello():
    return 'main page'


@app.after_request
def after_request(rawreturn):

    print(rawreturn) >><se 7 bytes [200 OK]>

    rawreturn.status='444 not found'
    resp.headers["Content-Type"] = "application/json"
    return rawreturn

@app.teardown_request
def over(e):
    print(e)
    print('it\'s over time work')


在请求钩子函数和视图函数之间共享数据一般使用上下文全局变量  g 

例如:
before_request 处理程序可以从数据库中加载已登录用户,并将其保存到g.user中
随后调用视图函数时,视图函数再使用g.uesr获取用户

状态保持
http是一种无状态协议,浏览器请求服务器是无状态的。

协议对于事务处理没有记忆能力
对同一个 url 请求没有上下文关系
每次的请求都是独立的,它的执行情况和结果与前面的请求和之后的请求是无直接关系的,它不会受前面的请求应答情况直接影响,也不会直接影响后面的请求应答情况
服务器中没有保存客户端的状态,客户端必须每次带上自己的状态去请求服务器

无状态:指一次用户请求时,浏览器、服务器不知道之前这个用户做过什么,每次请求都是一次新的请求。
无状态原因:浏览器与服务器是使用 socket 套接字进行通信的,服务器将请求结果返回给浏览器之后,会关闭当前的 socket 连接,而且服务器也会在处理页面完毕之后销毁页面对象。
有时需要保持下来用户浏览的状态,比如用户是否登录过,浏览过哪些商品等

实现状态保持的两种方式
在客户端存储信息使用Cookie
cookie

指某些网站为了辨别用户身份、进行会话跟踪而储存在用户本地的数据(通常经过加密)。
Cookie是由服务器端生成,发送给客户端浏览器,浏览器会将Cookie的key/value保存,下次请求同一网站时就发送该Cookie给服务器(前提是浏览器设置为启用cookie)。
Cookie的key/value可以由服务器端自己定义。
复数形式Cookies。
Cookie最早是网景公司的前雇员Lou Montulli在1993年3月的发明。
应用是判定注册用户是否已经登录网站
网站的广告推送
Cookie是存储在浏览器中的一段纯文本信息,建议不要存储敏感信息如密码,因为电脑上的浏览器可能被其它人使用
Cookie基于域名安全,不同域名的Cookie是不能互相访问的 基于浏览器的同源策略
当浏览器请求某网站时,会将本网站下所有Cookie信息提交给服务器,所以在request中可以读取Cookie信息

image.png

# 设置cookie
@app.route('/set_cookie')
def set_cooke():
    # 获取响应体的对象
    res = make_response('set_cookie')

    # 设置cookie
     res.set_cookie('name','cookievalue1')
     res.set_cookie('name1', 'cookievalue2',600)
    return res

# 获取cookie
@app.route('/get_cookie')
def get_cookie():
value1 = request.cookies.get('name')
value2 = request.cookies['name1']

    return 'cookie one is %s,cookie two is %s'%(value1,value2)

在服务器端存储信息使用Session

session
对于敏感、重要的信息,建议要存储在服务器端,不能存储在浏览器中,如用户名、余额、等级、验证码等信息
在服务器端进行状态保持的方案就是Session
Session依赖于Cookie
image.png

app.config["SECRET_KEY"]='asdasdsad'

# 设置session
@app.route('/set_session/<name>')
def set_session(name):
    session['name']=name
    return '设置session'

# 获取session
@app.route('/get_session')
def get_session():
name = session.get('name')
    return 'session name is %s'%name

cookie session本质都是一个个的键值对

上下文
相当于一个容器,保存了flask程序运行过程中的一些信息
image.png

request 不可能是全局变量, 在多线程服务器中,多个线程同时处理不同客户端发送的不同请求时,每个线程看到的request对象必然是不同,
Flask 使用上下文让特定的变量在一个线程中全局可访问,与此同时不会干扰其他线程


线程是可单独管理的最小指令集,
进程经常使用多个活动线程,有时还会共享内存或文件句柄等资源

 多线程web服务器会会创建一个线程池,在从线程池中选择一个线程用于处理接受到的请求
app.config.get('DEBUG')
等同于
current_app.config.get('DEBUG')

g对象
@app.before_request
def before_request():
g.name = "zhangsan"

@app.route('/')
def main():
    return g.name

jinja2模板引擎

Jinja2:是 Python 下一个被广泛应用的模板引擎,是由Python实现的模板语言,他的设计思想来源于 Django 的模板引擎,并扩展了其语法和一系列强大的功能,其是Flask内置的模板语言。
模板语言:是一种被设计来自动生成文档的简单文本格式,在模板语言中,一般都会把一些变量传给模板,替换模板的特定位置上预先定义好的占位变量名。
image.png
image.png
flask的 render_template 函数吧 Jinja2模板引擎集成到程序中,
第一个参数模板的文件名,随后的参数都是键值对,表示模板中变量对应的真实值。

    my_list = [
        {
            "id": 1,
            "value": "我爱工作"
        },

return render_template("1e.html",my_list=my_list)

控制结构

@app.route('/')
def hello_world():

    num = 10
    str = "隔壁老王在练腰"
    tuple = (1,2,3,4)
    list = [5,6,7,8,9]
    dict = {
        "name":"123",
        "age":29
    }
    #携带数据渲染页面
    return render_template("2.html",num=num,str=str,tuple=tuple,list=list,dict=dict)

image.png

前后端的数据交互  小胡子写法
在HTML显示后端发过来的数据  格式。
    <h2>获取整数: {{num + 100}}</h2>
    <h2>获取字符串: {{str}}</h2>
    <h2>获取元祖: {{tuple}}, 分开获取:{{ tuple[0] }}, {{ tuple.1 }}</h2>
    <h2>获取列表: {{list}},分开获取:{{ list.0 }}, {{ list.1 }}</h2>
    <h2>获取字典: {{dict}},分开获取:{{ dict.name }},{{ dict["age"] }} </h2>

用 {%%} 定义的控制代码块,可以实现一些语言层次的功能,比如循环或者if语句
image.png

    <h1>2.取出元祖中所有偶数</h1>
{% for item in tuple %}
        {% if item %2 == 0 %}
            <h2>{{ item }}</h2>
        {% endif %}
 {% endfor %}

loop
{'age': 123, 'name': 'yy'}
{% for key in dict %}
    {% if loop.index==2%}  
		>>loop.index  从1开始迭代,
选择第二次迭代的值。
        <h1>{{ dict[key] }}</h1>   >>yy

    {% elif loop.index0==0 %}
		>> loop.index0 从0开始迭代,
选择第一次迭代的值

        <h6>{{ dict[key] }}</h6>    >> 123

    {% endif %}
{% endfor %}


从1开始迭代
        {% for item in list %}
            {% if loop.index == 1 %}
                <p>{{ item }}</p>    >>获取第一次迭代获取到的值
            {% elif loop.index == 2 %}
                <p>{{ item }}</p>    >>获取第两次迭代获取到的值
            {% elif loop.index == 3 %}
                <p>{{ item }}</p>    >>获取第三次迭代获取到的值
            {% else  %}    
               <p>{{ item }}</p>    
            {% endif %}
        {% endfor %}



    {% for item in list if item !=9 %}
	给循环加先前条件,值循环满足条件的列表
            {{ item }}


    {% endfor %}

    <h1>3.遍历字典</h1>
 {% for key in dict %}
{# dict.key中的key是一个字符串,  dict[key]中的key是个变量 #}
        <h2>{{ key }} = {{ dict[key] }}</h2>

   {% endfor %}

循环遍历用的是一层 {} 中间添加%%来控制程序块

过滤器

image.png

safe
默认情况下,出于安全考虑,
Jinja2会转义所有变量。
例如 一个变量的值为 '<h1> hello </h1>'
jinja2会将其渲染为  '&lt;h1&gt; hello &lt;/&h1&gt;
浏览器只能显示这个h1元素,但不会进行解释。
很多情况下需要显示变量中存储的HTML代码,这时就可使用safe过滤器
别再不可信的值上使用safe过滤器,例如用户再表单中输入的文本

image.png
image.png

自定义过滤器

```需求:
1.求列表中所有偶数和
2.实现列表反转

from flask import Flask,render_template

app = Flask(__name__)

方法1 
先定义函数,再将函数添加到过滤器列表中
def oushu_sum(list):

    sum = 0
    for item in list:
        if item %2 == 0:
            sum += item
    return sum

app.add_template_filter(oushu_sum,"OUSHU_SUM")

方法2
定义函数的时候, 就使用过滤器装饰
@app.template_filter("my_reverse")
def reverse_function(list):
list.reverse()
    return list

@app.route('/')
def hello_world():

    return render_template("file04custom_filter.html")

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



HTML文件
    <h2>原列表: {{ [1,2,3,4,5,6,7] }}</h2>
    <h2>原列表偶数和: {{ [1,2,3,4,5,6,7] | OUSHU_SUM}}</h2>
    <h2>原列表反转: {{ [1,2,3,4,5,6,7] | my_reverse}}</h2>

    {# 使用系统提供的过滤器和自定义过滤器实现降序输出 #}
    <h3> 升序: {{ [5,8,1,3,9] | sort}} </h3>
    <h3> 降序: {{ [5,8,1,3,9] | sort | my_reverse}} </h3>

模板代码复用

image.png

    {% macro inputmode(name,password) %}
        <p>username: <input type="text" name="username" value="{{ name }}"></p>
        <p>password: <input type="password" name="password" value="{{ password }}"></p>
        <p><input type="submit" value="提交按钮"></p>
    {% endmacro %}


其他文件使用宏
  其他文件other_macro.html
{% macro input(name,password) %}
<label>{{ name }}:</label><input type="text" name="username"><br>
<label>{{ password }}:</label><input type="password" name="username"><br>
{% endmacro %}




从other_macro.html引入宏
    {% import 'other_macro.html' as other_macro %}
    {{ other_macro.input2('用户名',"密码") }}
    {{ other_macro.input3() }}

    {% block contentmodel %}
    <h1>静夜思</h1>
    <h2>床前我明月光</h2>
    <h2>疑似他地上霜</h2>
    <h2>举头那望明月</h2>
    <h2>低头他思故乡</h2>

    <h3>作者: 李白</h3>
    {% endblock %}


super
{% block contentmodel %}
    {{ super()}}
    <h1>这是重写的父类模板</h1>
{% endblock %}

image.png
image.png

模板特有的变量和函数

image.png


@app.route('/index2/<int:num>')
def index2(num):
    return num

flask-moment 本地化日期和时间
image.png

CSRF

image.png
image.png
csrf_token阻止CSRF攻击
实现csrf 保护, flask-WTF需要程序设置一个秘钥,
flask-WTF使用这个秘钥生成加密令牌,再用领跑验证请求中表单数据的真伪,
image.png

流程



ajax 给表单设置一个事件   
监听submit

数据库操作

orm

图示



流程

CURD

通过数据库管理对数据库所做的改动,
在flask-SQLAlchemy中,会话由 db.session表示
准备把对象写入数据库之前,先要将其添加到会话中
这里的session 和 上下文中的session没有关系,
数据库会话也称作为事务
数据库会话能保证数据库的一致性。
提交操作使用原子方式把会话中的对象全部写入数据库。
如果在写入回话的过程中发生了错误,整个会话都会失效。
如果始终把相关改动放在会话中提交,就能避免因为部分更新导致的数据库不一致性,
在准备把数据写入数据库前,先将数据添加到会话中然后调用 db.session.commit() 方法提交会话。

通过 query 对象操作数据。

最基本的查询是返回表中所有数据,可以通过过滤器进行更精确的数据库查询

当  模型.query 的时候,就已经把所有数据都给取出来了,
再通过过滤器,执行,得出想要的数据。

过滤器

filter()
把过滤器添加到原查询上,返回一个新查询
filter_by()
把等值过滤器添加到原查询上,返回一个新查询
limit
使用指定的值限定原查询返回的结果
offset()
偏移原查询返回的结果,返回一个新查询
order_by()
根据指定条件对原查询结果进行排序,返回一个新查询
group_by()
根据指定条件对原查询结果进行分组,返回一个新查询


执行器

all()
以列表形式返回查询的所有结果
first()
返回查询的第一个结果,如果未查到,返回None
first_or_404()
返回查询的第一个结果,如果未查到,终止请求、返回404错误响应
get()
返回指定主键对应的行,如不存在,返回None
get_or_404()
返回指定主键对应的行,如不存在,返回404
count()
返回查询结果的数量
paginate()
返回一个Paginate对象,它包含指定范围内的结果


查询数据后删除
user = User.query.first()
db.session.delete(user)
db.session.commit()
User.query.all()

更新数据
user = User.query.first()
user.name = 'dong'
db.session.commit()
User.query.first()

str
srt(User.query.filter_by(role = user_role))

SELECT users.id AS users_id, users_id,users.username AS users_username,
users.role_id AS users_role_id FROM users WHERE : param_1 = users.role_id

Role
class Role(db.Model):
    __tablename__ = "roles"
    id = db.Column(db.Integer,primary_key=True)
    name = db.Column(db.String(16),unique=True)

users = db.relationship("User",backref="role")

    def __repr__(self):
        return "<Role:%s>"%self.name


User
class User(db.Model):
    __tablename__ = "users"
    id = db.Column(db.Integer,primary_key=True)
    name = db.Column(db.String(16),unique=True)

 role_id = db.Column(db.Integer,db.ForeignKey(Role.id))

执行 role.users表达式的时候,隐含的查询会调用 all()返回一个用户列表
query对象是隐藏的,因此无法指定更精确的查询过滤器。

是通过过滤器和执行器的搭配来实现,过滤器可以没有,但一定要执行

表关系

image.png

数据库迁移

追踪数据库模式的变化,然后把变动应用到数据库中
为了备份表结构

迁移流程

使用命令进行迁移操作


from flask.ext.script import Shell

def make_shell_context():
   return dict(app=app,db=db.User=User,Role=Role)
manager.add_command('shell',Shell(make_context = make_shell_context))
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_script import Manager
from flask_migrate import  Migrate,MigrateCommand

app = Flask(__name__)

#2 设置数据库配置信息,关联app对象
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:mysql@localhost:3306/day04'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)


#3使用migrate关联app和db
Migrate(app,db)

#4使用manager 管理app
manager = Manager(app)

#5使用migratecommand 给manager添加操作命令
manager.add_command('db',MigrateCommand)

#6 创建模型类
class Student(db.Model):
    __tablename__ = 'student1'
    id = db.Column(db.Integer,primary_key=1)
    name = db.Column(db.String(16))

@app.route('/')
def hello():
    return 'hello'

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

蓝图 Blueprint

方法1:先在其他文件中定义好方法,再到启动入口装饰视图函数,缺点:耦合性太强,一个地方改动,其他也得动,所以引入了蓝图
方法2: flask中提供的类,不需要安装就可以使用

使用蓝图对象装饰视图函数
文件1

#1 导入蓝图类
from flask import Blueprint

#2 创建蓝图对象
product_blue = Blueprint('blue',__name__)
blue:  该蓝图对象装饰的视图函数到时候都在blue地下,

#3 使用蓝图装饰视图函数
@product_blue.route('/')
def hello():
    return 'hello'

@product_blue.route('/index')
def index():
    return 'index'

将蓝图注册到app对象中
app.register_blueprint(blue_product)

含有__init__文件。
单一的py文件称为模块

#1 导入类
from flask import Blueprint

#2 创建蓝图对象
blue_product = Blueprint('blue',__name__)

#4 引入试图函数
from day4.user import view
(此时不存在循环导入蓝图对象的问题,Python是弱类型语言,只要在内存中存在之后,就不再引入
)

views.py
from day4.user import blue_product
引入蓝图对象来装饰

#3 用蓝图装饰视图函数
@blue_product.route('/')
def hello():
    return 'hello'

@blue_product.route('/')
def index():
    return 'index'

@blue_product.route('/')
def error():
    return 'error'


入口文件
from flask import Flask
from day4.user import blue_product
引入蓝图对象

app = Flask(__name__)

#5 将蓝图对象注册到app中
app.register_blueprint(blue_product)

if __name__ == '__main__':
    print(app.url_map)
    app.run(debug=True)


app.url_map >>
Map([<Rule '/' (HEAD, GET, OPTIONS) -> blue.hello>,
 <Rule '/' (HEAD, GET, OPTIONS) -> blue.index>,
 <Rule '/' (HEAD, GET, OPTIONS) -> blue.error>,
 <Rule '/static/<filename>'
 (HEAD, GET, OPTIONS) -> static>])

from unittest import TestCase

#1 自定义类,继承TestCase
class Mytest(TestCase):

    #2 两个固定方法 setup teardown
    def setUp(self):
        print('setup')
    def tearDown(self):
        print('teardown')

    #3 编写测试方法
    def test_data(self):
        print('testtest')

        self.assert...(boolean,msg)
	断言
直接右键执行,开始测试
编写测试方法
def test_app(self):
    self.assert....()


常见的断言方法
assertEqual     如果两个值相等,则pass
assertNotEqual  如果两个值不相等,则pass
assertTrue      判断bool值为True,则pass
assertFalse     判断bool值为False,则pass
assertIsNone    不存在,则pass
assertIsNotNone 存在,则pass
#coding=utf-8
import unittest
from author_book import *

#自定义测试类,setUp方法和tearDown方法会分别在测试前后执行。以test_开头的函数就是具体的测试代码。
classDatabaseTestCase(unittest.TestCase):
defsetUp(self):
        app.config['TESTING'] = True
        app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:mysql@localhost/test0'
        self.app = app
        db.create_all()

deftearDown(self):
        db.session.remove()
        db.drop_all()

#测试代码
deftest_append_data(self):
        au = Author(name='itcast')
        bk = Book(info='python')
        db.session.add_all([au,bk])
        db.session.commit()
        author = Author.query.filter_by(name='itcast').first()
        book = Book.query.filter_by(info='python').first()
#断言数据存在
        self.assertIsNotNone(author)
        self.assertIsNotNone(book)
posted @ 2019-09-07 17:24  π=3.1415926  阅读(143)  评论(0编辑  收藏  举报