Flask框架基础学习三
Flask中解决csrf问题
-
安装:
pip install flask_wtf
-
使用
-
后端代码
-
from flask import Flask, render_template from flask_wtf import CSRFProtect # 导入 app = Flask(__name__) app.secret_key = '1111Aaaaa' # 自定义一个秘钥 CSRFProtect(app) # 使用,设置了这个之后,只要是post请求,都要求携带csrf_token值 @app.route('/') def index(): return render_template('csrf_index.html') @app.route('/login', methods=['POST']) def login(): return 'ok' if __name__ == '__main__': app.run(debug=True)
-
前端代码实现csrf_token值的上传
-
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="/login" method="post"> uname:<input type="text" name="uname"> password:<input type="password" name="pwd"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"> <!-- 这边的name一定要是csrf_token --> <input type="submit" name="提交"> </form> </body> </html>
-
Flask中使用CBV
from flask import Flask, views # 导入一个views
app = Flask(__name__)
# 函数的形式叫做FBV
def index():
return 'this is index'
app.add_url_rule('/index', endpoint=None, view_func=index) # 路由的第二种方式
# 类的形式叫做CBV
class Login(views.MethodView): # 继承views.MethodView
def get(self):
""" get方法 """
return 'Login get'
def post(self):
""" post方法 """
return 'Login post'
app.add_url_rule('/login', endpoint=None, view_func=Login.as_view('login')) # 路由/login指向Login类
if __name__ == '__main__':
app.run()
Flask中数据库相关操作
ORM
- ORM:
Object-Relation Mapping
,意思为 对象-关系映射。主要实现模型对象到关系数据库数据的映射。 - 优点
- 不用编写sql语句
- 数据库操作转化为类属性和方法的操作
- 不需要关注数据库的类型
- 相对于数据库配置,比较简单
- 缺点
- 相比于直接使用sql语句,性能会丢失。
Flask-SQLAlchemy
安装与使用
-
安装:
pip install flask-sqlalchemy -i https://pypi.tuna.tsinghua.edu.cn/simple
-
安装pysqldb相关依赖
-
pip install mysqlclient # 有的时候mysqlclient最新版会安装失败,可以安装mysqlclient==1.4.6 pip install pymysql # 如果连接的是mysql数据库,需要安装mysqldb驱动\ pip install flask-mysqldb -i https://pypi.tuna.tsinghua.edu.cn/simple
-
-
-
使用
-
连接数据库
-
from flask_sqlalchemy import SQLAlchemy # 导入 class Config: DEBUG = True SECRET_KEY = '1111AAAaaa' # 数据库配置 = 数据库类型名称://用户名:密码@数据库IP:数据库端口/库名?charset=编码类型 SQLALCHEMY_DATABASE_URI = 'mysql://root:123@127.0.0.1:3306/students?charset=utf8mb4' # 动态追踪修改设置,未设置只会提示警告 SQLALCHEMY_TRACK_MODIFICATIONS = True # 查询时会显示原始SQL语句 SQLALCHEMY_ECHO = True app.config.from_object(Config) # 根据上方库名创建相关数据库 mysql> create database students charset utf8mb4;
-
字段类型和约束选项
字段类型
字段类型名 | 数据类型 | 解释 |
---|---|---|
SmallInteger | int | 小型整数,一般16位 |
Integer | int | 普通整数,一般32位 |
BigInteger | int | 不限制精度的整数 |
Float | float | 浮点数 |
Numeric | decimal.Decimal | 普通数值,一般32位,Numeric(8,2)代表小数点后面两位,前面最多6位 |
String | str | 字符串 |
Text | str | 变长字符串 |
LargeBinary | str | 二进制文件内容 |
Unicode | unicode | Unicode字符串(万国码) |
UnicodeText | unicode | 变长Unicode字符串 |
Boolean | bool | 布尔值 |
Date | datetime.date | 日期 |
Time | datetime.time | 时间 |
DateTime | datetime.datetime | 日期和时间 |
约束选项
选项 | 解释 |
---|---|
primary_key | True代表主键 |
unique | True代表唯一 |
index | True代表为该字段创建索引 |
nullable | True代表允许为空 |
default | 定义默认值default=‘aaa’,不填默认为aaa |
连接数据库,定义模型类并添加到数据库
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
class Config:
DEBUG = True
SECRET_KEY = '1231441Aaaa'
# 数据库配置 = 数据库类型名称://用户名:密码@数据库IP:数据库端口/数据库名称?charset=编码类型
SQLALCHEMY_DATABASE_URI = 'mysql://root:123@127.0.0.1:3306/students?charset=utf8mb4'
# 动态追踪修改设置,未设置只会提示警告
SQLALCHEMY_TRACK_MODIFICATIONS = True
# 查询时会显示原始SQL语句
SQLALCHEMY_ECHO = True
app.config.from_object(Config)
# 这两行要放在app.config.from_object(Config)后面,不然会有warning
db = SQLAlchemy() # 初始化数据库操作对象
db.init_app(app) # 初始化数据库连接
class Student(db.Model):
__tablename__ = 'db_student' # 指定表名
id = db.Column(db.Integer, primary_key=True, comment='主键')
name = db.Column(db.String(64), index=True, comment='姓名')
sex = db.Column(db.Boolean, default=True, comment='性别')
age = db.Column(db.SmallInteger, nullable=True, comment='年龄')
email = db.Column(db.String(128), unique=True, comment='邮箱')
money = db.Column(db.Numeric(8, 2), default=0, comment='钱包') # 这边钱包金额最大是99999.99
def __repr__(self):
return f"Student: {self.name}"
class Teacher(db.Model):
__tablename__ = 'db_teacher'
id = db.Column(db.Integer, primary_key=True, comment='主键')
name = db.Column(db.String(64), unique=True, comment='姓名')
option = db.Column(db.Enum("数学", '语文', '体育'), default="体育", comment='教的学科')
def __repr__(self):
return f"Teacher: {self.name}"
class Course(db.Model):
__tablename__ = 'db_course'
id = db.Column(db.Integer, primary_key=True, comment='主键')
title = db.Column(db.String(128), unique=True, comment="课程名")
price = db.Column(db.Numeric(6, 2), comment='课程价格')
def __repr__(self):
return f'Course: {self.title}'
if __name__ == '__main__':
with app.app_context():
# db.drop_all() # 删除所有表
db.create_all() # 创建所有表(一定要放在app.app_context下)
app.run()
数据表基本操作
-
增
-
@app.route('/add_student') def add_student(): s1 = Student(name='bhlu', sex=True, age=24, email='12222@qq.com', money=1001) # 实例化一个对象 db.session.add(s1) # 添加 # db.session.add_all([s1, s2, s3]) # 批量增加 db.session.commit() # 提交 return "ok"
-
-
删
-
@app.route('/del_student/<name>') def del_student(name): # 方式一 student = Student.query.first() # 找到第一个对象 db.session.delete(student) # 删除该对象 # 方式二 Student.query.filter(Student.name == name).delete() # 找到并删除 db.session.commit() # 最后都需要提交 return 'ok'
-
-
改
-
@app.route('/update_student/<old_name>/<new_name>') def update_student(old_name, new_name): # 方式一 student = Student.query.filter(Student.name == old_name) student.name = new_name # 方式二,修改单条字段 Student.query.filter(Student.name == old_name).update({Student.name: new_name}) # 方式三,将年龄为24的学生钱包都增加100元 Student.query.filter(Student.age == 24).update({Student.money: Student.money + 100}) db.session.commit() # 最后都需要提交 return "ok"
-
-
查
-
get(),根据主键来查询,返回单个对象
-
# 获取id为7的学生姓名 s1 = Student.query.get(7) # get找不到返回None print(s1.name)
-
-
all(),根据查询结果返回全部对象
-
# 获取所有学生的姓名 all_students = Student.query.all() for s in all_students: print(s.name)
-
-
first(),根据查询结果返回第一个对象
-
# 查询sex为True的第一个学生姓名 s2 = Student.query.filter(Student.sex == True).first() print(s2.name)
-
-
查询有三种方式
-
s2 = Student.query.filter(Student.sex == True).first() # 方法一 s2 = db.session.query(Student).filter(Student.sex == True).first() # 方法二 s2 = Student.query.filter_by(sex=True).first() # 方法三
-
-
模糊查询:
startswith、contains、endswith
-
# startswith(以什么开头): 查询以bhlu开头的学生的钱包 s3 = Student.query.filter(Student.name.startswith('bhlu')).all() for s in s3: print(s.money) # contains(包含什么): 查询姓名包含u的学生对象列表 s4 = Student.query.filter(Student.name.contains('u')).all() print(s4) # endswith(以什么结尾): 查询姓名以g结尾的学生对象列表 s5 = Student.query.filter(Student.name.endswith('g')).all() print(s5)
-
-
多条件查询:
非、not_、and_、or_、in_、order_by、count、offset、limit
-
# !代表非: 查询sex不是True的学生对象列表 s6 = Student.query.filter(Student.sex != True).all() print(s6) from sqlalchemy import not_, and_, any_, or_ # 导入 # not_取反: 查询sex不是True的学生对象列表 s7 = Student.query.filter(not_(Student.sex==True)).all() # and_代表与: 查询年龄是24并且姓名是以b开头的对象 s8 = Student.query.filter(and_(Student.age==24, Student.startswith("b"))).all() # or_代表或: 查询年龄是24或者姓名是以b开头的对象 s9 = Student.query.filter(or_(Student.age==24, Student.startswith("b"))).all() # in_范围查询: 查询id为2,3,4,5的学生列表 s10 = Student.query.filter(Student.id.in_([2, 3, 4, 5])).all() # order_by排序: 查询学生列表以age降序输出 s11 = Student.query.order_by(Student.id.desc()).all() # count统计: 统计年龄为22的学生数量 s12 = Student.query.filter(Student.age == 22).count() # limit数量限制: 取年龄最大的3个学生 s13 = Student.query.order_by(Student.age.desc()).limit(3).all() # offset进行偏移: 查询年龄大小排第二,第三和第四的三位学生(降序输出,从第二个开始数,一共数三个) s14 = Student.query.order_by(Student.age.desc()).offset(2).limit(3).all()
-
-
分组和聚合
-
from sqlalchemy import func # 导入 # 聚合函数: count(计数),avg(平均值),min(最小值),max(最大值),sum(和) # 案例一: 查询不同年龄的学生数量 ret = db.session.query(Student.age, func.count(Student.id)).group_by(Student.age).all() # 案例二: 查询年龄大于25的学生的数量 ret = db.session.query(Student.age, func.count(Student.id)).group_by(Student.age).having(Student.age>25).all()
-
-
执行原生的SQL语句
-
# 读取多条数据(fetchall) ret = db.session.execute("select * from db_student").fetchall() # 读取一条数据(fetchone) ret = db.session.execute("select * from db_student").fetchone() # 添加/修改/删除(需要commit) db.session.execute("UPDATE db_student SET money=(db_student.money + %s) WHERE db_student.age = %s" % (200, 24)) db.session.commit()
-
-
分页器Pagination
-
Pagination对象参数
-
pagination(page=None, per_page=None, error_out=True, max_per_page=None) page: 指定页码,从1开始,一般从前端传过来 per_page: 每一页显示的条数 error_out: 它的值为True时,当page有误时,会抛出404错误(page为负数,浮点数等) max_per_page: 指定per_page的最大为多少
-
-
Pagination对象属性和方法
-
has_next: 判断是否存在后一页(尾页),存在为True,不存在为False has_prev: 判断是否存在前一页(首页),存在为True,不存在为False items: 当前页的元素集合 next(error_out=False): 返回下一页的Pagination对象 prev(error_out=False): 上一页的Pagination对象 page: 当前页页码 per_page: 上一页页码 next_num: 下一页页码 pages: 总页数 per_page: 每一页显示的元素个数 total: 元素总数 query: 创建Pagination对象对应的query对象
-
-
使用
Pagination
-
后端代码
-
from flask import Flask app = Flask(__name__) @app.route(rule="/list") def list(): """分页器使用""" pagination = Student.query.paginate(per_page=3) # 指定一页显示3条数据 return render_template("list.html",pagination=pagination) if __name__ == '__main__': app.run(debug=True)
-
-
前端代码
-
{% for student in pagination.items %} {# 这里的pagination.items是表示所有学生对象列表 #} <p>{{ student.id }}</p> <p>{{ student.name }}</p> <p>{{ student.age }}</p> {% endfor %} {% if pagination.has_prev %} {# 当存在上一页时,说明不是首页 #} <a href="?page=1">首 页</a> <a href="?page={{ pagination.page-1 }}">上一页</a> <a href="?page={{ pagination.page-1 }}">{{ pagination.page-1 }}</a> {% endif %} <span>{{ pagination.page }}</span> {# 这边显示的是当前页 #} {% if pagination.has_next %} {# 当存在下一页时,说明不是尾页 #} <a href="?page={{ pagination.page+1 }}">{{ pagination.page+1 }}</a> <a href="?page={{ pagination.page+1 }}">下一页</a> <a href="?page={{ pagination.pages }}">尾 页</a> {% endif %}
-
-
常用的SQLAlchemy关系选项
选项 | 解释 |
---|---|
backref | 用于外键反向引用的名称 |
primary join | 明确指定两个模型之间使用的连接条件 |
lazy | 指定是否加载关联模型的数据: select: 立即加载,将查询对象的所有关联模型数据都加载显示,相当于lazy=True subquery: 立即加载,使用子查询的时候才加载显示 dynamic: 不加载,查询数据时才加载显示 |
uselist | 一对一: 设为False 一对多: 设为True |
secondary | 指定多对多生成的第三张表的名称 |
secondary join | 在SQLAlchemy中无法自行决定时,指定多对多关系中的二级连表条件 |
一对一
-
一对一: 主表和附加表
-
主表中写relationship,附表中写Foreignkey
-
模型类代码
-
class Student(db.Model): """ 学生表 """ __tablename__ = 'db_student' # 指定表名 id = db.Column(db.Integer, primary_key=True, comment='主键') name = db.Column(db.String(64), index=True, comment='姓名') sex = db.Column(db.Boolean, default=True, comment='性别') age = db.Column(db.SmallInteger, nullable=True, comment='年龄') email = db.Column(db.String(128), unique=True, comment='邮箱') money = db.Column(db.Numeric(8, 2), default=0, comment='钱包') info = db.relationship('StudentInfo', backref='own', uselist=False) # 主表中添的是relationship,因为是一对一,uselist=False def __repr__(self): return f"Student: {self.name}" class StudentInfo(db.Model): """ 学生信息表 """ __tablename__ = 'db_student_info' id = db.Column(db.Integer, primary_key=True, comment='主键') address = db.Column(db.String(128), comment='地址') edu = db.Column(db.String(32), comment='学历') s_id = db.Column(db.Integer, db.ForeignKey(Student.id), comment='学生id') # 附加表中填的是ForeignKey def __repr__(self): return self.own.name
-
-
操作
-
# 查询操作 # 查询姓名为bhlu的学生的地址(正向查询) stu_address = Student.query.filter(Student.name=='bhlu').first().info.address # 查询学历是本科的学生姓名 stu_list = StudentInfo.query.filter(StudentInfo.edu=='本科').all() for stu in stu_list: stu_name = stu.own.name print(stu_name) # 添加操作 # 添加一个学生和他的信息 stu = Student(name='maomao', age=22, sex=True, email='maomao@qq.com', money=66) stu.info = StudentInfo(address='江苏省无锡市', edu='本科') db.session.add(stu) db.session.commit() # 修改操作 # 将maomao的地址改为江苏省泰州市(正向) stu = Student.query.filter(Student.name=='maomao').first() stu.info.address = '江苏省泰州市' db.session.commit() # 将学历为本科的学生钱包金额都改为666(反向) stu_info_list = StudentInfo.query.filter(StudentInfo.edu=='本科').all() for stu_info in stu_info_list: stu_info.own.money = 666 db.session.commit() # 删除操作 # 找到名字为maomao的学生,将他在信息表和学生表的上记录都删掉 stu = Student.query.filter(Student.name=='maomao').first() db.session.delete(stu.info) db.session.delete(stu) db.session.commit()
-
-
一对多
-
模型类代码
-
class Teacher(db.Model): __tablename__ = 'db_teacher' id = db.Column(db.Integer, primary_key=True, comment='主键') name = db.Column(db.String(64), unique=True, comment='姓名') option = db.Column(db.Enum("数学", '语文', '体育'), default="体育", comment='教的学科') # 主表中写的是relationshio,因为是一对多,所以uselist=True,lazy=subquery代表当使用子查询时,外键模型的属性会瞬间查出来 course_list = db.relationship('Course', backref='teacher', uselist=True, lazy='subquery') def __repr__(self): return f"Teacher: {self.name}" class Course(db.Model): __tablename__ = 'db_course' id = db.Column(db.Integer, primary_key=True, comment='主键') title = db.Column(db.String(128), unique=True, comment="课程名") price = db.Column(db.Numeric(6, 2), comment='课程价格') # 附加表使用ForeignKey teacher_id = db.Column(db.Integer, db.ForeignKey(Teacher.id), comment='教课老师') def __repr__(self): return f'Course: {self.teacher.name}的{self.title}'
-
-
操作
-
# 增加 # 增加一个teacher,并同时添加他的相关课程(正向) teacher = Teacher( name='maomao', option='数学', course_list = [ Course(title='随机数', price=999), Course(title='平方数', price=888), Course(title='立方数', price=666) ] ) db.session.add(teacker) db.session.commit() # 给maomao增加一个新的课程(反向) t_id = Teacher.query.filter(Teacher.name=='maomao').first().id course = Course(title='课程1', price=20, teacher_id=t_id) db.session.add(course) db.session.commit() # 修改 # 将maomao教的课程1价格修改为666(正向) course_list = Teacher.query.filter(Teacher.name=='maomao').first().course_list for course in course_list: if course.title == '课程1': course.price = 666 db.session.commit() # 将课程2的老师教的学科改为体育(反向) teacher = Course.query.filter(Course.title=='课程2').first().teacher teacher.option = '体育' db.session.commit() # 查找 # 查找所有数学来说教的课程名 teacher_list = Teacher.query.filter(Teacher.option == '语文').all() for teacher in teacher_list: course_list = teacher.course_list print(teacher.name, end=': \n\t') for course in course_list: print(course.title, end='\n\t') print() # 查找课程1是哪个老师教的 teacher = Course.query.filter(Course.title='课程1').first().teacher print(teacher.name) # 删除 # 删除maomao和他教的课程 teacher = Teacher.query.filter(Teacher.name=='maomao').first().teacher course_list = teacher.course_list for course in course_list: db.session.delete(course) db.session.delete(teacher) db.session.commit()
-
多对多
-
模型类代码
-
achievement = db.Table( 'db_achievement', db.Column('student_id', db.ForeignKey('db_student.id'), comment='学生'), db.Column('course_id', db.ForeignKey('db_course.id'), comment='课程') ) class Course(db.Model): __tablename__ = 'db_course' id = db.Column(db.Integer, primary_key=True, comment='主键') title = db.Column(db.String(128), unique=True, comment="课程名") price = db.Column(db.Numeric(6, 2), comment='课程价格') student_list = db.relationship('Student', secondary=achievement, backref='course_list', lazy=True) # secondary指向的是多对多生成的第三张表 def __repr__(self): return f'Course: {self.teacher.name}的{self.title}' class Student(db.Model): """ 学生表 """ __tablename__ = 'db_student' # 指定表名 id = db.Column(db.Integer, primary_key=True, comment='主键') name = db.Column(db.String(64), index=True, comment='姓名') sex = db.Column(db.Boolean, default=True, comment='性别') age = db.Column(db.SmallInteger, nullable=True, comment='年龄') email = db.Column(db.String(128), unique=True, comment='邮箱') money = db.Column(db.Numeric(8, 2), default=0, comment='钱包') def __repr__(self): return f"Student: {self.name}"
-
-
操作
-
# 添加 # 添加一个学生,并为该学生添加两个课程 student = Student(name='bhlu', age=24, sex=True, money=99999, email='111@qq.com') student.course_list = [ Course(title='课程1', price=666), Course(title='课程2', price=999) ] db.session.add(student) db.session.commit() # 给bhlu学生添加一个课程3 student = Student.query.filter(Student.name == 'bhlu').first() student.course_list.append( Course(title='课程2', price=222) ) db.session.commit() # 查询 # 查询bhlu的课程 student = Student.query.filter(Student.name == 'bhlu').first() for course in student.course_list: print(course.title) # 查询有课程1的学生 course = Course.query.filter(Course.title == '课程1').first() for student in course.student_list: print(student.name) # 更新 # 将bhlu的课程1的价格改为888 student = Student.query.filter(Student.name == 'bhlu').first() for course in student.course_list: if course.title == '课程1': course.price = 888 db.session.commit() # 删除 # 将bhlu学生删除,对应关系表中会自动删除 student = Student.query.filter(Student.name == 'bhlu').first() db.session.delete(student) db.session.commit()
-