Flask纪要

flask学习过程

1框架基础
2redis高性能key-value数据库
3视图具有装饰器的路由函数
4模板html文件 面向对象操作数据库orm
5蓝图
6单元测试
7GitHub
8项目

学习的目的
1清晰的业务逻辑 2解决bug的能力-->先定位bug 3面向百度变成-->搜索问题的精准程度StackOverflow 4能够灵活应用知识点-->内置函数,列表推导式


web的本质world wide web 利用互联网进行数据交流
客户端 浏览器 ios Android发送请求(用户输入,a,img,Ajax,爬虫) 渲染页面
服务端 服务器脚本->WSGI->应用程序 接收请求 解析请求 路由分发 业务逻辑 返回响应

使用web框架 的目的 安全性 高并发 提高开发效率 稳定 拓展性强

框架--协助开发的功能代码-->按照规定的要求 在指定位置上写业务代码

flask 快速完成项目
djiango 商城项目完备 齐全
tornado 异步框架

flask的wsgi werkzeug-->路由正则 jinja2-->模板的渲染

虚拟环境 为了在同一台电脑上使每个项目都有属于自己的python环境 不然会造成环境覆盖

搭建虚拟环境
sudo pip install virtualenv
sudo pip install virtualenvwrapper

创建建python2版本虚拟环境
mkvirtualenv 环境名 在使用python 在左侧会显示虚拟环境的名字 deacative退出虚拟环境
rmvirtualenv 环境名 删除虚拟环境

创建python3版本虚拟环境
mkvirtualenv -p python3 环境名

进入已创建的虚拟环境 workon tab tab 选择 回车

source activate原生启动虚拟环境

pip install flask == 版本号 安装响应版本的flask

查看已安装的包 pip list

which python 查看python或虚拟环境的所在路径


from flask import Flask
static_path 访问静态文件的路径 已弃用
static_url_path 访问静态文件的路径 指定为none 系统默认为/static
templates_folder 模板文件的文件夹 默认是 templates
static_folder 静态文件的问价夹
host 万能端口=" 0.0.0.0" 只要是本台电脑上的网卡都可以访问改ip


flask程序
from flask import Flask # 遵循PEP8的编码格式 使用哪个模块就import指定名称

app = Flask(__name__#是当前模块的名字,指定模块的绝对路径
static_url_path="/static"# 静态文件的路径
static_folder = "static",#指定静态文件夹的名字
template_folder = "templates"#指定模板文件夹的名字)


#加载配置
#1从类对象中加载
class Config(object):
DEBUG = True--->类属性必须是大写内置函数规定的小写不满足条件
app.config.from_object(Config)

#2从配置文件中加载 config 类似于字典类型
在静态文件夹中创建一个配置文件 里面写上DEBUG = True
app.config.from_pyfile("配置文件的路径")

#3环境变量
新建环境变量 里面新建配置文件
app.config.from_envvar("环境变量的名字")

4app.debug = true

5app.config["DEBUG"] = True
获取配置
app.config.get("DEBUG")

路由的本质是 url绑定 @app.route()装饰器用于把一个函数绑定到一个url上
视图函数的返回的响应可以是包含html的简单字符串 也可以是复杂的表单
同一路由指向多个不同的函数,在匹配过程中,至上而下依次匹配下面的函数并不执行


# 视图函数和路由
@app.route("/",methods = ["POST","GET"]) # 路由
def index(): # 视图函数
return "hello python"

浏览器只能获取get请求方式 post需要使用postman插件

# 动态路由 (带参数的路由)
@app.route("/user1/<int:user_id>"])# int:转换器

#自定义转换器 匹配6位数字
1起转换器的名字regex
2定义一个类继承自父类
class RegexConverter(BaseConverter):
regex = r"[0-9]{6}"
3把名字作为键 类对象作为值添加到系统默认的转换器字典中
app.url_map.converters ["regex"]= RegexConverter

向上查找可以找到父类被调用的地方

request接收请求报文的数据,进行处理
request.data.decode() 接收到的数据是二进制 需要解码
request.form.get("键")-->获取值
request.args.get("键")--.获取值
pic_obj = request.files.get("文件名")-->pic_obj.save("新的名字")

def index(user_id):

字典数据名 = {}
# json_data= json.dumps(字典名)--->字典数据转换位json数据--不常用
# 字典数据 = json.load(json数据)-->json数据转换为字典数据--不常用

return jsonify(字典数据名)--->将字典数据转换为json数据内容格式也会改变 需要导入jsonify库--最常用
return redirect(url_for("函数名"))-->通过函数名找到对应函数return"的网址" 需要导入url_for模块
return user_id
return '状态码为 666', 666

