Tornado学习笔记第五篇-peewee功能介绍
上篇我们使用原生的SQL
进行更新数据库,这篇我们学习下使用ORM
。
我们看下使用ORM
的一些好处:
- 隔离数据库之间的差异(不在乎数据库驱动和数据类型,接口一致)
- 便于维护
-
orm
会提供防止sql
注入等功能 - 变量传递式的调用更加简单
这节我们学习的ORM
框架是peewee
。peewee
简单,灵活,申明方式和django
的orm
接近。
其中async-peewee
是基于asyncio
和peewee
的异步IO库。
model的定义和表的自动生成
这个小节我们将会学习如何想使用Django ORM
一样由类创建成表。
如何创建一个model
:
from peewee import *
# 指定数据库连接 一个参数是数据库名
db = MySQLDatabase('message', host="127.0.0.1", port=3306, user="root", password="root")
class Goods(Model):
# 所有的模型都需要集成自 peewee 的 Model
name = CharField(max_length=100, verbose_name="商品名称", index=True)
click_num = IntegerField(default=0, verbose_name="点击数")
goods_num = IntegerField(default=0, verbose_name="库存数")
price = FloatField(default=0.0, verbose_name="价格")
brief = TextField(verbose_name="商品简介")
class Meta:
database = db # 指定数据库对象
table_name = "goods" # 指定表名
CharField
需要指定最大长度max_length
.使用ForeignKeyField
建立外键关系。里面很多字段含义和django orm
类似。
上面完成之后我们就可以直接创建数据表了
def init_table():
# 直接调用 db 对象的 create_tables 传入的是个可迭代对象(不一定是列表) 内容是需要创建的模型类
db.create_tables([Goods])
if __name__ == "__main__":
init_table()
我们看下生成的数据表
虽然我们没有自己定义id
为主键,peewee
为我们自动添加了一个值为id
的主键。而SQLAlchemy
则是必须指定主键的。
我们可以创建一个基类将指定数据库连接对象放到基类中,这样其他需要指定数据库连接的只需要继承基类即可。
class BaseModel(Model):
add_time = DateTimeField(default=datetime.now, verbose_name="添加时间")
class Meta:
# 子类不需要设置数据库连接对象了
database = db
class Goods(BaseModel):
pass
下面我们创建具有关联关系的两个模型。
class Supplier(BaseModel):
name = CharField(max_length=100, verbose_name="名称", index=True)
address = CharField(max_length=100, verbose_name="联系地址")
phone = CharField(max_length=11, verbose_name="联系方式")
class Meta:
database = db
table_name = "supplier"
class Goods(BaseModel):
supplier = ForeignKeyField(Supplier, verbose_name="商家", backref="goods")
name = CharField(max_length=100, verbose_name="商品名称", index=True)
click_num = IntegerField(default=0, verbose_name="点击数")
goods_num = IntegerField(default=0, verbose_name="库存数")
price = FloatField(default=0.0, verbose_name="价格")
brief = TextField(verbose_name="商品简介")
class Meta:
table_name = "goods"
创建数据表
def init_table():
db.create_tables((Goods, Supplier))
if __name__ == "__main__":
init_table()
我们看到在数据表里保存的是supplier_id
而不是supplier
。我们在查询的时候如果使用supplier
传入的是一个实例对象,使用supplier_id
则是一个数值。这个和django
的一致。
model的数据保存
在上面我们创建数据表的时候,使用了IntegerField
等字段类型来指定数据类型,那peewee
都有哪些字段数据类型呢,每个字段类型又有哪些属性呢?
我们可以通过查看官方文档得知:Models and Fields
通过类的实例化形式来保存数据
def save_model():
supplier = Supplier()
supplier.name = "华为"
supplier.address = "杭州"
supplier.phone = "13788745321"
supplier.save()
if __name__ == '__main__':
save_model()
直接使用save
方法即可保存到数据库。
我们看到数据库自己为我们新增了一个主键id
值。
我们在调用
save
方法之前 实例supplier
的id
值是为空的,只有调用save
之后才有值。这一点和其他ORM
框架类似。
如果数据是字典类型,我们可以直接使用解包功能创建类实例。
goods_list = [
{
"supplier": 2,
"name": "52度茅台集团国隆双喜酒500mlx6",
"click_num": 100,
"goods_num": 666,
"price": 128,
"brief": "贵州茅台酒厂(集团)保健酒业有限公司生产,是以“龙”字打头的酒水。中国龙文化上下8000年,源远而流长,龙的形象是一种符号、一种意绪、一种血肉相联的情感。"
},
{
"supplier": 3,
"name": "52度水井坊臻酿八號500ml",
"click_num": 585,
"goods_num": 288,
"price": 36,
"brief": "贵州茅台酒厂(集团)保健酒业有限公司生产,是以“龙”字打头的酒水。中国龙文化上下8000年,源远而流长,龙的形象是一种符号、一种意绪、一种血肉相联的情感。"
}
]
for data in goods_list:
good = Goods(**data)
good.save()
注意字典的键要和模型的字段值相同。
peewee查询数据
查询的官方文档:Querying
查询获取一条数据
# 通过 id 获取数据
good = Goods.get(Goods.id == 1)
good = Goods.get_by_id(1)
# 下面同样是通过 id 获得
good = Goods[1]
如果数据不存在,将会抛出异常
获得所有数据
goods = Goods.select()
for good in goods:
print(good.name)
上面
get_by_id
和select
的返回值是不一样的,前者返回的数据库实例对象,是直接拼凑好SQL
语句去数据库查询得到的结果。后者返回的是ModelSelect
对象,不是真正数据库执行的结果。只有我们执行了下面的迭代查询之后才真正去数据库执行SQL
。(底层原理是python
的迭代协议)
这点和Django
的查询类似。
获取指定条件的数据
goods = Goods.select(Goods.name, Goods.price)
如果指定了某些字段,在遍历的时候获取没有指定字段数据的时候将返回None
。
根据条件查询
# select * from goods where price > 100
goods = Goods.select().where(Goods.price>100)
很像原生的SQL
and
条件查询
#select * from goods where price>100 and click_num>200
goods = Goods.select().where((Goods.price>100)&(Goods.click_num>200))
or
条件查询
#select * from goods where price>100 or click_num>200
goods = Goods.select().where((Goods.price>100)|(Goods.click_num>200))
like
条件查询
#select * from goods where name like "%飞天"
goods = Goods.select().where(Goods.name.contains("飞天"))
这个很像Django
的
in
条件查询
goods = Goods.select().where(Goods.id<<[1,3])
goods = Goods.select().where((Goods.id==1)|(Goods.id==3))
goods = Goods.select().where((Goods.id.in_([1,3])))
使用<<
代表in_
操作。
关于操作符的文档:Query operators
表内字段比较查询
# select * from goods where price > click_num
goods = Goods.select().where(Goods.price>Goods.click_num)
排序查询
#排序 select * from goods order by price desc
# 显示指定排序
goods = Goods.select().order_by(Goods.price.asc())
# 通过 + - 号
goods = Goods.select().order_by(+Goods.price)
# 默认升序
goods = Goods.select().order_by(Goods.price)
分页查询
#分页
goods = Goods.select().order_by(Goods.price).paginate(2, 2)
# 第一个参数是从第几个数据开始 包含 从0开始计数 第二个参数表示取多少个
官方示例文档:Query Examples
数据更新和删除
更新数据
获得对应数据,使用赋值方式更新
good = Goods.get_by_id(1)
good.click_num += 1
good.save()
使用更新语句
# update click_num=100 where id =1
Goods.update(click_num=Goods.click_num+1).where(Goods.id==1).execute()
这里需要我们再次调用execute
去真正执行SQL
,这个execute
是同步操作,正是查询和拼接SQL
是分离的,将peewee
变成异步才有可能。
使用
update
更新的字段不需要指定模型
删除数据
获得想删除的数据对象,使用delete_instance
删除。
good = Goods.get_by_id(1)
good.delete_instance()
使用delete
语句删除数据
#delete from goods where price>150
Goods.delete().where(Goods.price>150).execute()
delete
语句同样不是直接去数据库执行
通过peewee-async集成到tornado
上面学习的peewee
是同步的ORM
框架,如果我们想在tornado
中使用,我们需要异步的ORM
。
peewee-async是将peewee
变成异步的一个库
我们直接看下官方的示例:
import asyncio
import peewee
import peewee_async
# Nothing special, just define model and database:
# 定义数据库连接和 peewee 一样
database = peewee_async.MySQLDatabase(
'message',
host="127.0.0.1",
port=3306,
user="root",
password="root"
)
# 创建模型和之前一样
class TestModel(peewee.Model):
text = peewee.CharField()
class Meta:
database = database
# Look, sync code is working!
# 同步的方式创建数据库
TestModel.create_table(True)
TestModel.create(text="Yo, I can do it sync!")
database.close()
# Create async models manager:
# 如果我们想异步使用 则是需要创建一个 Manager 以后执行 SQL 语句都是靠这个 Manager
objects = peewee_async.Manager(database)
# No need for sync anymore!
# 将同步禁用
database.set_allow_sync(False)
async def handler():
# 使用协程的方式来操作 进行数据库操作需要使用 我们创建的 Manager 实例
await objects.create(TestModel, text="Not bad. Watch this, I'm async!")
all_objects = await objects.execute(TestModel.select())
# objects 负责真正的执行 SQL objects.execute() 返回的是一个协程对象 一定要使用 await
for obj in all_objects:
print(obj.text)
loop = asyncio.get_event_loop()
loop.run_until_complete(handler())
# loop.close()
# run_until_complete 执行完之后自动调用 close
# Clean up, can do it sync again:
with objects.allow_sync():
TestModel.drop_table(True)
# Expected output:
# Yo, I can do it sync!
# Not bad. Watch this, I'm async!
我们之前peewee
使用很多同步方法都被peewee_async
做成了协程方式,可以看下源码结构知道哪些是协程方式了。
几篇不错的参考文章:
使用WTForms进行数据验证
这个小节和之前在学习Flask
中使用WTForms
中试一致的。
注意一点:
如果我们在tornado
中直接使用wtforms
的话是会报错的。
message_from = MessageForm(self.request.arguments)
我们可以安转一个wtforms-tornado库来解决
安装成功后将继承的基类Form
换成wtforms_tornado
的。
from wtforms_tornado import Form
class SumForm(Form):
a = IntegerField(validators=[Required()])
b = IntegerField(validators=[Required()])
这样直接使用self.request.arguments
就不会报错了。
我们看下模板中如何使用form
{% autoescape None %}
{% for field in message_form %}
<span>{{ field.label.text }} :</span>
{{ field(placeholder="请输入"+field.label.text) }}
{% if field.errors %}
{% for error_msg in field.errors %}
<div class="error-msg">{{ error_msg }}</div>
{% end %}
{% else %}
<div class="error-msg"></div>
{% end %}
{% end %}