Skip to content

数据库操作

数据库操作主要是 CRUD,即 Create(创建)、Read(读取/ 查询)、Update(更新)和Delete(删除)。

Flask-SQLAlchemy 自动帮我们创建会话,可以通过 db.session 属性获取

这一节我们会演示 CRUD 操作。

默认情况下,Flask-SQLAlchemy 会自动为模型类生成一个 __repr__() 方法。 当在 Python Shell 中调用模型的对象时,__repr__() 方法会返回一条类 似<模型类名主键值> 的字符串,比如 <Students 2>。 为了便于实际操作测试,示例程序中,所有的模型类都重新定义了 __repr__()方法,返回一些更有用的信息

python
import click
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import sqlalchemy as sa

app = Flask(__name__)


class Config:
    # 数据库链接配置参数
    SQLALCHEMY_DATABASE_URI = 'sqlite:///data_02.db'
    SQLALCHEMY_TRACK_MODIFICATIONS = False
    # SQLALCHEMY_ECHO = True


app.config.from_object(Config)

# 创建数据库链接对象
db = SQLAlchemy(app)


class Student(db.Model):
    id = sa.Column(sa.Integer, primary_key=True)
    name = sa.Column(sa.String(length=20))
    math = sa.Column(sa.Integer, default=0)
    chinese = sa.Column(sa.Integer)
    english = sa.Column(sa.Integer)

    # total = sa.Column(sa.Integer)

    @property
    def total(self):
        return self.math + self.chinese + self.english

    def __repr__(self):
        return '<Student %s>' % self.name


# 使用外部命令 创建数据库
@app.cli.command()
def init():
    """创建数据库"""
    db.create_all()
    click.echo('初始化数据库。')

新增数据

添加一条新记录到数据库主要分为三步:

  1. 创建 Python 对象(实例化模型类)作为一条记录。
  2. 添加新创建的记录到数据库会话。
  3. 提交数据库会话。

下面的示例向数据库中添加了一条数据:

python
@app.cli.command()
def create():
    """新增数据"""
    click.echo('新增数据')
    # 使用类对象创建一个实例对象(一条数据)
    stu1 = Student(name='张三', math=60, chinese=60, english=60)
    # 添加一条数据
    db.session.add(stu1)
    # 提交数据
    db.session.commit()
    print(stu1.id)

在这个示例中,我们首先使用了 Students 类创建一个 Students 实例表示一条记录,使用关键字参数传入字段数据。

我们的 Students 类继承自 db.Model 基类,db.Model 基类会为 Students 类提供一个构造函数,接收匹配类属性名称的参数值,并赋值给对应的类属性,所以我们不需要自己在 Students 类中定义构造方法。接着我们调用 add() 方法把这三个 Students 对象添加到会话对象 db.session 中,最后调用 commit() 方法提交会话。

除了依次调用 add() 方法添加多个记录,也可以使用 add_all() 一次添加包含所有记录对象的列表。

你可能注意到了,我们在创建模型类实例的时候并没有定义id字段的数据,这是因为主键由 SQLAlchemy 管理。模型类对象创建后作为临时对 象(transient),当你提交数据库会话后,模型类对象才会转换为数据库记录写入数据库中,这时模型类对象会自动获得id值:

python
>>> stu1.id 
1

最后添加多条数据给后面使用

python
stu2 = Student(name='李四', math=65, chinese=65, english=65)
stu3 = Student(name='王五', math=70, chinese=70, english=70)
stu4 = Student(name='正心', math=100, chinese=100, english=100)
db.session.add(stu2)
db.session.add(stu3)
db.session.add(stu4)
db.session.commit()

更新数据

更新一条记录非常简单,直接赋值给模型类的字段属性就可以改变字 段值,然后调用 commit() 方法提交会话即可。下面的示例改变了一条记 录的 body 字段的值:

python
"""更新数据"""
stu4 = Student.query.get(4)
stu4.chinese = 99
db.session.commit()

插入新的记录或要将现有的记录添加到会话中时才需要使用 add() 方法,单纯要更新现有的记录时只需要直接为属性赋新值,然后提交会话。

删除数据

删除记录和添加记录很相似,不过要把 add() 方法换成 delete() 方法,最后都需要调用 commit() 方法提交修改。下面的示例删除了 id(主键)为 4 的记录:

python
"""删除数据"""
stu4 = Student.query.get(4)
db.session.delete(stu4)
db.session.commit()

