什么是flask

Flask是一个轻量级的可定制框架,使用Python语言编写,较其他同类型框架更为灵活、轻便、安全且容易上手。它可以很好地结合MVC模式进行开发,开发人员分工合作,小型团队在短时间内就可以完成功能丰富的中小型网站或Web服务的实现。另外,Flask还有很强的定制性,用户可以根据自己的需求来添加相应的功能,在保持核心功能简单的同时实现功能的丰富与扩展,其强大的插件库可以让用户实现个性化的网站定制,开发出功能强大的网站。

Flask主要包括Werkzeug和Jinja2两个核心函数库,它们分别负责业务处理和安全方面的功能,这些基础函数为web项目开发过程提供了丰富的基础组件。

  • Werkzeug库十分强大,功能比较完善,支持URL路由请求集成,一次可以响应多个用户的访问请求;

  • 支持Cookie和会话管理,通过身份缓存数据建立长久连接关系,并提高用户访问速度;

  • 支持交互式Javascript调试,提高用户体验;

  • 可以处理HTTP基本事务,快速响应客户端推送过来的访问请求。

  • Jinja2库支持自动HTML转移功能,能够很好控制外部黑客的脚本攻击。系统运行速度很快,页面加载过程会将源码进行编译形成python字节码,从而实现模板的高效运行;

  • 模板继承机制可以对模板内容进行修改和维护,为不同需求的用户提供相应的模板。

基本模式

Flask的基本模式为在程序里将一个视图函数分配给一个URL,每当用户访问这个URL时,系统就会执行给该URL分配好的视图函数,获取函数的返回值并将其显示到浏览器上,其工作过程见图。

img


bilibili

入门例子

from flask import Flask, jsonify

# 实例化flask对象
app = Flask(import_name=__name__)

# import_name flask程序所在的包模块。__name__ 决定flask在访问静态文件的查找路径

#
# static_path 静态文件访问路径(不推荐使用,使用static_url_path代替)
# static_url_path 静态文件访问路径,不必须,默认为 : / + static_folder
# static_folder 静态文件存储的文件夹,可以不传,默认为static
# template_folder 模板文件存储的文件夹,可以不传,默认为templates
#


tasks = [
{
'id': 1,
'title': u'Buy groceries',
'description': u'Milk, Cheese, Pizza, Fruit, Tylenol',
'done': False
},
{
'id': 2,
'title': u'Learn Python',
'description': u'Need to find a good Python tutorial on the web',
'done': False
}
]


#  编写路由和视图
# methods 默认是get

@app.route(rule='/todo/api/v1.0/tasks', methods=['GET'])
def get_tasks():
return jsonify({'tasks': tasks})


# 加载项目配置

class Config(object):
DEBUG = True


# flask中支持多种配置方式,通过app.config进行加载,我们会这里常用的配置类
app.config.from_object(Config)

if __name__ == '__main__':
# debug 跟踪错误信息
app.run(debug=True, host="0.0.0.0", port=5555)

路由

路由传递参数 任意路由

@app.route('/user/<user_id>', methods=['get'])
def user_info(user_id):
print(user_id)
return '<h1>hello %s </h1>' % user_id

自带的路由参数转换器限定参数类型

#: the default converter mapping for the map.
DEFAULT_CONVERTERS = {
"default": UnicodeConverter,
"string": UnicodeConverter,
"any": AnyConverter,
"path": PathConverter,
"int": IntegerConverter,
"float": FloatConverter,
"uuid": UUIDConverter,
}

举例

@app.route('/user/<float:data>')
def getdata(data):
return repr(data)

自定义路由参数转换器(正则匹配)

from werkzeug.routing import BaseConverter

# 继承BaseConverter 定义自己的regx路由匹配
class MobileConverter(BaseConverter):
regx = r"1[3-9]\d{9}"

def __init__(self, map, *args, **kwargs):
super().__init__(map, *args, **kwargs)

# 注册路由参数转换器到 app的路由参数列表对象 url_map 中
app.url_map.converters["mobile"] = MobileConverter


@app.route("/user/<mobile:cc>")
def getcc(cc):
print("%s" % cc)
return "%s" % cc


通用路由参数转换器

可以临时注册路由参数转换器时限定其regex,从而实现ReGex的复用

from werkzeug.routing import BaseConverter


class ReGex(BaseConverter):

def __init__(self, map, *args):
super().__init__(map)
self.regex = args[0]


app.url_map.converters["re"] = ReGex


