Tornado学习笔记第五篇-peewee功能介绍

上篇我们使用原生的SQL进行更新数据库,这篇我们学习下使用ORM

我们看下使用ORM的一些好处:

  1. 隔离数据库之间的差异(不在乎数据库驱动和数据类型,接口一致)
  2. 便于维护
  3. orm会提供防止sql注入等功能
  4. 变量传递式的调用更加简单

这节我们学习的ORM框架是peeweepeewee简单,灵活,申明方式和djangoorm接近。

其中async-peewee是基于asynciopeewee的异步IO库。

peewee的github地址文档

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方法之前 实例 supplierid值是为空的,只有调用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_idselect的返回值是不一样的,前者返回的数据库实例对象,是直接拼凑好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做成了协程方式,可以看下源码结构知道哪些是协程方式了。

几篇不错的参考文章:

peewee查询

使用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 %}
        {{ field.label.text }} :
        {{ field(placeholder="请输入"+field.label.text) }}

        {% if field.errors %}
            {% for error_msg in field.errors %}
                
{{ error_msg }}
{% end %} {% else %}
{% end %} {% end %}

你可能感兴趣的:(Tornado学习笔记第五篇-peewee功能介绍)