查询数据

已经知道了如何向数据库里添加记录,那么如何从数据库里取回数据呢?使用模型类提供的 query 属性附加调用各种过滤方法及查询方法可 以完成这个任务。

一般来说,一个完整的查询遵循下面的模式:

python
q = db.select(<模型类>).<过滤器>
ret = db.session.execute(q).<触发器>

注意

旧版本用法如下

<模型类>.query.<过滤器>.<触发器>

查询时,过滤器可以连续调用。

python
q = db.select(<模型类>).<过滤器>.<过滤器>

触发器必选,触发之后才能得到查询结果

从某个模型类出发,通过在 query 属性对应的 Query 对象上附加的过滤方法和查询函数对模型类对应的表中的记录进行各种筛选和调整,最终返 回包含对应数据库记录数据的模型类实例,对返回的实例调用属性即可获 取对应的字段数据。

如果你执行了上面小节里的操作,我们的数据库现在一共会有四条记录

idnamemathchineseenglish
1张三606060
2李四656565
3王五707070
4正心100100100

触发器

SQLAlchemy 提供了许多查询方法用来获取记录。 常用的 SQLAlchemy 触发器

查询方法(触发器)说 明
all()返回包含所有查询记录的列表
first()返回查询的第一条记录,如果未找到,则返回 None
get(ident)传入主键值作为参数,返回指定主键值的记录,如果未找到,则返回 None
count()返回查询结果的数量
one_or_none()类似 one() ,如果结果数量不为1,返回 None
paginate()返回一个 Pagination 对象,可以对记录进行分页处理
with_parent(instance)传人模型类实例作为参数,返回和这个实例相关联的对象
scalars()返回一个列表对象
scalar()返回一条数据
first_or_404()返回查询的第一条记录,如果未找到,则返回 404 错误响应
get_or_404(ident)传人主键值作为参数,返回指定主键值的记录,如果未找到,则返回404错误响应

first_or_404()、get_or_404() 以及 paginate() 方法是 Flask-SQLAlchemy 附加的查询方法。

下面是对 Students 类进行查询的几个示例。all() 返回所有记录:

python
>>> Students.query.all()
[<Students 张三>, <Students 李四>, <Students 王五>, <Students 正心>]

first() 返回第一条记录:

python
>>> Students.query.first()
<Students 张三>

get() 返回指定主键值(id字段)的记录:

python
>>> stu2 = Students.query.get(2)
>>> stu2 
<Students 李四>

count() 返回记录的数量:

python
>>> Students.query.count() 
4

过滤器

SQLAlchemy 还提供了许多过滤方法,使用这些过滤方法可以获取更 精确的查询,比如获取指定字段值的记录。对模型类的 query 属性存储的 Query 对象调用过滤方法将返回一个更精确的 Query 对象(后面我们简称为 查询对象)。因为每个过滤方法都会返回新的查询对象,所以过滤器可以 叠加使用。在查询对象上调用前面介绍的查询方法,即可获得一个包含过滤后的记录的列表。常用的查询过滤方法如下表所示。

查询过滤器说 明
where()使用指定的规则过滤记录,返回新产生的查询对象
filter()使用指定的规则过滤记录,返回新产生的查询对象
filter_by()使用指定规则过滤记录(以关键字表达式的形式),返回新产生的查洵对象
order_by()根据指定条件对记录进行排序,返回新产生的查询对象
limit(limit)使用指定的值限制原查询返回的记录数量,返回新产生的查询对象
group_by()根据指定条件对记录进行分组,返回新产生的查询对象
offset(offset)使用指定的值偏移原查询的结果,返回新产生的查询对象

filter() 方法是最基础的查询方法。它使用指定的规则来过滤记录, 下面的示例在数据库里找出了 body 字段值为 “SHAVE” 的记录:

python
>>> Students.query.filter(Students.name == '正心').first()
<Students 正心>

上面的是方法,等价于新方法

python
>>> q = db.select(Student).where(Student.name == '正心')
>>> db.session.execute(q).scalar()
<Students 正心>

设置 SQLALCHEMY_ECHO = True 开始调试模式之后,可以直接打印查询对象或将其转换为字符串可以查看对应的 SQL 语句:

