您的位置:首页 > 编程语言 > Python开发

三十五、python学习之Flask框架(七)数据库:Flask对数据库的基本操作、常见关系模板、数据库迁移、综合案例:图书管理

2018-10-23 17:45 1051 查看
版权声明:浅弋、璃鱼的原创 https://blog.csdn.net/qq_35709559/article/details/83275875

补充:
  使用SQL_Alchemy定义一个模型类,不可以不指定primary_key=True创建表.

一、数据库基本操作

1. 数据库的基本操作(CRUD):

  • 在Flask-SQLAlchemy中,插入、修改、删除操作,均由数据库会话管理。

  • 会话用 db.session 表示。在准备把数据写入数据库前,要先将数据添加到会话中然后调用 commit() 方法提交会话。
  • 在 Flask-SQLAlchemy 中,查询操作是通过 query 对象操作数据。

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

    2.在视图函数中定义模型类:

    # !/usr/bin python
    # coding=utf-8
    
    from flask import Flask
    # 导入flask_sqlalchemy扩展
    from flask_sqlalchemy import SQLAlchemy
    
    app = Flask(__name__)
    # 配置数据库连接
    app.config['SQLALCHEMY_DATABASE_URI'] = "mysql://root:19970125@127.0.0.1:3306/python32"
    # 关闭动态追踪修改的警告
    app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
    # 展示sql语句
    app.config['SQLALCHEMY_ECHO'] = True
    
    # 实例化sqlalchemy对象
    db = SQLAlchemy(app)
    
    # 定义用户类型数据模型
    class Role(db.Model):
    __tablename__ = "roles"
    id = db.Column(db.Integer, primary_key = True)
    name = db.Column(db.String(32), unique=True)
    
    # 定义关系引用,第一个参数User表示多方的类名
    # 第二个backref表示的是反向引用,给User模型用,实现多对一的查询
    # 等号左边给一方Role使用,backref给多方User使用
    us = db.relationship('User',backref='role')
    
    # 定义方法,输出查询结果
    def __repr__(self):
    return "name:%s" % self.name
    
    # 定义用户数据模型
    class User(db.Model):
    __tabelname__ = "users"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(32))
    email = db.Column(db.String(32), unique=True)
    pswd = db.Column(db.String(32))
    # 定义外键
    role_id = db.Column(db.Integer, db.ForeignKey("roles.id"))
    
    def __repr__(self):
    return "name:%s, email:%s, passwd:%s" % (self.name, self.email, self.pswd)
    
    # 定义视图
    @app.route("/")
    def index():
    return "hello world!!"
    
    if __name__ == '__main__':
    # 删除数据库
    db.drop_all()
    # 创建新库
    db.create_all()
    
    # 实例化用户类型模型类
    ro1 = Role(name = "admin")
    ro2 = Role(name = "user")
    # 通过db.session提交数据库
    db.session.add_all([ro1, ro2])
    db.session.commit()
    
    # 实例化用户模型累
    us0 = User(name = "张三", email = "117128@qq.com", pswd = "123456", role_id = ro1.id)
    us1 = User(name='wang', email='wang@163.com', pswd='123456', role_id=ro1.id)
    us2 = User(name='zhang', email='zhang@189.com', pswd='201512', role_id=ro2.id)
    us3 = User(name='chen', email='chen@126.com', pswd='987654', role_id=ro2.id)
    us4 = User(name='zhou', email='zhou@163.com', pswd='456789', role_id=ro1.id)
    db.session.add_all([us0,us1,us2,us3,us4])
    db.session.commit()
    
    app.run(debug=True)

    3.常用的SQLAlchemy查询过滤器:

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

    分析:

    • filter(): 可以进行逻辑判断,(相对重要)
    • filter_by():等值就是赋值语句
    • order_by(): (相对重要)
    • 参数:desc:使用降序排序; asc:使用升序排序(默认)

    4.常用的SQLAlchemy查询执行器:

    方法 说明
    all() 以列表形式返回查询结果
    first() 返回查询结果的第一个结果,如果未找查到,返回None
    first_or_404() 返回查询的第一个结果,如果未查到返回404
    get() 返回指定的主键对应的行,如果不存在,返回None
    get_or_404() 返回指定主键对应的行,如果不存在,返回404
    count() 返回查询结果的数量
    paginate() 返回一个paginate对象,它包含指定范围内的结果

    分析:

    • 查询器得到一个类,通过执行器拿到结果
    • all,first,get常用
    • paginate:分页,比较难,重点掌握一下

    5.数据库的查询操作:

    5.1 查询所有

    User.query.all()    # 返回一个列表,是对象列表

    解决问题:在模型类中定义方法,实现查询结果显示成可读字符串:

    def __repr__(self):
    return 'name: %s' % self.name   # 返回刻度对象中的属性
    # Python中这个_repr_函数,对应repr(object)这个函数,返回一个可以用来表示对象的可打印字符串.
    # __str__就是基于__repr__实现的.

    5.2 查询一个

    User.query.first()  # 直接返回一个数据

    5.3 查询过滤器:类似于sql中的where

    User.query.filter(User.name == "wang")  # 返回一个基本查询对象
    • 是过滤查询;
    • 接收的参数必须使用模型的类名;
    • 必须使用查询过滤器;
    • filter查询不加条件,相当于查询所有;

    加上执行器即可显示查询内容:

    User.query.filter(User.name == "wang").first()

    可以使用字符串的操作语句,来约束要查询的条件:
    例如:

    User.query.filter(User.name.endseith("g")).all()    # 查询name以字符串"g"结尾的数据
    User.query.filter(User.name.startswith("z")).all()  # 查询name以"z"开头的数据

    使用查询执行器显示需求的数据:
    例如:

    User.query.filter(User.name.startswith("z")).count  # 查询name以"z"开头的数据个数

    在filter中加多个查询条件(条件以逗号分隔):
    例如:

    User.query.filter(User.name != "wang", User.email.endswith('163.com)).all()

    sqlalchemy中的and_, or_, not_操作:
    例如:

    # 导入sqlalchemy中的判断语句
    from sqlalchemy import or_, and_, not_
    User.query.filter(or_(User.name != "wang", User.email.endswith('163.com))).all()```

    5.4等值过滤器:

    User.query.filter(name = "wang")    # 返回一个基本查询对象
    • 只能使用的等值操作;
    • 参数为具体模型的字段名,但是不能出现具体的类名;
    • 必须使用查询执行器

    5.5 限制查询结果的数量:

    User.query.filter().limit(2).all()  # 返回一个列表,限制查询结果的数量

    5.6 分页查询:

    paginate = User.query.filter().paginate(1, 2, False)
    # 分页操作的参数,可以是2个,也可以是3个
    # 参数1:当前页号(1~n);
    # 参数2:每一页的数据;
    # 参数3:如果分页异常不报错;
    # 得到一个分页对象;
    paginate.page   # 获取当前页
    paginate.pages  # 获取总页数
    paginate.items  # 获取当前页中的数据

    5.7 get查询:

    User,get(key)   # get查询接受的参数是主键值,不填报错

    5.8 排序查询:

    User.query.order_by().all() # 默认根据主键的顺序排
    User.query.order_by(User.id.desc()).all()   # 使用id字段降序排序
    User.query.order_by(User.id.asc()).all()   # 使用id字段升序排序

    5.9 练习:

    • 查询所有用户数据:
    User.query.all()
    • 查询有多少个用户:
    User.query.count()
    • 查询第1个用户:
    User.query.first()
    • 查询id为4的用户[3种方式]:
    # 方法一:
    User.query.filter(User.id==4).all()
    # 方法二:
    User.query.filter_by(id=4).all()
    # 方法三:
    User.query.get(4)
    • 查询名字结尾字符为z的所有数据[开始/包含]:
    User.query.filter(User.name.startswith("z")).all()
    • 查询名字不等于wang的所有数据[2种方式]:
    # 方法一:
    User.query.filter(User.name != "wang").all()
    # 方法二:
    from sqlalchemy import or_, and_,not_
    User.query.filter(not_(User.name=="wang")).all()
    • 查询名字和邮箱都以 chen 开头的所有数据[2种方式]:
    # 方法一:
    User.query.filter(User.name.startswith("chen"), User.email.startswith("chen")).all()
    # 方法二:
    User.query.filter(and_(User.name.startswith("chen"), User.email.startswith("chen"))).all()
    • 查询password是
      123456
      或者
      email
      itheima.com
      结尾的所有数据:
    User.query.filter(or_(User.pswd == "123456", User.email=="itcast.com")).all()
    • 查询id为 [1, 3, 5, 7, 9] 的用户列表:
    User.query.filter(User.id.in_([1,3,5,7,9])).all()
    • 查询name为zhou的角色数据:
    User.query.filter(User.name == "zhou").all()
    • 查询所有用户数据,并以邮箱排序:
    User.query.order_by(User.email).all()
    • 每页3个,查询第2页的数据:
    User.query.paginate(2,3,False).items

    6.数据库修改操作:

    完成后需要由sqlalchemy的对象db通过session.commit()提交操作

    6.1更新数据:

    方法一:

    user = User.query.first()user.name = 'dong'
    db.session.commit()
    

    方法二:

    User.query,filter(User.name == "zhang").update({"name":"li"})
    db.session.commit()

    方法三:

    user = user = User.query.get(2)
    user.name = "zhang"
    User.query.all()db.session.add(user)    # 通过额外的一个对象修改的数据,需要先添加这个对象,再提交
    db.session.commit()
    

    7. 一对多和多对多查询:

    7.1 一对多:

    r = Role.query.get(1)	# 拿到对象
    r.us	# 使用关系引用返回的对象

    7.2 多对一

    u = User.query.get(2)
    u.role

    二、综合案例:图书管理系统:

    1.开发基本过程:

    • 需求: 实现图书案例,数据的增删改查.
    • 流程:
    • 1.web表单:wtf扩展,添加数据,验证函数,csrf保护,自定义表单类,设置秘钥,表单域中需要设置csrf_token
    • 2.模板:使用模板语法,获取视图返回的数据,使用语句遍历后端返回的数据;
    • 3.模型类:flask_sqlalchemy,配置数据的连接和动态追踪修改,定义模型(作者和图书),创建表,添加测试数据
    • 4.业务逻辑:视图中数显wtf表单,实现数据的查询,删除,调用模板

    2. 代码实现:

    • 实现简单的功能,然后版本迭代

    版本一: 功能实现

    模板文件:index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>图书管理</title>
    </head>
    <body>
    <form method="post">
    {{ form.csrf_token }}
    <p>作者: {{ form.wtf_author }}</p>
    <p>书名: {{ form.wtf_book }}</p>
    <p>{{ form.wtf_submit}}</p>
    </form>
    
    <ul>
    {% for author in authors %}
    <li>{{ author.name}}</li>
    <a href="/delete_author/{{ author.id }}">删除</a>
    <br>
    {% endfor %}
    </ul>
    
    <ul>
    {% for book in books %}
    <li>{{ book.info }}</li>
    <a href="/delete_book/{{ book.id }}">删除</a>
    <br>
    {% endfor %}
    </ul>
    </body>
    </html>

    视图模块:demo02.py

    导入使用的模块

    from flask import Flask, render_template,url_for,redirect
    # 导入wtf表单
    from flask_wtf import FlaskForm
    # 导入wtf的字段类型
    from wtforms import StringField, SubmitField
    # 导入表单的验证字段
    from wtforms.validators import DataRequired
    # 导入sqlalcjemy模块
    from flask_sqlalchemy import SQLAlchemy

    实例化Flask对象, 数据库对象和设置配置文件

    app = Flask(__name__)
    
    # 设置秘钥
    app.config['SECRET_KEY'] = "frekflnlngldnglk"
    # 配置数据库
    app.config["SQLALCHEMY_DATABASE_URI"] = "mysql://root:passwd@127.0.0.1:3306/auth_book"
    # 关闭动检测
    app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
    
    db = SQLAlchemy(app)

    定义表单类

    # 定义表单
    class Form(FlaskForm):
    wtf_author = StringField(validators=[DataRequired()])
    wtf_book = StringField(validators=[DataRequired()])
    wtf_submit = SubmitField("提交")

    定义作者模型类

    # 定义作者模型
    class Author(db.Model):
    __tablename__ = "authors"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(32), unique=True)
    
    # 重写方法
    def __repr__(self):
    return "name: %s \n" % self.name

    定义书籍模型类

    # 定义书籍
    class Book(db.Model):
    __tablename__ = "books"
    id = db.Column(db.Integer, primary_key=True)
    info = db.Column(db.String(32), unique=True)
    
    # 重写方法
    def __repr__(self):
    return "book_name: %s \n" % self.info

    实现主页面视图函数:

    # 定义主页面的视图
    @app.route('/',methods=['GET','POST'])
    def index():
    form = Form()
    # 获取数据
    authors = Author.query.all()
    books = Book.query.all()
    
    if form.validate_on_submit():
    author = form.wtf_author.data
    book = form.wtf_book.data
    
    au = Author(name = author)
    bk = Book(info = book)
    db.session.add_all([au, bk])
    db.session.commit()
    
    authors = Author.query.all()
    books = Book.query.all()
    
    print(form.wtf_author.data)
    print(form.wtf_book.data)
    print(form.validate_on_submit())
    
    return render_template("index.html", authors = authors, books = books,form=form)

    实现删除作者的视图函数:

    # 定义删除作者的视图函数
    @app.route("/delete_author/<int:id>")
    def del_auth(id):
    author = Author.query.get(id)
    db.session.delete(author)
    db.session.commit()
    return redirect(url_for("index"))

    实现删除图书的视图函数:

    # 定义删除图书的视图函数
    @app.route("/delete_book/<int:id>")
    def del_book(id):
    book = Book.query.get(id)
    db.session.delete(book)
    db.session.commit()
    return redirect(url_for("index"))

    入口主函数

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

    版本二: 在原有基础上做异常处理:

    主页面视图加入异常处理:

    # 定义主页面的视图
    @app.route('/',methods=['GET','POST'])
    def index():
    form = Form()
    authors, books = None, None
    # 获取数据
    try:
    authors = Author.query.all()
    books = Book.query.all()
    except Exception as e:
    print(e)
    
    if form.validate_on_submit():
    author = form.wtf_author.data
    book = form.wtf_book.data
    
    try:
    au = Author(name = author)
    bk = Book(info = book)
    db.session.add_all([au, bk])
    db.session.commit()
    except Exception as e:
    print(e)
    # 添加数据如果发生异常,需要进行回滚
    db.session.rollback()
    
    # 获取数据
    try:
    authors = Author.query.all()
    books = Book.query.all()
    except Exception as e:
    print(e)
    
    return render_template("index.html", authors = authors, books = books,form=form)

    删除作者视图加入异常处理:

    # 定义删除作者的视图函数
    @app.route("/delete_author/<int:id>")
    def del_auth(id):
    author = Author.query.get(id)
    try:
    # 执行删除
    db.session.delete(author)
    db.session.commit()
    except Exception as e:
    print(e)
    db.session.rollback()
    return redirect(url_for("index"))

    删除图书视图加入异常处理:

    # 定义删除图书的视图函数
    @app.route("/delete_book/<int:id>")
    def del_book(id):
    book = Book.query.get(id)
    try:
    # 执行删除
    db.session.delete(book)
    db.session.commit()
    except Exception as e:
    print(e)
    db.session.rollback()
    return redirect(url_for("index"))

    需要异常处理的情况:

    • 对数据库的增删改查都需要异常处理;
    • 修改数据库时需要异常处理。如果发生异常,需要回滚数据库:db.session.rollback()

    在请求过程中自动提交数据:(但是不建议使用)

    app.config["SQLALCHEMY_COMMIT_ON_TEARDOWN] = True

    三、常见关系模板代码:

    1.一对多:

    示例场景:

    • 用户与其发布的帖子(用户表与帖子表)
    • 角色与所属于该角色的用户(角色表与多用户表)

    实例代码:

    class Role(db.Model):
    """角色表"""
    __tablename__ = 'roles'
    
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True)
    users = db.relationship('User', backref='role', lazy='dynamic')
    
    class User(db.Model):
    """用户表"""
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True, index=True)

    2.多对多:

    示例场景

    • 讲师与其上课的班级(讲师表与班级表)
    • 用户与其收藏的新闻(用户表与新闻表)
    • 学生与其选修的课程(学生表与选修课程表)

    示例代码:

    tb_student_course = db.Table('tb_student_course',
    db.Column('student_id', db.Integer, db.ForeignKey('students.id')),
    db.Column('course_id', db.Integer, db.ForeignKey('courses.id'))
    )
    
    class Student(db.Model):
    __tablename__ = "students"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True)
    
    courses = db.relationship('Course', secondary=tb_student_course,
    backref=db.backref('students', lazy='dynamic'),
    lazy='dynamic')
    
    class Course(db.Model):
    __tablename__ = "courses"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True)```

    3.自关联一对多:

    示例场景

    • 评论与该评论的子评论(评论表)
    • 参考网易新闻
    • 省市区表

    实例代码:

    class Comment(db.Model):
    """评论"""
    __tablename__ = "comments"
    
    id = db.Column(db.Integer, primary_key=True)
    # 评论内容
    content = db.Column(db.Text, nullable=False)
    # 父评论id
    parent_id = db.Column(db.Integer, db.ForeignKey("comments.id"))
    # 父评论(也是评论模型)
    parent = db.relationship("Comment", remote_side=[id],
    backref=db.backref('childs', lazy='dynamic'))
    
    # 测试代码
    if __name__ == '__main__':
    db.drop_all()
    db.create_all()
    
    com1 = Comment(content='我是主评论1')
    com2 = Comment(content='我是主评论2')
    com11 = Comment(content='我是回复主评论1的子评论1')
    com11.parent = com1
    com12 = Comment(content='我是回复主评论1的子评论2')
    com12.parent = com1
    
    db.session.add_all([com1, com2, com11, com12])
    db.session.commit()
    app.run(debug=True)

    4.自关联多对多:

    示例场景

    • 用户关注其他用户(用户表,中间表)

    示例代码:

    tb_user_follows = db.Table(
    "tb_user_follows",
    db.Column('follower_id', db.Integer, db.ForeignKey('info_user.id'), primary_key=True),  # 粉丝id
    db.Column('followed_id', db.Integer, db.ForeignKey('info_user.id'), primary_key=True)  # 被关注人的id
    )
    
    class User(db.Model):
    """用户表"""
    __tablename__ = "info_user"
    
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(32), unique=True, nullable=False)
    
    # 用户所有的粉丝,添加了反向引用followed,代表用户都关注了哪些人
    followers = db.relationship('User',
    secondary=tb_user_follows,
    primaryjoin=id == tb_user_follows.c.followed_id,
    secondaryjoin=id == tb_user_follows.c.follower_id,
    backref=db.backref('followed', lazy='dynamic'),
    lazy='dynamic')

    四、数据库的迁移:

    1.为什么要迁移:

    • 在开发过程中,需要修改数据库模型,而且还要在修改之后更新数据库。最直接的方式就是删除旧表,但这样会丢失数据。
    • 更好的解决办法是使用数据库迁移框架,它可以追踪数据库模式的变化,然后把变动应用到数据库中。
    • 在Flask中可以使用Flask-Migrate扩展,来实现数据迁移。并且集成到Flask-Script中,所有操作通过命令就能完成。
    • 为了导出数据库迁移命令,Flask-Migrate提供了一个MigrateCommand类,可以附加到flask-script的manager对象上。

    2.迁移流程:

    • 在开发过程中,需要修改数据库模型,而且还要在修改之后更新数据库。最直接的方式就是删除旧表,但这样会丢失数据。
    • 更好的解决办法是使用数据库迁移框架,它可以追踪数据库模式的变化,然后把变动应用到数据库中。
    • 在Flask中可以使用Flask-Migrate扩展,来实现数据迁移。并且集成到Flask-Script中,所有操作通过命令就能完成。
    • 为了导出数据库迁移命令,Flask-Migrate提供了一个MigrateCommand类,可以附加到flask-script的manager对象上。

    文件名:sql_moe.py

    from flask import Flask
    
    from flask_sqlalchemy import SQLAlchemy
    from flask_migrate import Migrate,MigrateCommand
    from flask_script import Shell,Manager
    
    app = Flask(__name__)
    manager = Manager(app)
    
    app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:mysql@127.0.0.1:3306/Flask_test'
    app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
    app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
    db = SQLAlchemy(app)
    
    #第一个参数是Flask的实例,第二个参数是Sqlalchemy数据库实例
    migrate = Migrate(app,db)
    
    #manager是Flask-Script的实例,这条语句在flask-Script中添加一个db命令
    manager.add_command('db',MigrateCommand)
    
    #定义模型Role
    class Role(db.Model):
    # 定义表名
    __tablename__ = 'roles'
    # 定义列对象
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True)
    user = db.relationship('User', backref='role')
    
    #repr()方法显示一个可读字符串,
    def __repr__(self):
    return 'Role:'.format(self.name)
    
    #定义用户
    class User(db.Model):
    __talbe__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64), unique=True, index=True)
    #设置外键
    role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
    
    def __repr__(self):
    return 'User:'.format(self.username)
    
    if __name__ == '__main__':
    manager.run()

    步骤:

    • python 文件名.py db init

    • python 文件名 db migrate -m “版本描述”

    • python 文件名.py db upgrade

    • 观察mysql表结构,完成第一次迁移

    • 根据要求修改模型

    • python 文件名.py db migrate -m “版本描述”

    • python 文件名.py db upgrade

    • 观察表结构,完成修改

    • 若返回版本,则利用python 文件名 db history 查看版本号

    • python 文件名 db downgrade(或 upgrade) 版本号

    阅读更多
  • 内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: 
    相关文章推荐