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

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

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

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

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

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

peeweegithub地址文档

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 %}
        <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 %}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容