python
>>> q = db.select(Student).where(Student.name == '正心')
>>> db.session.execute(q).scalar
2023-12-16 16:27:52,582 INFO sqlalchemy.engine.Engine SELECT student.id, student.name, student.math, student.chinese, student.english 
FROM student 
WHERE student.name = ?
2023-12-16 16:27:52,582 INFO sqlalchemy.engine.Engine [cached since 34.95s ago] ('正心',)
<bound method Result.scalar of <sqlalchemy.engine.result.ChunkedIteratorResult object at 0x000001772FF514C0>>

查询操作符

在 filter() 方法中传入表达式时,除了 == 以及表示不等于的 !=, 其他常用的查询操作符以及使用示例如下所示:

LIKE:

python
db.select(Student).where(Student.name.like('%正%'))

IN:

python
db.select(Student).where(Students.name.in_(['张三', '李四'])

NOT IN:

python
db.select(Student).where(~Student.name.in_(['张三', '李四']))

AND:

python
from sqlalchemy import and_

db.select(Student).where(and_(Student.name == '正心', Student.chinese == 100))

# 或在 where() 中加入多个表达式,使用逗号分隔
.where(Student.name == '正心', Student.chinese == 100)

OR:

python
from sqlalchemy import or_

db.select(Student).where(or_(Student.name == '正心', Student.chinese == 60)).all())

可以在 完整的可用操作符列表 可以查看。

分页查询

python
q = db.select(Student)
# 分页查询
paginate_obj = db.paginate(q, page=1, per_page=20, error_out=False)
# 第一页,每页 20 条数据。 默认第一页。
# 参数:error_out 默认 True 表示页数不是 int 或超过总页数时,会报错。 默认 True

# 获取总页数
total_page = paginate_obj.pages

查询参数:

  • page 查询的页数
  • per_page 每页的条数
  • max_per_page 每页最大条数,有值时,per_page 受它影响
  • error_out 当值为 True 时,下列情况会报错
    • page 为 1 时,找不到任何数据
    • page 小于 1,或者 per_page 为负数
    • pageper_page 不是整数

该方法返回一个分页对象 Pagination

查询字段

  • items 当前页的数据列表
  • page 当前页码
  • pages 总页数
  • total 总条数
  • per_page 每页的条数
  • has_next 如果下一页存在,返回 True
  • has_prev 如果上一页存在,返回 True
  • next_num 下一页的页码
  • prev_num 上一页的页码
  • query 用于创建此分页对象的无限查询对象。
  • iter_pages(left_edge=2, left_current=2, right_current=2, right_edge=2) 迭代分页中的页码,四个参数,分别控制了省略号左右两侧各显示多少页码,在模板中可以这样渲染

案例-操作数据库

在视图函数里操作数据库的方式和我们在 Python Shell 中的练习大致相 同,只不过需要一些额外的工作。

比如把查询结果作为参数传入模板渲染出来,或是获取表单的字段值作为提交到数据库的数据。在这一节,我们 将把上一节学习的所有数据库操作知识运用到一个简单的程序中。这个程序可以让你创建、编辑和删除信息,并在主页列出所有保存后的笔记。

项目初始化

python
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import sqlalchemy as sa

app = Flask(__name__)


class Config:
    SQLALCHEMY_DATABASE_URI = 'sqlite:///data03.db'


app.config.from_object(Config)

db = SQLAlchemy()
db.init_app(app)


class Student(db.Model):
    id = sa.Column(sa.Integer, primary_key=True)
    name = sa.Column(sa.String(length=20))
    username = sa.Column(sa.String(length=20))
    mail = sa.Column(sa.String(length=50))
    sex = sa.Column(sa.String(length=1))
    birthdate = sa.Column(sa.DATE)
    address = sa.Column(sa.Text)


@app.cli.command()
def init():
    db.drop_all()
    db.create_all()
    import faker
    fake = faker.Faker('zh-CN')
    for i in range(1, 101):
        data = fake.simple_profile(sex=None)
        obj = Student(**data)
        db.session.add(obj)
    db.session.commit()

前端显示表格数据

当进入学生信息管理系统时,应该将所有的信息全部展示出来

在视图函数中查询数据库记录 并传入模板

python
@app.route("/")
def index_view():
    return render_template("student.html")

在模版中配置后台的接口,只要得到符合规范的数据,前端就能够正常渲染数据。

1、编写基础骨架

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>正心全栈编程</title>
    <link href="//cdn.staticfile.org/layui/2.9.1/css/layui.css" rel="stylesheet">