if __name__ == '__main__':
app.run()
app.run(host="主机ip" port=端口 debug=True-->1重启修改后的代码2抛出异常的位置利于开发)


子类重写父类的init方法会实例化子类,我自己称之为实例类对象

获取类中的所有实例对象 使用dir()内置函数

 

状态保持 session(服务端) 与 cookie(客户端)

cookie 是由服务端给客户端的,存储在客户端(通常是加密的),存储cookie的key:value,是纯文本信息 不能存储敏感信息
不同域名的cookie不能共用
cookie同源策略 同域名,同路径,如天猫和淘宝cookie共享
浏览器保存的cookie最大只能由4kb 服务器保存session不限量

cookie
设置cookie response.set_cookie("name", "老王")
cookie 设置过期时间 在设置cookie时设置过期时间 set_cookie(max_age=秒数)
获取cookie request.cookie.get("cookie的名字")
删除cookie在退出时删除cookie delete.cookie("name")


session
session_id 服务器加密之后以cookie的方式发送给浏览器 ,浏览器无法解析,只有服务器能认识session_id
设置session session["name"] = "laowang"设置session需要加密 使用app.secret_key = "aaa"#session秘钥
获取session session.get("name",None)如果没有获取到就返回none
删除session session.pop("name",None) 删除name对应的键值对,没有name就返回None


请求钩子 对所有的视图函数进行管理
@app.before_first_request # 在第一次请求之前会执行 应用场景:链接数据库 因为只需要链接一次
def before_first_request():

@app.before_request # 在每一次请求之前都会执行 可以做权限验证工作
def before_request(): v'c'c'c'c'c'c'c'c'c'c'c'c'c'c'c'c'c'c

@app.after_request # 在每一次请求之后都会执行 可以做扫尾工作 设置响应报文的内容必须接收一个参数作为响应
def after_request(respose):
return response

@app.terar_down_request # 在每一次请求之后都会执行 会捕获到响应中的异常 可以做捕获异常的操作
def terar_down_request(e):

只要访问视图函数都需要走这几步骤 只有第一次链接会触发before_first_request()二次之后就不会

 

主动抛出异常 abort()
http异常的主动抛出 abort()方法
如果是非法id访问 可以抛出404 -->参数是符合http协议的状态码 404 500 302等
可以主动拦截非法ip


捕获异常
@app.errorhandler(符合http协议的状态码-也可以捕获异常类名)<--如果视图函数出现()里的异常就执行下面的代码
def exception_handle(e):全局捕获
return ""


上下文 在代码执行到某一时刻时,根据之前的代码所做的操作以及下文即将要执行的逻辑,可以决定在当前时刻下可以使用到的变量,或者可以完成的事情

请求上下文 request context -包含->request(封装了http请求的内容) 和 session(记录请求会话中的用户信息) 保存了c s交互的数据

应用上下文 application context -->current_app 和 g变量 flask程序运行过程中保存了一些配置信息

current_app -->获取模块名,获取debug的值 和app的功能是类似的
g变量 全局临时变量 g对象-->可以动态添加属性 把g放在请求钩子中所有视图函数都能使用
---!!只能设置在当前文件中


flask script -->sys.argv
app.run 不能通过命令行指定端口,主机ip 以及其它参数 通过flask-script就可以
使用步骤
1 pip install flask-script
2 from flask-script import Manager
3 manager = Manager(app)
4 manager.run()
5 在Edit中添加runserver 参数

 

 

 

模板----->templates
jinja2 是由python开发的一个模板引擎
把变量名给模板替换模板里的内容

创建一个文件夹templates
写一个视图函数进行渲染
指定templates文件夹为templates文件夹
配置模板语言为jinjia2

在视图函数中渲染到模板
return render_template("demo1.html",)多个变量之间使用,隔开


过滤器的本质就是一个函数 |
可以链式操作如 {{ my_str | reverse | lower}}
safe 让带标签的字符串按找格式被浏览器渲染出来

自定义过滤器
1.定义一个过滤器的名字
lireverse
2.定义一个过滤器函数
def do_lireverse(li):
temp = list(li)
retern list(reversed(temp))
3.将名字作为键,函数的引用作为值添加到系统默认的过滤器中
app.add_template_filter(do_lireverse:"lireverse")

f 函数的引用


控制代码块{% for my_dict in my_list%}
{% if loop.index == 1 %}
<li style="background-color: orange">{{ my_dict.value }}</li>
{%endfor%}


模板的继承
父类模板中需要挖坑{% block contentBlock%}{%endblock%} 用来放子类模板自己的内容