@app.route("/user/<re('\d{6}'):mobile>")
def getMobile(mobile):
return mobile

Request

从客服端发送的请求都会集中到request来处理,其中request有以下集中方法:

request.method # 获取本次http请求的方式  有 get post put patch delete
request.query_string # 获取本次客户端的查询字符串
request.path # 获取本次客户端的访问路径
request.url # 获取本次客户端的访问url
request.data # 获取请求体数据并转换为字符串,只要是通过其他方法无法识别的请求提数据都会保存在data属性中 bytes
request.form # 表单数据 MultiDict
request.args # 查询字符串 MultiDict [('ad','df'),('ad',123)]
request.cookies # cookies Dict
headers # 请求体中的请求头 EnvironHeaders
files #请求上传的文件 *
json #ajax请求的json数据 json

image-20210314171519303

response

响应默认两种响应

  1. 页面响应 html
  2. 数据响应 json - jsonify(data)

响应报文部分,可以通过以下三种方法获取:

  • r.json():如果报文结构为 json 格式,则解析为字典格式,也就是说此方法获取的结果是字典。注意,如果报文主体不是 json 格式,则会抛出异常。
  • r.text:text 属性,可以以此方式返回除二进制外的任意数据格式,比如 HTML,XML,Json 均可。网络爬虫中一般使用此方式获取 HTML 页面。
  • r.content:content 属性用于获取二进制的数据格式,比如视频、图片等,常用于网络爬虫中爬取图片、视频等资源。
  • 图片传输1 :

​ 无论从从服务器接收图片,还是发送给客户端,都是将图片转化成base64字节流形式返回,收到这串字节数据,然后通过base64将字节转化为图片。

​ 发送方: res = base64.b64encode(f.read())

​ 接收方: img = base64.b64decode(result)

  • 图片传输2:

     
    with open("2.jpg","rb") as f:
    content = f.read()
    response = make_response(content)
    response.headers["Content-Type"]="image/jpeg"
    return response



    • zip下载
    with open("2.zip","rb") as f:
    content = f.read()
    response = make_response(content)
    response.headers["Content-Type"]="application/zip"
    return response

重定向

image-20210314200521699

自定义响应

image-20210314201453906

cookie

image-20210314203823020

session

image-20210314211557532

image-20210314212024451

image-20210314213310119

请求钩子

image-20210314214010705

  • 网站第一次启动时 第一个请求 before_first_request 常见操作是:数据库连接,网站全局缓存
  • before_request 判断权限、识别用户身份
  • after_request 日志记录、操作历史记录、备份操作
  • teardown_request 每次请求中报错才执行,异常处理、错误日志记录

abort抛出http异常

image-20210314220545388

raise抛出异常

image-20210314220951482

image-20210314221131168

上下文

请求上下文

image-20210314222341296

应用上下文

image-20210314225940998

image-20210314230705230

  • 注意g变量是个临时的变量,不能存储变量长时间,长时间你的话最好用 数据库或者redis

请求上下文 与应用上下文区别:

  • 请求上下文保存了客户端和服务端的交互数据,一般来自于客户端
  • 应用上下文:flask应用程序运行过程中,保存的一些配置信息,比如路由列表,程序名,数据库连接,应用信息。

数据库操作

image-20210314232235002

sqlalchemy数据类型

image-20210315001849210

sqlalchemy的列约束选项

image-20210315002032150

数据库操作

image-20210315225217043

举例

from flask import Flask, render_template
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)


class Config(object):
SQLALCHEMY_DATABASE_URI = "mysql://python1:123@localhost:3306/rundb?charset=utf8mb4"
# 动态追踪修改配置 入未设置只会显示警告
SQLALCHEMY_TRACK_MODIFICATION = True
SQLALCHEMY_TRACK_MODIFICATIONS = True
# 查询是会显示原始sql语句
SQLALCHEMY_ECHO = True
DEBUG = False


app.config.from_object(Config)

# 初始化sqlalchemy对象
db = SQLAlchemy()

# 传递app配置信息
db.init_app(app)


#


# 创建类关联模型 注意 db
class Students(db.Model):
"""相关属性"""
# 没有tablename 或者写错会报错 且Students类不能创建
__tablename__ = "student"

"""字段类型"""
id = db.Column(db.Integer, comment="id", primary_key=True, nullable=False, autoincrement=True)
name = db.Column(db.String(10), comment="name", nullable=False)
sex = db.Column(db.Enum("男", "女"), comment="sex", default="男")
major = db.Column(db.String(20), comment="major", nullable=False)