</head>
<body>

<script src="//cdn.staticfile.org/layui/2.9.1/layui.js"></script>
<script>
    layui.use(function () {
    });
</script>
</body>
</html>

2、渲染表格

html

<div class="layui-container">
    <!-- 数据表格开始 -->
    <table class="layui-hide" id="tables" lay-filter="tables"></table>
</div>
javascript
// 渲染数据表格
table.render({
    elem: '#tables',  //渲染的目标对象
    id: 'tables'  //渲染的目标对象
    , url: '/student' //数据接口
    , title: '用户数据表'//数据导出来的标题
    , toolbar: '#toolbar'  //表格的工具条
    , height: 'full-300'
    , cellMinWidth: 100 //设置列的最小默认宽度
    , done: function (res, curr, count) {
    }
    , page: true //是否启用分页
    , cols: [
        [  //列表数据
            {type: 'checkbox'},
            {field: 'id', title: 'ID', hide: true},
            {field: 'name', title: '用户名'},
            {field: 'username', title: '昵称'},
            {field: 'sex', title: '性别'},
            {field: 'birthdate', title: '出生日'},
            {field: 'mail', title: '邮箱'},
            {field: 'address', title: '地址'},
            {title: '操作', toolbar: '#tools', width: 220}]],
});

3、后端接口提供数据

python

@app.get('/student')
def get_student_list():
    # 分页信息获取
    page = request.args.get("page", type=int, default=1)
    per_page = request.args.get("limit", type=int, default=10)

    q = db.select(Student)

    student_pag = db.paginate(q, page=page, per_page=per_page, error_out=False)

    return {
        "code": 0,
        "message": "",
        "count": student_pag.total,
        "data": [
            {
                "id": student.id,
                "name": student.name,
                "username": student.username,
                "sex": student.sex,
                "birthdate": str(student.birthdate),
                "address": student.address,
                "mail": student.mail,
            }
            for student in student_pag.items
        ],
        "status": "success",
    }

新增数据

1、添加表格头

html

<script type="text/html" id="toolbar">
    <button type="button" class="layui-btn layui-btn-sm" lay-event="toolbar-add">增加</button>
</script>

2、新增点击事件

javascript
//监听头部工具栏事件
table.on('toolbar(tables)', function (obj) {
    if (obj.event === 'toolbar-add') {
        console.log('新增事件被点击')
        layer.open({
            type: 1,
            title: '添加用户',
            area: ['800px', '500px'],
            content: $('#student-form'),
        });
    }
});

3、新增表单

html
<!-- 添加和修改的弹出层开始 -->
<form style="display: none;width: 400px" class="layui-form" action="" lay-filter="student-form" id="student-form">
    <div class="layui-form-item">
        <label class="layui-form-label">用户 ID:</label>
        <div class="layui-input-block">
            <input type="text" name="id" lay-verify="required" autocomplete="off" class="layui-input" value="0"
                   disabled>
        </div>
    </div>
    <div class="layui-form-item">
        <label class="layui-form-label">用户名:</label>
        <div class="layui-input-block">
            <input type="text" name="name" lay-verify="required" autocomplete="off" class="layui-input">
        </div>
    </div>
    <div class="layui-form-item">
        <label class="layui-form-label">昵称:</label>
        <div class="layui-input-block">
            <input type="text" name="username" lay-verify="required" autocomplete="off" class="layui-input">
        </div>
    </div>
    <div class="layui-form-item">
        <label class="layui-form-label">邮箱:</label>
        <div class="layui-input-block">
            <input type="text" name="mail" autocomplete="off" class="layui-input">
        </div>
    </div>
    <div class="layui-form-item">
        <label class="layui-form-label">性别:</label>
        <div class="layui-input-block">
            <input title="男" type="radio" name="sex" value="F" class="layui-input">
            <input title="女" type="radio" name="sex" value="M" class="layui-input" checked>
        </div>
    </div>
    <div class="layui-form-item">
        <label class="layui-form-label">出生日:</label>
        <div class="layui-input-block">
            <input type="text" name="birthdate" autocomplete="off" class="layui-input">
        </div>
    </div>
    <div class="layui-form-item">
        <label class="layui-form-label">住址:</label>
        <div class="layui-input-block">
            <input type="text" name="address" autocomplete="off" class="layui-input">
        </div>
    </div>

    <div class="layui-form-item">
        <div class="layui-input-block">
            <button type="button" class="layui-btn layui-btn-normal layui-btn-sm layui-icon layui-icon-release"
                    lay-filter="form-submit" lay-submit=""> 提交
            </button>
            <button type="reset" class="layui-btn layui-btn-warm layui-btn-sm layui-icon layui-icon-refresh"> 重置
            </button>
        </div>
    </div>
