一、数据库的设置

Web应用中普遍使用的是关系模型的数据库,关系型数据库把所有的数据都存储在表中,表用来给应用的实体建模,表的列数是固定的,行数是可变的。它使用结构化的查询语言。关系型数据库的列定义了表中表示的实体的数据属性。比如:商品表里有name、price、number等。 Flask本身不限定数据库的选择,你可以选择SQL或NOSQL的任何一种。也可以选择更方便的SQLALchemy,类似于Django的ORM。SQLALchemy实际上是对数据库的抽象,让开发者不用直接和SQL语句打交道,而是通过Python对象来操作数据库,在舍弃一些性能开销的同时,换来的是开发效率的较大提升。

SQLAlchemy是一个关系型数据库框架,它提供了高层的ORM和底层的原生数据库的操作。flask-sqlalchemy是一个简化了SQLAlchemy操作的flask扩展。

1.1数据库安装

安装服务端

sudo apt-get install mysql-server

安装客户端

sudo apt-get install mysql-client
sudo apt-get install libmysqlclient-dev

1.2 数据库的基本命令

登录数据库

mysql -u root -p

创建数据库,并设定编码

create database <数据库名> charset=utf8;

显示所有数据库

show databases;

1.3 在Flask中使用mysql数据库,需要安装一个flask-sqlalchemy的扩展。

pip install flask-sqlalchemy

要连接mysql数据库,仍需要安装flask-mysqldb

pip install flask-mysqldb

1.4 使用Flask-SQLAlchemy管理数据库

使用Flask-SQLAlchemy扩展操作数据库,首先需要建立数据库连接。数据库连接通过URL指定,而且程序使用的数据库必须保存到Flask配置对象的SQLALCHEMY_DATABASE_URI键中。

对比下Django和Flask中的数据库设置:

Django的数据库设置:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'mydb',
        'HOST':'192.168.18.128',
        'PORT':3306,
        'USER':'root',
        'PASSWORD':'123456',
    }
}

Flask的数据库设置:

app.config['SQLALCHEMY_DATABASE_URI'] = 'mysq+pymysqll://root:mysql@127.0.0.1:3306/mydb'

 

常用的SQLAlchemy字段类型

类型名

python中类型

说明

Integer

int

普通整数,一般是32位

SmallInteger

int

取值范围小的整数,一般是16位

BigInteger

int或long

不限制精度的整数

Float

float

浮点数

Numeric

decimal.Decimal

普通整数,一般是32位

String

str

变长字符串

Text

str

变长字符串,对较长或不限长度的字符串做了优化

Unicode

unicode

变长Unicode字符串

UnicodeText

unicode

变长Unicode字符串,对较长或不限长度的字符串做了优化

Boolean

bool

布尔值

Date

datetime.date

时间

Time

datetime.datetime

日期和时间

LargeBinary

str

二进制文件

常用的SQLAlchemy列选项

选项名

说明

primary_key

如果为True,代表表的主键

unique

如果为True,代表这列不允许出现重复的值

index

如果为True,为这列创建索引,提高查询效率

nullable

如果为True,允许有空值,如果为False,不允许有空值

default

为这列定义默认值

常用的SQLAlchemy关系选项

选项名

说明

backref

在关系的另一模型中添加反向引用

primary join

明确指定两个模型之间使用的联结条件

uselist

如果为False,不使用列表,而使用标量值

order_by

指定关系中记录的排序方式

secondary

指定多对多中记录的排序方式

secondary join

在SQLAlchemy中无法自行决定时,指定多对多关系中的二级联结条件

代码演示

db_demo.py

# coding:utf-8
#db_demo.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy


app = Flask(__name__)


class Config(object):
    """配置参数"""
    # sqlalchemy的配置参数
    SQLALCHEMY_DATABASE_URI = "mysql://root:mysql@127.0.0.1:3306/db_python04"

    # 设置sqlalchemy自动更跟踪数据库
    SQLALCHEMY_TRACK_MODIFICATIONS = True


app.config.from_object(Config)

# 创建数据库sqlalchemy工具对象
db = SQLAlchemy(app)


class Role(db.Model):
    """用户角色/身份表"""
    __tablename__ = "tbl_roles"

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(32), unique=True)
    users = db.relationship("User", backref="role")

    def __repr__(self):
        """定义之后,可以让显示对象的时候更直观"""
        return "Role object: name=%s" % self.name


# 表名的常见规范
# ihome -> ih_user   数据库名缩写_表名
# tbl_user  tbl_表名
# 创建数据库模型类
class User(db.Model):
    """用户表"""
    __tablename__ = "tbl_users"  # 指明数据库的表名

    id = db.Column(db.Integer, primary_key=True)  # 整型的主键,会默认设置为自增主键
    name = db.Column(db.String(64), unique=True)
    email = db.Column(db.String(128), unique=True)
    password = db.Column(db.String(128))
    role_id = db.Column(db.Integer, db.ForeignKey("tbl_roles.id"))

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


