1 简介
这是我的系列教程「Python+Dash快速web应用开发」的第十七期,在之前的各期教程中,我们针对Dash
中各种基础且常用的概念展开了学习,但一直没有针对与数据库之间交互进行专门的介绍,只是在某些示例中利用pandas
、SQLAlchemy
等工具简陋地操作数据库。
而在今天的教程中,我就将带大家学习在Dash
中利用简单好用的ORM
库peewee
,快速高效地将数据库整合进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
❞
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
还支持MySQL
、PostgreSQL
,你可以在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()
方法:
# 创建单条记录 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()
方法才会真正向数据库执行:
# 批量插入数据 ( 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的记录:
# 删除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()
如下面的例子那样:
# 修改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()
方法,常用的有以下方式:
# 获取查询结果方式1: query_results = Model1.select().where(Model1.level > 2).execute() for query_result in query_results: print(query_result.username)
图8
# 获取查询结果方式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
为例:
python -m pwiz -e sqlite model1.db >model2.py
自动生成的model2.py
代码如下,在这个基础上我们可以进一步的优化修改:
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实现在线留言板功能
get
到peewee
的常用基础用法之后,我们回到本文的重点——结合Dash
整合数据库,要实现的功能很简单,就是实现一个在线留言板,每个访问应用的用户都可以在填写若干信息后,发表自己的留言,其他用户后续访问可以看到前面用户发表过的留言信息。
为了方便演示,我选择SQLite
作为示例数据库,首先我们需要构建一个model.py
来设计表模型,来存放每条留言信息,并自定义一些功能函数:
❝model.py
❞
from peewee import SqliteDatabase, Model from peewee import CharField, DateTimeField, TextField from datetime import datetime db = SqliteDatabase('17 整合数据库/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
❞
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)