</form>

4、新增事件

javascript
/*添加数据 表单组件,提交按钮之后触发的事件*/
form.on('submit(form-submit)', function (data) {
    $.ajax({
        url: '/student',
        data: JSON.stringify(data.field),
        contentType: 'application/json',
        type: 'post',
        success: function (result) {
            if (result.code === 0) {
                layer.msg(result.message, {icon: 1, time: 1000}, function () {
                    layer.closeAll()
                    // 重置表单
                    $('#student-form')[0].reset()
                    // 重载数据
                    table.reload('tables');
                });
            } else {
                layer.msg(result.message, {icon: 2, time: 1000});
            }
        },
    });
    return false;
});

5、后端新增数据接口

python
@app.post('/student')
def create_student():
    data = request.get_json()
    del data['id']  # 删除多余的 id

    # sqlite 的日期必须接受对象,MySQL可以直接处理字符串
    if data['birthdate']:
        data['birthdate'] = datetime.strptime(data['birthdate'], '%Y-%m-%d')
    else:
        del data['birthdate']

    # 解包初始化
    stu = Student(**data)
    db.session.add(stu)
    db.session.commit()
    return {
        'code': 0,
        'message': '新增数据成功'
    }

修改数据

1、添加表格行事件

html

<script type="text/html" id="tools">
    <a class="layui-btn layui-btn-xs" lay-event="tools-edit">编辑</a>
    <a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="tools-del">删除</a>
</script>

2、监听表格事件

javascript
//监听行工具事件
table.on('tool(tables)', function (obj) {
    if (obj.event === 'tools-edit') {
        // 设置表格数据
        form.val('student-form', obj.data)

        // 打开修改页面
        layer.open({
            type: 1,
            title: '修改用户',
            content: $('#student-form'),
            area: ['800px', '500px'],
        });
    }
});

3、提交修改数据

javascript
/*添加数据 表单组件,提交按钮之后触发的事件*/
form.on('submit(form-submit)', function (data) {
    if (data.field.id == 0) {
        $.ajax({
            url: '/student',
            data: JSON.stringify(data.field),
            contentType: 'application/json',
            type: 'post',
            success: function (result) {
                if (result.code === 0) {
                    layer.msg(result.message, {icon: 1, time: 1000}, function () {
                        layer.closeAll()
                        // 重置表单
                        $('#student-form')[0].reset()
                        // 重载数据
                        table.reload('tables');
                    });
                } else {
                    layer.msg(result.message, {icon: 2, time: 1000});
                }
            },
        });
    } else {
        $.ajax({
            url: `/student/${data.field.id}`,
            data: JSON.stringify(data.field),
            contentType: 'application/json',
            type: 'put',
            success: function (result) {
                if (result.code === 0) {
                    layer.msg(result.message, {icon: 1, time: 1000}, function () {
                        layer.closeAll()
                        // 重置表单
                        $('#student-form')[0].reset()
                        // 重载数据
                        table.reload('tables');
                    });
                } else {
                    layer.msg(result.message, {icon: 2, time: 1000});
                }
            },
        });
    }
    return false;
});

4、后端完成修改

python
@app.put('/student/<int:sid>')
def update_student(sid):
    data = request.get_json()
    del data['id']
    if data['birthdate']:
        data['birthdate'] = datetime.strptime(data['birthdate'], '%Y-%m-%d')
    else:
        del data['birthdate']

    stu = Student.query.get(sid)
    for key, value in data.items():
        setattr(stu, key, value)
    db.session.commit()
    return {
        'code': 0,
        'message': '修改数据成功'
    }

删除数据

1、前端事件拦截

javascript
//监听行工具事件
table.on('tool(tables)', function (obj) {
    if (obj.event === 'tools-edit') {
        // ...
    } else if (obj.event === 'tools-del') { 
        // 删除
        layer.confirm('真的删除行么', function (index) { 
            $.ajax({ 
                url: '/student/' + obj.data.id, 
                type: 'delete', 
                success: function () { 
                    table.reload('tables'); 
                }, 
            }); 
            layer.close(index); 
        });
    }
});