if __name__ == '__main__':
    # 清除数据库里的所有数据
    db.drop_all()

    # 创建所有的表
    db.create_all()

    # 创建对象
    role1 = Role(name="admin")
    # session记录对象任务
    db.session.add(role1)
    # 提交任务到数据库中
    db.session.commit()

    role2 = Role(name="stuff")
    db.session.add(role2)
    db.session.commit()

    us1 = User(name='wang', email='wang@163.com', password='123456', role_id=role1.id)
    us2 = User(name='zhang', email='zhang@189.com', password='201512', role_id=role2.id)
    us3 = User(name='chen', email='chen@126.com', password='987654', role_id=role2.id)
    us4 = User(name='zhou', email='zhou@163.com', password='456789', role_id=role1.id)

    # 一次保存多条数据
    db.session.add_all([us1, us2, us3, us4])
    db.session.commit()

terminal

Role.query.all()
Out[2]: [<db_demo.Role at 0x10388d190>, <db_demo.Role at 0x10388d310>]

In [3]: li = Role.query.all()

In [4]: li
Out[4]: [<db_demo.Role at 0x10388d190>, <db_demo.Role at 0x10388d310>]

In [5]: r = li[0]

In [6]: type(r)
Out[6]: db_demo.Role

In [7]: r.name
Out[7]: u'admin'

In [8]: Role.query.first()
Out[8]: <db_demo.Role at 0x10388d190>

In [9]: r = Role.query.first()

In [10]: r.name
Out[10]: u'admin'

#  根据主键id获取对象
In [11]: r = Role.query.get(2)

In [12]: r
Out[12]: <db_demo.Role at 0x10388d310>

In [13]: r.name
Out[13]: u'stuff'

In [14]:


# 另一种查询方式
In [15]: db.session.query(Role).all()
Out[15]: [<db_demo.Role at 0x10388d190>, <db_demo.Role at 0x10388d310>]

In [16]: db.session.query(Role).get(2)
Out[16]: <db_demo.Role at 0x10388d310>

In [17]: db.session.query(Role).first()
Out[17]: <db_demo.Role at 0x10388d190>

In [18]:

In [18]: User.query.filter_by(name="wang")
Out[18]: <flask_sqlalchemy.BaseQuery at 0x1038c90d0>

In [19]: User.query.filter_by(name="wang").all()
Out[19]: [<db_demo.User at 0x1038c87d0>]

In [20]: User.query.filter_by(name="wang").first()
Out[20]: <db_demo.User at 0x1038c87d0>

In [21]: user = User.query.filter_by(name="wang").first()

In [22]: user.name
Out[22]: u'wang'

In [23]: user.email
Out[23]: u'wang@163.com'

In [24]: User.query.filter_by(name="wang", role_id=1).first()
Out[24]: <db_demo.User at 0x1038c87d0>

In [25]: User.query.filter_by(name="wang", role_id=2).first()

In [26]: user = User.query.filter_by(name="wang", role_id=2).first()

In [27]: type(user)
Out[27]: NoneType

In [28]:


In [28]: user = User.query.filter(User.name=="wang", User.role_id==1).first
    ...: ()

In [29]: user
Out[29]: <db_demo.User at 0x1038c87d0>

In [30]: user.name
Out[30]: u'wang'

In [31]: from sqlalchemy import or_

In [32]: User.query.filter(or_(User.name=="wang", User.email.endswith("163.com")
    ...: )).all()
Out[32]: [<db_demo.User at 0x1038c87d0>, <db_demo.User at 0x1038ef310>]

