(数据科学学习手札120)Python+Dash快速web应用开发——整合数据库

2021-05-11 14:32:00 浏览数 (1)

本文示例代码已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes

1 简介

   这是我的系列教程Python Dash快速web应用开发的第十七期,在之前的各期教程中,我们针对Dash中各种基础且常用的概念展开了学习,但一直没有针对与数据库之间交互进行专门的介绍,只是在某些示例中利用pandasSQLAlchemy等工具简陋地操作数据库。

  而在今天的教程中,我就将带大家学习在Dash中利用简单好用的ORMpeewee,快速高效地将数据库整合进Dash应用中。

图1

2 利用peewee在Dash中整合数据库

  说起peewee,很多使用过ORM(Object Relational Mapping,对象关系映射)工具的朋友都听说过,它跟SQLAlchemy等框架从功能上看都大同小异,目的都是为了不写SQL,而是利用面向对象编程的方式,在Python中实现常用的SQL功能。

图2

peewee虽然相比SQLAlchemy等重型的ORM框架已经轻量很多了,但内容还是非常丰富,我们今天就针对一些典型场景,展示一下其与Dash应用如何相互结合。

2.1 创建数据表

  利用peewee构建数据表,需要定义相应的Model类,在类中构建的属性即对应表中的字段,并且在Meta类中定义其他的一些属性,譬如下面的例子我们就以最简单的SQLite数据库为例:

model1.py

代码语言:javascript复制
from peewee import SqliteDatabase, Model
from peewee import CharField, IntegerField, DateTimeField

from datetime import datetime

# 关联数据库,对于sqlite数据库若不存在则会直接创建
db = SqliteDatabase('17 整合数据库/model1.db')

class Model1(Model):

    # 用户名为字符型,并设置唯一性约束
    username = CharField(unique=True)

    # 用户等级设定为整数型
    level = IntegerField()

    # 用户加入时间为时间日期类型
    join_datetime = DateTimeField()

    class Meta:
        database = db # 指定数据库
        table_name = 'user_info' # 自定义数据表名,不设置则自动根据类名推导

# 创建数据表,若对应数据库中已存在此表,则会跳过
db.create_tables([Model1])

  上述的代码在执行之后,便会在关联到的SQLite数据库中创建对应的表:

图3

  而除了最简单的SQLite之外,peewee还支持MySQLPostgreSQL,你可以在http://docs.peewee-orm.com/en/latest/peewee/database.html查看更多使用示例,关于更多有关Model创建的知识可以参考http://docs.peewee-orm.com/en/latest/peewee/models.html

2.2 向表中新增记录

  在数据表创建完成之后,我们第一件事当然是要向表中插入数据,这在peewee中操作非常简单:

  • 插入单条数据

  在peewee中向表中插入单条记录可以使用create()方法:

代码语言:javascript复制
# 创建单条记录
Model1.create(username='张三', level=6, join_datetime=datetime(2020, 1, 1, 10, 28, 45))

Model1.create(username='李四', level=1, join_datetime=datetime(2020, 5, 1, 10, 28, 45))

  执行完上述命令后旋即会更新到数据库表中:

图4

  • 插入多条数据

  在peewee中批量插入数据可以使用insert_many()方法传入对应每行内容的字典列表,记得最后要跟着执行execute()方法才会真正向数据库执行:

代码语言:javascript复制
# 批量插入数据
(
    Model1
    .insert_many([
    {'username': '王五', 'level': 3, 'join_datetime': datetime(2020, 3, 1, 10, 28, 45)},
    {'username': '赵六', 'level': 2, 'join_datetime': datetime(2020, 4, 1, 10, 28, 45)}])
    .execute()
)

图5

2.3 从表中删除数据

  对于已存在数据的表,进行数据删除可以使用到delete()方法其后再链式上where()来声明判断条件,最后同样跟上execute()方法执行即可,如果要清空整张表则不用加where(),譬如我们要删除level小于3的记录:

代码语言:javascript复制
# 删除level小于3的记录
Model1.delete().where(Model1.level < 3).execute()

图6

  更多关于peewee数据删除的知识可以参考官方文档http://docs.peewee-orm.com/en/latest/peewee/querying.html#deleting-records部分内容。

2.4 对表中数据进行更新

  作为增删改查中非常重要的,在peewee中实现也是非常的方便,基础的用法是配合update()where()如下面的例子那样:

代码语言:javascript复制
# 修改username为张三的记录值level字段为8
Model1.update(level=8).where(Model1.username == '张三').execute()

图7

  更多内容可参考官方文档http://docs.peewee-orm.com/en/latest/peewee/querying.html#updating-existing-records

2.5 对表中数据进行查询

  作为增删改查中使用频次最高的,在peewee中涉及到的知识内容非常之庞大,但基础的格式都是利用select()方法,常用的有以下方式:

代码语言:javascript复制
# 获取查询结果方式1:
query_results = Model1.select().where(Model1.level > 2).execute()

for query_result in query_results:
    print(query_result.username)

图8

代码语言:javascript复制
# 获取查询结果方式2:
query_results = Model1.select().where(Model1.level > 2).dicts()
list(query_results)