2、后端完成删除

python
@app.delete('/student/<int:sid>')
def delete_student(sid):
    stu = Student.query.get(sid)
    db.session.delete(stu)
    db.session.commit()
    return {
        'code': 0,
        'message': '删除数据成功'
    }

表单查询

1、新增查询表单

html
<div class="layui-container">
    <!-- 查询表单 -->
    <fieldset class="layui-elem-field layui-field-title" style="margin-top: 20px;">
        <legend>查询条件</legend>
    </fieldset>
    <form action="" class="layui-form">
        <div class="layui-form-item">
            <div class="layui-inline">
                <label class="layui-form-label">用户名:</label>
                <div class="layui-input-inline">
                    <input type="text" name="name" autocomplete="off" class="layui-input">
                </div>
            </div>
            <div class="layui-inline">
                <label class="layui-form-label">住址:</label>
                <div class="layui-input-inline">
                    <input type="text" name="address" autocomplete="off" class="layui-input">
                </div>
            </div>
        </div>
        <div class="layui-form-item" style="text-align: center;">
            <div class="layui-input-block">
                <button type="button"
                        class="layui-btn layui-btn-normal layui-btn-sm layui-icon layui-icon-search"
                        lay-submit lay-filter="search-form-submit">查询
                </button>
                <button type="reset" class="layui-btn layui-btn-warm layui-btn-sm layui-icon layui-icon-refresh">重置
                </button>
            </div>
        </div>
    </form>
</div>

2、表单查询事件

javascript
// 监听提交
form.on('submit(search-form-submit)', function (data) {
    table.reload('tables', {where: data.field});
    return false;
});

3、后端完成查询

python
@app.get('/student')
def get_student_list():
    # 分页信息获取
    page = request.args.get("page", type=int, default=1)
    per_page = request.args.get("limit", type=int, default=10)

    q = db.select(Student)

    name = request.args.get('name')  #
    address = request.args.get('address')  #
    if name:  #
        q = q.where(Student.name.like(f'%{name}%'))  #
    if address:  #
        q = q.where(Student.address.like(f'%{address}%'))  #

    student_pag = db.paginate(q, page=page, per_page=per_page, error_out=False)
    return {...}

附录

完整代码
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>正心全栈编程</title>
    <link href="//cdn.staticfile.org/layui/2.9.1/css/layui.css" rel="stylesheet">
</head>
<body>
<div class="layui-container">
    <!-- 查询表单 -->
    <fieldset class="layui-elem-field layui-field-title" style="margin-top: 20px;">
        <legend>查询条件</legend>
    </fieldset>
    <form action="" class="layui-form">
        <div class="layui-form-item">
            <div class="layui-inline">
                <label class="layui-form-label">用户名:</label>
                <div class="layui-input-inline">
                    <input type="text" name="name" autocomplete="off" class="layui-input">
                </div>
            </div>
            <div class="layui-inline">
                <label class="layui-form-label">住址:</label>
                <div class="layui-input-inline">
                    <input type="text" name="address" autocomplete="off" class="layui-input">
                </div>
            </div>
        </div>
        <div class="layui-form-item" style="text-align: center;">
            <div class="layui-input-block">
                <button type="button"
                        class="layui-btn layui-btn-normal layui-btn-sm layui-icon layui-icon-search"
                        lay-submit lay-filter="search-form-submit">查询
                </button>
                <button type="reset" class="layui-btn layui-btn-warm layui-btn-sm layui-icon layui-icon-refresh">重置
                </button>
            </div>
        </div>
    </form>
</div>


<div class="layui-container">
    <!-- 数据表格开始 -->
    <table class="layui-hide" id="tables" lay-filter="tables"></table>
</div>

<script type="text/html" id="toolbar">
    <button type="button" class="layui-btn layui-btn-sm" lay-event="toolbar-add">增加</button>
</script>

<script type="text/html" id="tools">
    <a class="layui-btn layui-btn-xs" lay-event="tools-edit">编辑</a>
    <a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="tools-del">删除</a>
</script>