"""模型方法"""

# def __init__(self, id, name, sex, major):
# self.id = id
# self.name = name
# self.sex = sex
# self.major = major

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


@app.route(rule="/add")
def add():
# 自增的id 重复插入会报错 所以最好不用指定id
student1 = Students(name="小明", sex='男', major="计算机")
student2 = Students(name="王南干", major="软件")
db.session.add(student1)
db.session.commit()

db.session.add(student2)
# 事务中操作 乐观锁
# db.session.add_all(student1,student2)
db.session.commit()
return "Ok"


@app.route("/")
def root():
return render_template("error.html")


if __name__ == "__main__":
# 创建 student model与数据库表的关联关系,如果数据库没有相应的表会创建表

with app.app_context():
db.create_all()

app.run(port=9999)

查询

image-20210315232931579

query 相当于 select… where … 部分,query 中给定条件相当于指定 where 的筛选条件。
删除
image-20210316001249876

更新

image-20210316001533968

分页器使用

image-20210316090309970

image-20210316093607484

分组查询、聚合函数

image-20210316094009475

原生 sql

image-20210316095221677

关联查询

image-20210316095526209

举例

一对一的关系

image-20210316111737222

创建类关联模型 注意 db


class Students(db.Model):
"""相关属性"""
# 没有tablename 或者写错会报错 且Students类不能创建
__tablename__ = "student"

"""字段类型"""
id = db.Column(db.Integer, comment="id", primary_key=True, nullable=False, autoincrement=True)
name = db.Column(db.String(10), comment="name", nullable=False)
sex = db.Column(db.Enum("男", "女"), comment="sex", default="男")
major = db.Column(db.String(20), comment="major", nullable=False)
# 主表申明外附信息表的 关系 info转为StudentR类型数据

# relationship 模型字段是在代码中申明表之间的关系 不会再表中体现出来
# backref = 主表的tablename ,用于从外表 逆向查找 主表中数据,
info = db.relationship("StudentR", uselist=False, backref="student")
"""模型方法"""



class StudentR(db.Model):
""""""
__tablename__ = "studentr"

""""""
# id = db.Column(db.Integer, comment="id", primary_key=True)
# 申明外键 会在表中体现出来
sid = db.Column(db.Integer, db.ForeignKey(Students.id), primary_key=True, comment="学生id")
address = db.Column(db.String(200), nullable=True, comment="家庭地址")
mobile = db.Column(db.String(16), unique=True, comment="电话号码")
money = db.Column(db.Numeric(8, 2), default=0, comment="钱包")


@app.route("/add1")
def add2():
s = Students(name="王芳", sex="女", major="电子信息")
s.info = StudentR(address="花明楼王府井10023", mobile="1231", money=12312)
db.session.add(s)
db.session.commit()
return repr(s)

一对多关系

一个老师上多门课程

image-20210316111959218

举例


class Teacher(db.Model):
__tablename__ = "tb_teacher"

id = db.Column(db.Integer, primary_key=True, comment="老师id", autoincrement=True)
name = db.Column(db.String(12), nullable=False)
mobile = db.Column(db.String(16), nullable=True)
# uselist = True 一对多的关系 切允许使用列表
course_list = db.relationship("Course", uselist=True, backref="tb_teacher", lazy="subquery")

def __repr__(self):
return "{} {}".format(self.name, self.mobile)


class Course(db.Model):
__tablename__ = "tb_course"
id = db.Column(db.Integer, primary_key=True, comment="课程id", autoincrement=True)
tid = db.Column(db.Integer, db.ForeignKey(Teacher.id))
name = db.Column(db.String(20), nullable=False, unique=True)


@app.route("/add2")
def add3():
teacher = Teacher(name="刘 夜", mobile="18457682345")
# 添加课程列表
teacher.course_list = [Course(name="软件管理"), Course(name="java"), Course(name="c++")]
db.session.add(teacher)
db.session.commit()

return teacher.__repr__()

```
### 多对多
#### 第一种实现方式

![image-20210316144523500](https://cdn.jsdelivr.net/gh/deckcode/articleResource@main/blog_imgs/image-20210316144523500.png)



举例
```python

# 申明选课表 以及外键
takes = db.Table('tb_takes', db.Column('student_id', db.Integer, db.ForeignKey('tb_student.id'), primary_key=True),
db.Column('course_id', db.Integer, db.ForeignKey('tb_course.id'), primary_key=True),
db.Column('score', db.Numeric(5, 2), nullable=True, comment="成绩"),
db.Column('time', db.Time, comment="考试时间"))


