Flask框架基础学习三

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

安装与使用
  • 文档: https://www.osgeo.cn/sqlalchemy/index.html

  • 安装: 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;
      
字段类型和约束选项

字段类型

字段类型名数据类型解释
SmallIntegerint小型整数,一般16位
Integerint普通整数,一般32位
BigIntegerint不限制精度的整数
Floatfloat浮点数
Numericdecimal.Decimal普通数值,一般32位,Numeric(8,2)代表小数点后面两位,前面最多6位
Stringstr字符串
Textstr变长字符串
LargeBinarystr二进制文件内容
UnicodeunicodeUnicode字符串(万国码)
UnicodeTextunicode变长Unicode字符串
Booleanbool布尔值
Datedatetime.date日期
Timedatetime.time时间
DateTimedatetime.datetime日期和时间

约束选项

选项解释
primary_keyTrue代表主键
uniqueTrue代表唯一
indexTrue代表为该字段创建索引
nullableTrue代表允许为空
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()
      
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值