<!-- 添加和修改的弹出层开始 -->
<form style="display: none;width: 400px" class="layui-form" action="" lay-filter="student-form" id="student-form">
    <div class="layui-form-item">
        <label class="layui-form-label">用户 ID:</label>
        <div class="layui-input-block">
            <input type="text" name="id" lay-verify="required" autocomplete="off" class="layui-input" value="0"
                   disabled>
        </div>
    </div>
    <div class="layui-form-item">
        <label class="layui-form-label">用户名:</label>
        <div class="layui-input-block">
            <input type="text" name="name" lay-verify="required" autocomplete="off" class="layui-input">
        </div>
    </div>
    <div class="layui-form-item">
        <label class="layui-form-label">昵称:</label>
        <div class="layui-input-block">
            <input type="text" name="username" lay-verify="required" autocomplete="off" class="layui-input">
        </div>
    </div>
    <div class="layui-form-item">
        <label class="layui-form-label">邮箱:</label>
        <div class="layui-input-block">
            <input type="text" name="mail" autocomplete="off" class="layui-input">
        </div>
    </div>
    <div class="layui-form-item">
        <label class="layui-form-label">性别:</label>
        <div class="layui-input-block">
            <input title="男" type="radio" name="sex" value="F" class="layui-input">
            <input title="女" type="radio" name="sex" value="M" class="layui-input" checked>
        </div>
    </div>
    <div class="layui-form-item">
        <label class="layui-form-label">出生日:</label>
        <div class="layui-input-block">
            <input type="text" name="birthdate" autocomplete="off" class="layui-input">
        </div>
    </div>
    <div class="layui-form-item">
        <label class="layui-form-label">住址:</label>
        <div class="layui-input-block">
            <input type="text" name="address" autocomplete="off" class="layui-input">
        </div>
    </div>

    <div class="layui-form-item">
        <div class="layui-input-block">
            <button type="button" class="layui-btn layui-btn-normal layui-btn-sm layui-icon layui-icon-release"
                    lay-filter="form-submit" lay-submit=""> 提交
            </button>
            <button type="reset" class="layui-btn layui-btn-warm layui-btn-sm layui-icon layui-icon-refresh"> 重置
            </button>
        </div>
    </div>
</form>

<script src="//cdn.staticfile.org/layui/2.9.1/layui.js"></script>
<script>
    layui.use(function () {
        var $ = layui.jquery;
        var layer = layui.layer;
        var form = layui.form;
        var table = layui.table;

        // 渲染数据表格
        table.render({
            elem: '#tables',  //渲染的目标对象
            id: 'tables'  //渲染的目标对象
            , url: '/student' //数据接口
            , title: '用户数据表'//数据导出来的标题
            , toolbar: '#toolbar'  //表格的工具条
            , height: 'full-300'
            , cellMinWidth: 100 //设置列的最小默认宽度
            , done: function (res, curr, count) {
            }
            , page: true //是否启用分页
            , cols: [
                [  //列表数据
                    {type: 'checkbox'},
                    {field: 'id', title: 'ID', hide: true},
                    {field: 'name', title: '用户名'},
                    {field: 'username', title: '昵称'},
                    {field: 'sex', title: '性别'},
                    {field: 'birthdate', title: '出生日'},
                    {field: 'mail', title: '邮箱'},
                    {field: 'address', title: '地址'},
                    {title: '操作', toolbar: '#tools', width: 220}]],
        });


        // 监听头部工具栏事件
        table.on('toolbar(tables)', function (obj) {
            if (obj.event === 'toolbar-add') {
                console.log('新增事件被点击')
                layer.open({
                    type: 1,
                    title: '添加用户',
                    area: ['800px', '500px'],
                    content: $('#student-form'),
                });
            }
        });

        //监听行工具事件
        table.on('tool(tables)', function (obj) {
            if (obj.event === 'tools-edit') {
                // 设置表格数据
                form.val('student-form', obj.data)

                // 打开修改页面
                layer.open({
                    type: 1,
                    title: '修改用户',
                    content: $('#student-form'),
                    area: ['800px', '500px'],
                });
            } else if (obj.event === 'tools-del') {
                // 删除
                layer.confirm('真的删除行么', function (index) {
                    $.ajax({
                        url: '/student/' + obj.data.id,
                        type: 'delete',
                        success: function () {
                            table.reload('tables');
                        },
                    });
                    layer.close(index);
                    // 向服务端发送删除指令
                });
            }
        });


        /*添加数据 表单组件,提交按钮之后触发的事件*/
        form.on('submit(form-submit)', function (data) {
            if (data.field.id == 0) {
                $.ajax({
                    url: '/student',
                    data: JSON.stringify(data.field),
                    contentType: 'application/json',
                    type: 'post',
                    success: function (result) {
                        if (result.code === 0) {
                            layer.msg(result.message, {icon: 1, time: 1000}, function () {
                                layer.closeAll()
                                // 重置表单
                                $('#student-form')[0].reset()
                                // 重载数据
                                table.reload('tables');
                            });
                        } else {
                            layer.msg(result.message, {icon: 2, time: 1000});
                        }
                    },
                });
            } else {
                $.ajax({
                    url: `/student/${data.field.id}`,
                    data: JSON.stringify(data.field),
                    contentType: 'application/json',
                    type: 'put',
                    success: function (result) {
                        if (result.code === 0) {
                            layer.msg(result.message, {icon: 1, time: 1000}, function () {
                                layer.closeAll()
                                // 重置表单
                                $('#student-form')[0].reset()
                                // 重载数据
                                table.reload('tables');
                            });
                        } else {
                            layer.msg(result.message, {icon: 2, time: 1000});
                        }
                    },
                });
            }
            return false;
        });

        // 监听提交
        form.on('submit(search-form-submit)', function (data) {
            table.reload('tables', {where: data.field});
            return false;
        });
    });