子类模板中
{%extens "父类模板名"%}
{% block contentBlock%}
{{super()继承父类他自己的内容}}
子类模板自己的内容
{%endblock%}

 

相同格式的页面抽取父类模板

1 将两个页面进行对比
2 去异存同 将不同之处删除 再删除的位置挖坑 {%block 名字Block%}{% endblock %}
3 起名字可利用标签名或者class的属性进行起名 以便于在继承时进行一一对应
4 抽取完成的页面就是父类模板
5 在子类页面中继承父类模板 {% extends "base_news.html" %}
6 取出父类模板中的坑在里面填上子类模板自己的内容
{% block scriptBlock %}<script type="text/javascript" src="../static/news/js/detail.js"></script>
{% endblock %} 相同的内容继承自父类模板


导入一个模块中的多个类 在被导的模块中使用__all__ = ["类名","类名"]指定要导入的类 使用import * 导入时就是列表中的中

也可以添加一个字典aa = {} 导入时导字典名


模板中特有的变量和函数
config 可以通过模板直接获取
request
session
url_for 通过函数的名字获取函数对应的url


CSRF 跨站请求伪造

访问a进行转账 a未退出cookie存在 去访问b b中访问a的转账(cookie存在) a无法判别访问自己的对象

防止csrf攻击
确定攻击实在哪个代码块产生
区分请求是到底是谁(在转账界面设置cookie)
在webA中的转账界面生成随机数存入cookie中
将随机的cookie传到界面的form表单的隐藏标签中
在转账界面对cookie和界面中的cookie进行比较 相等才能进行转账
由于webB无法获取到webA的隐藏标签,获取它的表单中的cookie值时显示为None 值不等 无法执行转账操作

实际开发中引入CSRFProected 即可


flask 数据库 ORM 将表关系对应到类中 sql语句对应到类的方法
Flask-SQLAlchemy
创建数据库 create database 数据库名(test) chartset utf8

pycharm 创建数据库
点击右侧database -->点击右侧.QL-->创建数据库 create database 数据库名 cahrtset utf8-->刷新-->在下面More中选择添加-->双击使用

链接数据库 数据库 用户名: 密码 地址 端口 数据库名
app.config["SQLALCHEMY_DATABASE_URI"] = "mysql://root:mysql@127.0.0.1:3306/test"

跟踪动态跟踪修改设置,未设置只会提示警告
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = True

查询时显示原生SQL语句
app.config['SQLALCHEMY_ECHO'] = True

实例化数据库sqlalchemy工具对象
db = SQLALchemy(app)


创建模型类
class BookInfo(db.Model): -->继承自实例对象的Model
__tablename__ = "book-info" # 如果不指明就是类名的小写
# 表结构
name = db.Column(db.String(64), unique=True, nullable=Flask)
pub_date = db.Column(db.String(128), nullable=True)
is_delete = db.Column(db.Boolean, default=False)
id = db.Column(db.Integer, primary_key=True)

# 返回对象的name属性 不写的话返回的是对象的地址引用
def __repr__(self):
return self.name


表操作

创建表
db.create_all()

删除表
db.drop_all()

新增一条数据
创建对象,设置属性
book1 = BookInfo()
book1.name = "西游记"
book1.pub_date = "2001-1-1"

添加到数据库中
db.session.add(book1)-----add_all()可以添加多个对象

真正提交数据
db.session.commit()

删除数据
user = PeopleInfo.query.first() -->查询出一个信息
db.session.delete(user)-->删除这条数据
db.session.commit()-->提交删除的操作到数据库
PeopleInfo.query.all()-->查询全部信息确认是否删除成功


更新数据
user = PeopleInfo.query.first() -->查询出一个信息
user.name = "新的值"-->将对象的属性设为新的值
db.session.commit()-->将更新的操作提交到数据库
PeopleInfo.query.first()-->再次查询这条数据确认修改成功

 

----------------------------------------------------->>>>>

查询 可以使用ipython3进行查询,需要导入
from 模块名 import *
类名.query.filter().all()
filter()返回的是一个查询集
all()以列表的方式返回查询的所有结果
类名.query.filter(条件).all()-->模糊查询
类名.query.filter_by(条件).first()-->精确查询


多表联合查询
查询出书下面的所有人物
方式-
book = BookInfo.query.filter(BookInfo.name =="三国演义").first()-->查询出book对象
PeopleInfo.query.filter(PeopleInfo.book_id == book.id).all()-->以列表显示的结果

方式二
book = BookInfo.query.filter(BookInfo.name =="三国演义").first()-->查询出book对象
people = db.relationship("PeopleInfo")-->需要在BookInfo表中添加和PeopleInfo的relationshhip