图9

  而有关跨表连接等进阶的查询操作,请参考官方文档http://docs.peewee-orm.com/en/latest/peewee/query_examples.html#query-examples

2.6 基于已存在的表逆向生成Model

  如果你的数据库表已然存在,又希望生成相应的Model类,peewee提供了命令行工具帮我们做这件事,以SQLite为例:

代码语言:javascript复制
python -m pwiz -e sqlite model1.db >model2.py

  自动生成的model2.py代码如下,在这个基础上我们可以进一步的优化修改:

代码语言:javascript复制
from peewee import *

database = SqliteDatabase('model1.db')


class UnknownField(object):
    def __init__(self, *_, **__): pass


class BaseModel(Model):
    class Meta:
        database = database


class UserInfo(BaseModel):
    join_datetime = DateTimeField()
    level = IntegerField()
    username = CharField(unique=True)

    class Meta:
        table_name = 'user_info'

  而更多关于peewee利用pwiz生成Model类的参数和用法可参考官方文档http://docs.peewee-orm.com/en/latest/peewee/playhouse.html#pwiz-a-model-generator

3 peewee配合Dash实现在线留言板功能

getpeewee的常用基础用法之后,我们回到本文的重点——结合Dash整合数据库,要实现的功能很简单,就是实现一个在线留言板,每个访问应用的用户都可以在填写若干信息后,发表自己的留言,其他用户后续访问可以看到前面用户发表过的留言信息。

  为了方便演示,我选择SQLite作为示例数据库,首先我们需要构建一个model.py来设计表模型,来存放每条留言信息,并自定义一些功能函数:

model.py

代码语言:javascript复制
from peewee import SqliteDatabase, Model
from peewee import CharField, DateTimeField, TextField
from datetime import datetime

db = SqliteDatabase('message_board.db')


class MessageBoard(Model):
    nickname = CharField()

    pub_datetime = DateTimeField()

    message_content = TextField()

    class Meta:
        database = db  # 指定数据库
        table_name = 'message_board'  # 自定义数据表名,不设置则自动根据类名推导


db.create_tables([MessageBoard])


# 新增留言记录
def submit_new_message(nickname, message_content):
    MessageBoard.create(
        nickname=nickname,
        pub_datetime=datetime.now(),
        message_content=message_content
    )


# 获取全部留言记录
def fetch_all_message():
    return list(MessageBoard.select().dicts())

  接着我们只需要在对应Dash应用的app.py中调用model.py中的相关功能即可,效果如下(动图录制有些花屏,大家可以自己运行尝试,效果更佳):

图10

app.py

代码语言:javascript复制
import dash
import dash_html_components as html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output, State

from model import MessageBoard, submit_new_message, fetch_all_message

app = dash.Dash(__name__)

app.layout = html.Div(
    dbc.Container(
        [
            html.Div(style={'height': '20px'}),
            html.H2('Dash示例留言板'),
            dbc.Container(
                id='history-message',
                style={
                    'paddingTop': '50px',
                    'width': '70%',
                    'height': '70%',
                    'overflowY': 'auto',
                    'backgroundColor': '#fafafa'
                }
            ),
            dbc.Container(
                dbc.Row(
                    [
                        dbc.Col(
                            dbc.Input(placeholder='输入昵称:', id='nickname', style={'width': '100%'}),
                            width=3,
                            style={
                                'padding': 0
                            }
                        ),
                        dbc.Col(
                            dbc.Input(placeholder='输入留言内容:', id='message', style={'width': '100%'}),
                            width=7,
                            style={
                                'padding': 0
                            }
                        ),
                        dbc.Col(
                            dbc.Button('提交', id='submit', color='primary', block=True),
                            width=2,
                            style={
                                'padding': 0
                            }
                        )
                    ]
                ),
                style={
                    'paddingTop': '10px',
                    'width': '70%',
                }
            )
        ],
        style={
            'height': '800px',
            'boxShadow': 'rgb(0 0 0 / 20%) 0px 13px 30px, rgb(255 255 255 / 80%) 0px -13px 30px',
            'borderRadius': '10px'
        }
    ),
    style={
        'paddingTop': '50px'
    }
)


@app.callback(
    Output('history-message', 'children'),
    Input('submit', 'n_clicks'),
    [State('nickname', 'value'),
     State('message', 'value')]
)
def refresh_message_board(n_clicks, nickname, message):
    if nickname and message:
        submit_new_message(nickname, message)

    return [
        html.Div(
            [
                html.Strong(record['nickname']),
                html.Span(' '),
                html.Em(record['pub_datetime'].strftime(format='%Y-%m-%d %H:%M:%S')),
                html.Br(),
                html.P(record['message_content'])
            ]
        )
        for record in fetch_all_message()
    ]


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

  有关peewee的内容非常丰富,想要完全记住不太现实,大家可以养成多查官网http://docs.peewee-orm.com/en/latest/的习惯,内容非常详细生动,给官方点个赞!

  以上就是本文的全部内容,欢迎在评论区发表你的意见和想法。

0 人点赞