</script>
</body>
</html>
py
from datetime import datetime

from flask import Flask, render_template, request
from flask_sqlalchemy import SQLAlchemy
import sqlalchemy as sa

app = Flask(__name__)


class Config:
    SQLALCHEMY_DATABASE_URI = 'sqlite:///data03.db'


app.config.from_object(Config)

db = SQLAlchemy()
db.init_app(app)


class Student(db.Model):
    id = sa.Column(sa.Integer, primary_key=True)
    name = sa.Column(sa.String(length=20))
    username = sa.Column(sa.String(length=20))
    mail = sa.Column(sa.String(length=50))
    sex = sa.Column(sa.String(length=1))
    birthdate = sa.Column(sa.DATE, default=datetime.now)
    address = sa.Column(sa.Text)


@app.cli.command()
def init():
    db.drop_all()
    db.create_all()
    import faker
    fake = faker.Faker('zh-CN')
    for i in range(1, 101):
        data = fake.simple_profile(sex=None)
        obj = Student(**data)
        db.session.add(obj)
    db.session.commit()


@app.get('/')
def student_view():
    return render_template('student.html')


@app.get('/student')
def get_student_list():
    # 分页信息获取
    page = request.args.get("page", type=int, default=1)
    per_page = request.args.get("limit", type=int, default=10)

    q = db.select(Student)

    name = request.args.get('name')
    address = request.args.get('address')
    if name:
        q = q.where(Student.name.like(f'%{name}%'))
    if address:
        q = q.where(Student.address.like(f'%{address}%'))

    student_pag = db.paginate(q, page=page, per_page=per_page, error_out=False)

    return {
        "code": 0,
        "message": "",
        "count": student_pag.total,
        "data": [
            {
                "id": student.id,
                "name": student.name,
                "username": student.username,
                "sex": student.sex,
                "birthdate": str(student.birthdate),
                "address": student.address,
                "mail": student.mail,
            }
            for student in student_pag.items
        ],
        "status": "success",
    }


@app.post('/student')
def create_student():
    data = request.get_json()
    del data['id']  # 删除多余的 id

    # sqlite 的日期必须接受对象,MySQL可以直接处理字符串
    if data['birthdate']:
        data['birthdate'] = datetime.strptime(data['birthdate'], '%Y-%m-%d')
    else:
        del data['birthdate']

    # 解包初始化
    stu = Student(**data)
    db.session.add(stu)
    db.session.commit()
    return {
        'code': 0,
        'message': '新增数据成功'
    }


@app.put('/student/<int:sid>')
def update_student(sid):
    data = request.get_json()
    del data['id']
    if data['birthdate']:
        data['birthdate'] = datetime.strptime(data['birthdate'], '%Y-%m-%d')
    else:
        del data['birthdate']

    stu = Student.query.get(sid)
    for key, value in data.items():
        setattr(stu, key, value)
    db.session.commit()
    return {
        'code': 0,
        'message': '修改数据成功'
    }


@app.delete('/student/<int:sid>')
def delete_student(sid):
    stu = Student.query.get(sid)
    db.session.delete(stu)
    db.session.commit()
    return {
        'code': 0,
        'message': '删除数据成功'
    }