book.people


backref="book"动态的给PeopleInfo添加了一个book属性-->反向引用
等同于book = db.relationship("BookInfo")-->需要在PeopleInfo表中添加和BookInfo的relationshhip

lazy ="dynamic"--->book.people是一个查询集占用小内存使用,可以近一步操作
------------------------------------------------------>>>>>>>>>

使用闪现flash()需要设置密钥 它依赖与session

 

状态保持
请求钩子
csrf
图书管理案例

 

数据库迁移 -->可以记录版本信息 可以反悔操作 必须先备份数据 不删除表就可以修改表结构防止数据丢失
跟踪设置数据结构 原始数据可以恢复 监听对数据库的操作 必须先备份数据! ! ! ! !

from flask_migrate import Migrate,MigrateCommand
from flask_migrate import Manager,Shell

在flask 中对数据库进行迁移配置
manager = Manager(app)

migrate= Manager(app,db)-->关联
manager.add_command("db", MigrateCommand)


迁移的命令
python xx.py db init 创建一个迁移文件夹(迁移文件的仓库)

python xx.py db migrate -m "注明(修改的内容)"-->生成一个版本文件 将模型添加到迁移文件中 迁移脚本

python xx.py db upgrade 更新数据库中的表 执行迁移 观察表结构

返回旧版本
python xx.py db history 查看历史版本(迁移记录)

python xx.py db downgrade 版本号 -->返回指定旧版本

 

多对多案例分析 如:学生 学科 第三表
1 创建数据库
2 数据模型 继承(db.Model)
表名
字段= 约束
关系
建立连接
数据模型 继承(db.Model)
表名
字段= 约束
关系
建立连接
3 第三表建立关系 直接创建一张表不需要使用模型类
tb_student_course = db.Table(
"表名",
db.Colum("字段名",约束))

4 添加数据

 


蓝图
为什么使用蓝图--->对程序进行模块化管理--进行协同开发--可以解决循环导入问题--降低耦合度
模块化管理-->根据一定的条件对函数进行分类(按照功能,按照开发人员等)


步骤
1.在蓝图对象模块中导入Blueprint
2.实例化蓝图对象 = Blueprint("名字",__name__,url_prefix = "/路由的开头 区分模块之间的路由,防止冲突")
3.定以路由
4.导入蓝图对象
5.将蓝图注册到app中(蓝图实例对象)

一个蓝图模块可以注册多次

 

单元测试--->严谨性特别高的程序使用单元测试

向功能单一的模块进行测试
断言assert 判断一个函数或方法的一个功能是否符合预期结果
在单元测试中使用
在自己写的工具类供别人使用使用需要断言

例如:
def func(a,b):
assert isinstance(a,int),"参数为int类型"
return a/b
func(参数,参数)


单元测试测试接口------------------------------------数据库测试-------晚间实现

1使用代码模拟浏览器发送一个post请求
2判断errorcode是否存在字典中
3判断errorcode是否为-1

测试代码的步骤
import unittest
import app
import json

class LoginTest(unittest.TestCase):
def setUp(self): # 相当于__init__ 做初始化工作
app.testing = True--->被测试代码有异常会直接抛出
定义模拟客户端=app.test_clicent()

def test_empty_username_password(self):
app.test_clicent().post("url",data={}) #模拟浏览器发送一个post请求
json_data = responde.data
dict_data = josn_data.loads()
print(dict_data)
self.assertIn("键",容器,"返回的消息")
self.assertEqual(数据的[键],状态码,"返回的消息")

def tearDown(): # 类似与__del__()-->析构方法
数据库的断开操作

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

 


nosql--->以键值对存储数据
sql------>适用于关系复杂的数据查询场景

redis-->数据存贮在内存中 速度极快 也支持磁盘存储 重启再次加载使用
支持 list set zset hash 数据据结构存储 支持主从配置 master--slave

应用场景
缓存
社交类应用
session共享 购物车

安装redis
wget url 下载
解压
移动到指定目录
今日redis目录
sudo make 进行编译
sudo make test 测试
sudo make install 安装命令
将配置文件.conf加载到etc目录下

客户端启动 redis-cli
select 0-15 选择使用的数据库
服务端启动 redis-serve

redis 端口 6379

设置守护进程 demaonize yes

默认16个数据库 0-15


redis 哈希 基本的数据类型
哈希槽 利用哈希算法计算出来的存储数据的空间

主从 读写分离 数据备份

 

posted @ 2019-05-07 17:41  殷太平  阅读(170)  评论(0编辑  收藏  举报