In [33]: li = User.query.filter(or_(User.name=="wang", User.email.endswith("163.
    ...: com"))).all()

In [34]: li[0].name
Out[34]: u'wang'

In [35]: li[1].name
Out[35]: u'zhou'

In [36]:


# offset偏移  跳过几条
In [36]: User.query.offset(2).all()
Out[36]: [<db_demo.User at 0x1038c0950>, <db_demo.User at 0x1038ef310>]

In [37]: li = User.query.offset(2).all()

In [38]: li[0].name
Out[38]: u'chen'

In [39]: li[1].name
Out[39]: u'zhou'

In [40]:


In [42]: li = User.query.offset(1).limit(2).all()

In [43]: li
Out[43]: [<db_demo.User at 0x1038fd990>, <db_demo.User at 0x1038c0950>]

In [44]: li[0].name
Out[44]: u'zhang'

In [45]: li[1].name
Out[45]: u'chen'

In [46]:

In [50]: User.query.order_by("-id").all()
Out[50]:
[<db_demo.User at 0x1038ef310>,
 <db_demo.User at 0x1038c0950>,
 <db_demo.User at 0x1038fd990>,
 <db_demo.User at 0x1038c87d0>]

In [51]:

In [51]: li = User.query.order_by(User.id.desc()).all()

In [52]: li
Out[52]:
[<db_demo.User at 0x1038ef310>,
 <db_demo.User at 0x1038c0950>,
 <db_demo.User at 0x1038fd990>,
 <db_demo.User at 0x1038c87d0>]

In [53]: li[0].name
Out[53]: u'zhou'

In [54]: li[3].name
Out[54]: u'wang'

In [55]:


In [55]: from sqlalchemy import func

In [56]: db.session.query(User.role_id, func.count(User.role_id)).group_by(User.role_i
    ...: d)
Out[56]: <flask_sqlalchemy.BaseQuery at 0x103a38050>

In [57]: db.session.query(User.role_id, func.count(User.role_id)).group_by(User.role_i
    ...: d).all()
Out[57]: [(1L, 2L), (2L, 2L)]

In [58]:


In [61]: ro = Role.query.get(1)

In [62]: type(ro)
Out[62]: db_demo.Role

In [63]: ro.users
Out[63]: [<db_demo.User at 0x1038c87d0>, <db_demo.User at 0x1038ef310>]

In [64]: ro.users[0].name
Out[64]: u'wang'

In [65]: ro.users[1].name
Out[65]: u'zhou'

In [66]:



In [67]: user
Out[67]: <db_demo.User at 0x1038c87d0>

In [68]: user.role_id
Out[68]: 1L

In [69]: Role.query.get(user.role_id)
Out[69]: <db_demo.Role at 0x10388d190>

In [70]: user.role
Out[70]: <db_demo.Role at 0x10388d190>

In [71]: user.role.name
Out[71]: u'admin'

In [72]:


# 更新
In [14]: User.query.filter_by(name="zhou").update({"name": "python", "emai
    ...: l": "python@itast.cn"})
Out[14]: 1L

In [15]: db.session.commit()

In [16]:


# 删除
In [16]: user = User.query.get(3)

In [17]: db.session.delete(user)

In [18]: db.session.commit()

In [19]:

图书管理

(删除函数:注释代码部分为post请求写法)

后端部分

# coding:utf-8

from flask import Flask, render_template, request, redirect, url_for, jsonify
from flask_sqlalchemy import SQLAlchemy
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired


app = Flask(__name__)


class Config(object):
    # sqlalchemy的配置参数
    SQLALCHEMY_DATABASE_URI = "mysql://root:mysql@127.0.0.1:3306/author_book_py04"

    # 设置sqlalchemy自动更跟踪数据库
    SQLALCHEMY_TRACK_MODIFICATIONS = True

    SECRET_KEY = "doiso7fd89fyd9^(fsd"


app.config.from_object(Config)

db = SQLAlchemy(app)


# 定义数据库的模型
class Author(db.Model):
    """作者"""
    __tablename__ = "tbl_authors"

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(32), unique=True)
    books = db.relationship("Book", backref="author")


class Book(db.Model):
    """书籍"""
    __tablename__ = "tbl_books"

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True)
    author_id = db.Column(db.Integer, db.ForeignKey("tbl_authors.id"))


# 创建表单模型类
class AuthorBookForm(FlaskForm):
    """作者数据表单模型类"""
    author_name = StringField(label=u"作者", validators=[DataRequired(u"作者必填")])
    book_name = StringField(label=u"书籍", validators=[DataRequired(u"书籍必填")])
    submit = SubmitField(label=u"保存")


@app.route("/", methods=["GET", "POST"])
def index():
    # 创建表单对象
    form = AuthorBookForm()

    if form.validate_on_submit():
        # 验证表单成功
        # 提取表单数据
        author_name = form.author_name.data
        book_name = form.book_name.data
        # 保存数据库
        author = Author(name=author_name)
        db.session.add(author)
        db.session.commit()

        book = Book(name=book_name, author_id=author.id)
        # book = Book(name=book_name, author=author)
        db.session.add(book)
        db.session.commit()

    # 查询数据库
    author_li = Author.query.all()
    return render_template("author_book.html", authors=author_li, form=form)


# post  /delete_book  json
# {"book_id":x}

# @app.route("/delete_book", methods=["POST"])
# def delete_book():
#     """删除数据"""
#     # 提取参数
#     # 如果前端发送的请求体数据是json格式,get_json会解析成字典
#     # get_json 要求前端传送的数据的Content-Type: application/json
#     req_dict = request.get_json()
#     book_id = req_dict.get("book_id")
#
#     # 删除数据
#     book = Book.query.get(book_id)
#     db.session.delete(book)
#     db.session.commit()
#
#     # "Content-Type": "application/json"
#     return jsonify(code=0, message="OK")


# /delete_book?book_id=xx
@app.route("/delete_book", methods=["GET"])
def delete_book():
    """删除数据"""
    # 提取参数
    book_id = request.args.get("book_id")

    # 删除数据
    book = Book.query.get(book_id)
    db.session.delete(book)
    db.session.commit()

    return redirect(url_for("index"))


if __name__ == '__main__':
    #测试数据
    # db.drop_all()
    # db.create_all()
    # au_xi = Author(name='我吃西红柿')
    # au_qian = Author(name='萧潜')
    # au_san = Author(name='唐家三少')
    # db.session.add_all([au_xi, au_qian, au_san])
    # db.session.commit()
    #
    # bk_xi = Book(name='吞噬星空', author_id=au_xi.id)
    # bk_xi2 = Book(name='寸芒', author_id=au_qian.id)
    # bk_qian = Book(name='飘渺之旅', author_id=au_qian.id)
    # bk_san = Book(name='冰火魔厨', author_id=au_san.id)
    # db.session.add_all([bk_xi, bk_xi2, bk_qian, bk_san])
    # db.session.commit()
    app.run(debug=True)

前端部分 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form method="post">
        {{ form.csrf_token }}

        {{ form.author_name.label }}
        <p>{{form.author_name}}</p>
        {% for msg in form.author_name.errors %}
            <p>{{msg}}</p>
        {% endfor %}

        {{ form.book_name.label }}
        <p>{{form.book_name}}</p>
        {% for msg in form.book_name.errors %}
            <p>{{msg}}</p>
        {% endfor %}

        {{ form.submit }}
    </form>
    <hr/>
    <ul>
        {% for author in authors %}
        <li>作者:{{ author.name }}</li>
            <ul>
                {% for book in author.books%}
               <li>书籍:{{ book.name }}</li>
                <!--<a href="javascript:;" book-id="{{book.id}}">删除</a>-->
                <a href="/delete_book?book_id={{book.id}}">GET删除</a>
                {% endfor %}
            </ul>
        {% endfor %}
    </ul>
    <script type="text/javascript" src="/static/js/jquery.min.js"></script>
    <script>
//        $("a").click(
//            function () {
//                var data = {
//                    book_id: $(this).attr("book-id")
//                };
//                // 将js中的对象转换为 json字符串
//                var req_json = JSON.stringify(data);
//    //            $.post("/delete_book", req_json, function (resp) {
//    //                if (resp.code == 0) {
//    //                    location.href = "/";
//    //                }
//    //            })
//                $.ajax({
//                    url: "/delete_book", // 请求的后端url
//                    type: "post",  // 请求方式
//                    data: req_json,  // 向后端发送的请求体数据
//                    contentType: "application/json", // 指明向后端发送的数据格式
//                    dataType: "json",  // 指明后端返回的数据格式
//                    success: function (resp) {
//                        if (resp.code == 0) {
//                            alert("oK");
//                            location.href = "/";
//                        }
//                    }
//                })
//            }
//        )
    </script>
</body>
</html>

1.6数据库迁移

在开发过程中,需要修改数据库模型,而且还要在修改之后更新数据库。最直接的方式就是删除旧表,但这样会丢失数据。

更好的解决办法是使用数据库迁移框架,它可以追踪数据库模式的变化,然后把变动应用到数据库中。

在Flask中可以使用Flask-Migrate扩展,来实现数据迁移。并且集成到Flask-Script中,所有操作通过命令就能完成。

为了导出数据库迁移命令,Flask-Migrate提供了一个MigrateCommand类,可以附加到flask-script的manager对象上。

首先要在虚拟环境中安装Flask-Migrate。


pip install flask-migrate


文件:database.py


#coding=utf-8 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)    def __repr__(self):        return 'Role:'.format(self.name) #定义用户 class User(db.Model):    __tablename__ = 'users'    id = db.Column(db.Integer, primary_key=True)    username = db.Column(db.String(64), unique=True, index=True)    def __repr__(self):        return 'User:'.format(self.username) if __name__ == '__main__':    manager.run()


创建迁移仓库


#这个命令会创建migrations文件夹,所有迁移文件都放在里面。 python database.py db init


创建迁移脚本

自动创建迁移脚本有两个函数,upgrade()函数把迁移中的改动应用到数据库中。downgrade()函数则将改动删除。自动创建的迁移脚本会根据模型定义和数据库当前状态的差异,生成upgrade()和downgrade()函数的内容。对比不一定完全正确,有可能会遗漏一些细节,需要进行检查


#创建自动迁移脚本 python database.py db migrate -m 'initial migration'


更新数据库


python database.py db upgrade


回退数据库

回退数据库时,需要指定回退版本号,由于版本号是随机字符串,为避免出错,建议先使用python database.py db history命令查看历史版本的具体版本号,然后复制具体版本号执行回退。


python database.py db downgrade 版本号