# 创建类关联模型 注意 db
class Students(db.Model):
"""相关属性"""
# 没有tablename 或者写错会报错 且Students类不能创建
__tablename__ = "tb_student"

"""字段类型"""
id = db.Column(db.Integer, comment="id", primary_key=True, nullable=False, autoincrement=True)
name = db.Column(db.String(10), comment="name", nullable=False)
sex = db.Column(db.Enum("男", "女"), comment="sex", default="男")
major = db.Column(db.String(20), comment="major", nullable=False)
# 添加多对多关系 secondary 设置关联的多对对关联表 takes
course_list = db.relationship("Course", uselist=True, secondary=takes, backref="tb_student", lazy="dynamic")
# 主表申明外附信息表的 关系 info转为StudentR类型数据
# relationship 模型字段是在代码中申明表之间的关系 不会再表中体现出来
# backref = 主表的tablename ,用于从外表 逆向查找 主表中数据,
info = db.relationship("StudentR", uselist=False, backref="tb_student")


class Course(db.Model):
__tablename__ = "tb_course"
id = db.Column(db.Integer, primary_key=True, comment="课程id", autoincrement=True)
tid = db.Column(db.Integer, db.ForeignKey(Teacher.id))
name = db.Column(db.String(20), nullable=False, unique=True)
# 添加多对多关系 secondary 设置关联的多对对关联表 takes
students = db.relationship("Students", secondary=takes, backref="tb_course", lazy="dynamic")


@app.route("/add_s_t_c")
def add4():
student = Students(name="王芳", sex="女", major="计算机")
student.info = StudentR(address="花明楼王府井23号街道", mobile="13377653456", money=123000)
student.course_list = [Course(name="java"), Course(name="c++")]

course = Course(name="python")
course.students=[student]
db.session.add(student)
db.session.add(course)
db.session.commit()

return "ok"


但是该种方法及其的不便,对于 takes 表中的另外的字段 score time,不能在代码中进行设置,只能设置 student 与 course 表的外键字段。

image-20210316155744282

而且该种方法通过 student 添加 course,与通过 teacher 添加 course 可能产生冲突,(course 的 name unique 不能重复,所以当通过 student 添加 java 一门课程时候,再通过 teacher 添加 java 这门课程就会违反 unique 约束)

image-20210316155923937

image-20210316155801697

image-20210316155856802

另外的实现方式

创建 student 与 course 之间的关联关系模型


class Archivevment(db.Model):
__tablename__ = "tb_archivevment"
student_id = db.Column(db.Integer, db.ForeignKey("tb_student2.id"), primary_key=True, comment="学生id")
course_id = db.Column(db.Integer, db.ForeignKey("tb_course2.id"), comment="课程id", primary_key=True)

# 创建类关联模型 注意 db
class Students(db.Model):
"""相关属性"""
# 没有tablename 或者写错会报错 且Students类不能创建
__tablename__ = "tb_student2"

"""字段类型"""
id = db.Column(db.Integer, comment="id", primary_key=True, nullable=False, autoincrement=True)
name = db.Column(db.String(10), comment="name", nullable=False)
sex = db.Column(db.Enum("男", "女"), comment="sex", default="男")
major = db.Column(db.String(20), comment="major", nullable=False)

course_list = db.relationship("Archivevment", uselist=True, backref="tb_student2", lazy=True)



class Course(db.Model):
__tablename__ = "tb_course2"
id = db.Column(db.Integer, primary_key=True, comment="课程id", autoincrement=True)
tid = db.Column(db.Integer, db.ForeignKey(Teacher.id))
name = db.Column(db.String(20), nullable=False, unique=True)
student_list = db.relationship("Archivevment", uselist=True, backref="course", lazy=True)


@app.route("/add")
def add():
stu = Students(name="夏明", sex="女", major="人工智能")
c1, c2 = Course(name="java"), Course(name="c++")
# 先添加课程
db.session.add_all([c1,c2])


# backref="course"
# 再添加学生,以及选课关系
# 根据关联关系 Archivement(通过course为c1来反向查询),得到对应的Archivement填充student信息
# 以此来填充Archivement表,和student表。


stu.course_list = [
Archivevment(course=c1),
Archivevment(course=c2)
]

db.session.add(stu)
db.session.commit()
return "ok"

image-20210316215931853