ORM 基础

未匹配的标注
本文档最新版为 4.0,旧版本可能放弃维护,推荐阅读最新版!

介绍

ORM使用ActiveRecord方式实现来操作数据库。每个数据库都有对应的Model与表对应。

在开始之前,确保配置好了DatabaseManager,参考基本操作章节。

from orator import DatabaseManager

config = {
    'mysql': {
        'driver': 'mysql',
        'host': 'localhost',
        'database': 'database',
        'user': 'root',
        'password': '',
        'prefix': ''
    }
}

db = DatabaseManager(config)

基本操作

在开始前,你需要告诉ORM使用配置好的DatabaseManager,且让所有继承Model类模型生效:

from orator import Model

Model.set_connection_resolver(db)

差不多是这样了。现在可以定义我们的模型了。

定义模型

class User(Model):
    pass

注意这里我们没有告诉ORM模型User对应的表。默认会使用复数的"蛇形命名法"来作为对应的表名。本例中,ORM会使用users表来对应User模型。你可以通过__table__属性来指定表名:

class User(Model):

    __table__ = 'my_users'

ORM还会假定每个表都还有一个id主键。你可以自定义__primary_key__属性来覆盖默认规定字段。,你可以定义__connection__属性来覆盖模型使用的数据库连接。

一旦模型定义好了,你就可以为你的表创建和获取记录。注意,默认你必须为你的表创建updated_atcreated_at字段。如果你不想在表中维护这两个字段,你需要在模型中设置__timestamps__属性为False。

你还可以使用make:model命令来创建模型类:

orator make:model User

这将创建User.py文件在models目录(可以通过提供-p/-path选项来指定目录)。

你还可以通过提供-m/--migration参数来告诉Orator同时创建迁移文件:

orator make:model User -m

获取所有集合

users = User.all()

通过主键返回匹配结果

user = User.find(1)

print(user.name)

所有查询方法参考查询构造器文档。

通过主键查询,未找到抛出异常

有时我们在没有查询到结果时希望抛出异常。find_or_fail方法就会在未查询到指定主键结果时抛出ModelNotFound异常。

model = User.find_or_fail(1)

model = User.where('votes', '>', 100).first_or_fail()

使用模型查询

users = User.where('votes', '>', 100).take(10).get()

for user in users:
    print(user.name)

聚合

还可以通过查询构造器使用聚合函数:

count = User.where('votes', '>', 100).count()

如果你觉得构造器有局限的话,还可以使用where_raw方法:

users = User.where_raw('age > ? and votes = 100', [25]).get()

分块结果

如果要处理大量结果集,可以使用chunk方法避免使大量内存:

for users in User.chunk(100):
    for user in users:
        # ...

指定查询连接

你可以在模型构造查询时通过on方法来指定要使用数据库连接:

user = User.on('connection-name').find(1)

如果你使用了读写分离连接,你可以通过以下方式强制使用写连接:

user = User.on_write_connection().find(1)

批量赋值

当新建模型时,你通过传递属性给构造器。这些属性通过批量赋值的方式传递给模型。使用很简单,由于用户可以任意修改模型属性,当接收用户输入赋值给模型可能会带来安全性问题。出于该原因,所有模型通过批量赋值方式进行保护。

通过设置__fillable__或者__guarded__属性来实现。

定义模型可填充属性

__fillable__属性定义哪些属性可以批量赋值。

class User(Model):

    __fillable__ = ['first_name', 'last_name', 'email']

定义模型保护属性

__guarded__提供相对的“黑名单”作用。

class User(Model):

    __guarded__ = ['id', 'password']

虽然有__guarded__,也应该不直接传递用户输入给属性。

你可以阻止所有属性批量赋值:

__guarded__ = ['*']

插入,更新和删除

保存新模型

要在数据库创建新记录,简单使用模型的save方法即可。

user = User()

user.name = 'John'

user.save()

模型默认存在自动增长的主键,如果你希望自行维护主键的话通过将__incrementing__属性设置为False来实现。

你还可以直接使用create方法以便能在一行代码方式来保存模型,不过确保你设置好了模型的__fillable__或者__guarded__属性,因为默认所有的模型是禁止批量赋值的。

在创建并保存宝兴后,我们可以通过自增的id属性来获取该对象:

inserted_id = user.id

使用create方法

# Create a new user in the database
user = User.create(name='John')

# Retrieve the user by attributes, or create it if it does not exist
user = User.first_or_create(name='John')

# Retrieve the user by attributes, or instantiate it if it does not exist
user = User.first_or_new(name='John')

更新获取到的模型

user = User.find(1)

user.name = 'Foo'

user.save()

你可以使用update方法来更新系列模型:

affected_rows = User.where('votes', '>', 100).update(status=2)

保存模型以及表关系

有时你不单想保存模型,也同时想更新它的表关系。使用push方法来实现:

user.push()

删除模型

使用delete方法来删除模型:

user = User.find(1)

user.delete()

通过主键删除模型

User.destroy(1)

User.destroy(1, 2, 3)

也可以在查询构造后直接使用delete方法:

affected_rows = User.where('votes', '>' 100).delete()

只更新模型时间戳

通过使用touch方法来只更新模型的时间戳:

user.touch()

软删除

当使用软删除时,它并没有从数据库中删除记录。而是配置了deleted_at时间戳。要使模型实现软删除,需要继承SoftDeletes混合类:

from orator import Model, SoftDeletes

class User(SoftDeletes, Model):

    __dates__ = ['deleted_at']

你可以在迁移中使用soft_delete方法为你的表增加delete_at字段。 (查看 Schema 构造器):

table.soft_deletes()

现在,当在模型中调用delete方法,deleted_at字段会设置为当前时间戳。当查询使用了软删除的模型时,删除的模型将不会出现在查询结果中。

强制查询结果集包含删除内容

要强制软删除记录集出现在查询结果中,通过在查询构造器中使用with_trashed方法来实现:

users = User.with_trashed().where('account_id', 1).get()

with_trashed也可以用在定义了表关联中:

user.posts().with_trashed().get()

表关联

0.7.1版本中,装饰器注解是唯一支持定义表关联的方法。

之前通过属性标注方法已经废弃并不再支持了。在下个主要版本将会被移除。

Orator管理表关联非常容易。支持多种表关联关系:

一对一

定义一对一关联

因为一对一关联是非常基础的表关联关系。例如,一个User模型有一个Phone模型。我们可以如下在ORM进行实现:

from orator.orm import has_one

class User(Model):

    @has_one
    def phone(self):
        return Phone

返回的值为相应的类。一旦定义好了表关联,我们就可以使用动态属性:

phone = User.find(1).phone

SQL执行语句类似如下:

SELECT * FROM users WHERE id = 1

SELECT * FROM phones WHERE user_id = 1

Orator ORM假定外键关联为模型的名字。本例中,Phone模型假定使用user_id为外键。如果你想更改该默认行为,你可以传递第一个参数给has_one装饰器函数。此外,你可以还可以提供第二个参数要定义关联的本表字段:

@has_one('foreign_key')
def phone(self):
    return Phone

@has_one('foreign_key', 'local_key')
def phone(self):
    return Phone

定义反向关联

要定义Phone模型的反向关联,我们可以使用belongs_to装饰器:

from orator.orm import belongs_to

class Phone(Model):

    @belongs_to
    def user(self):
        return User

上面例子中,Orator ORM将会查找phones表的user_id字段,你可以通过传递第一个参数给belongs_to装饰器来指定使用的外键字段:

@belongs_to('local_key')
def user(self):
    return User

此外,你可以通过传递第二个参数来指定关联的父表字段:

@belongs_to('local_key', 'parent_key')
def user(self):
    return User

一对多

文档博客例子博文里的评论提供了一对多的表关联:

from orator.orm import has_many

class Post(Model):

    @has_many
    def comments(self):
        return Comment

现在,咱们就可以通过动态属性访问post的comments:

comments = Post.find(1).comments

同样,你可以通过传递第一个参数给has_many装饰器来定义外键,与has_one定义表关联一样,可以通过第二个参数来指定本表字段:

@has_many('foreign_key')
def comments(self):
    return Comment

@has_many('foreign_key', 'local_key')
def comments(self):
    return Comment

定义方向关联

要定义Comment模型的反向关联关系,我们需要使用belongs_to方法:

from orator.orm import belongs_to

class Comment(Model):

    @belongs_to
    def post(self):
        return Post

多对多

多对多是稍微复杂的表关联类型。例如,用户有多个角色,角色被多个用户所有。例如,许多用户具备“管理员”角色。这里需要三个表:users, rolesroles_usersroles_users表根据字母表顺序排列,且含有user_idrole_id字段。

我们使用belongs_to_many装饰器来定义多对多表联系:

from orator.orm import belongs_to_many

class User(Model):

    @belongs_to_many
    def roles(self):
        return Role

现在我们可以通过如下方式从User模型获取角色:

roles = User.find(1).roles

如果要使用非默认第三关联表,你可以通过传递第一个参数给belongs_to_many来指明其表名:

@belongs_to_many('user_role')
def roles(self):
    return Role

你还可以指定两表所关联的字段名:

@belongs_to_many('user_role', 'user_id', 'foo_id')
def roles(self):
    return Role

同样,咱们可以在Role模型内定义反向关联:

class Role(Model):

    @belongs_to_many
    def users(self):
        return User

第三方关联多对多

关联提供了方便的途径让我们可以通过中间关系来访闻其关联表。例如,Country模型通过User模型含有多个Post对象,该表实现其关联方式类似如下:

countries
    id - integer
    name - string

users
    id - integer
    country_id - integer
    name - string

posts
    id - integer
    user_id - integer
    title - string

尽管posts表不包含country_id字段,hash_many_through关联允许我们通过country.posts访问country所包含的posts:

from orator.orm import has_many_through

class Country(Model):

    @has_many_through(User)
    def posts(self):
        return Post

若果你想手动指定其关联键,可以通过装饰器中的第二和第三个参数来指明:

@has_many_through(User, 'country_id', 'user_id')
def posts(self):
    return Post

多态关联

多态关联允许一个模型在单个关联让其属于多个模型。例如,你有一个Photo模型,它属于StaffOrder模型。

from orator.orm import morph_to, morph_many

class Photo(Model):

    @morph_to
    def imageable(self):
        return

class Staff(Model):

    @morph_many('imageable')
    def photos(self):
        return Photo

class Order(Model):

    @morph_many('imageable')
    def photos(self):
        return Photo

获取多态关联

现在我们可以通过Staff或者Order来获得photos:

staff = Staff.find(1)

for photo in staff.photos:
    # ...

获取多态关联其所属

多态时期的地方在于,你可以访问Photo模型所属的staff或者order模型:

photo = Photo.find(1)

imageable = photo.imageable

Photo模型属性imageable返回是Staff还是Order,取决其所属模型。

多态关联表结构

要理解它是如何工作的,让我们来了解以下其多态关联的数据库表结构:

staff
    id - integer
    name - string

orders
    id - integer
    price - integer

photos
    id - integer
    path - string
    imageable_id - integer
    imageable_type - string

关键的字段为photos表的imageable_idimageable_type。其ID所保存对应关联ID。本例中,要么是staff,要么是order。type将会其所属模型的表名。这样以便ORM决定访问imageable关联时映射哪个模型。

当访问其关联时,Orator会使用imageable_type字段来获取其相应的类。

默认 这里假定其值为相关联的模型表名,所以本例为staff或者orders。如果你想覆盖此默认行为,只需添加其__morph_name__到关联表中:

class Order(Model):

    __morph_name__ = 'order'

多对多多态关联

多对多多态关联表结构

除了通常的多态关联,你还可以指明多对多的多态关联。例如,一个博客的PostVideo模型,可共享Tag模型多态关联。首先,我们看看表结构:

posts
    id - integer
    name - string

videos
    id - integer
    name - string

tags
    id - integer
    name - string

taggables
    tag_id - integer
    taggable_id - integer
    taggable_type - string

PostVideo模型两个都使用了morph_to_many装饰其tags方法:

class Post(Model):

    @morph_to_many('taggable')
    def tags(self):
        return Tag

Tag模型定义了两个模型的的关联:

class Tag(Model):

    @morphed_by_many('taggable')
    def posts(self):
        return Post

    @morphed_by_many('taggable')
    def videos(self):
        return Video

如果你想在表关联中加入查询条件,你可以通过返回一个Builder实例来替代Model的子类。

例如,你想返回用户的所有评论,通过创建日期倒序排序,并且只返回idtitle字段:

class User(Model):

    @has_many
    def comments(self):
        return (
            Comment
                .select('id', 'title', 'user_id')
                .order_by('created_at', 'desc')
        )

别忘了返回其关联的字段.

查询关联

查询结果及其关联

当访问一个模型的记录时,你可能想通过其关联来限制结果。例如,你想查询至少包含一条评论的所有博文。要实现它,你需要使用has方法:

posts = Post.has('comments').get()

这里将执行如下SQL查询:

SELECT * FROM posts
WHERE (
    SELECT COUNT(*) FROM comments
    WHERE comments.post_id = posts.id
) >= 1

你还可以指定操作符和操作数:

posts = Post.has('comments', '>', 3).get()

这里将执行:

SELECT * FROM posts
WHERE (
    SELECT COUNT(*) FROM comments
    WHERE comments.post_id = posts.id
) > 3

还可以使用"点"符号来实现嵌套的has语句:

posts = Post.has('comments.votes').get()

相应SQL语句如下:

SELECT * FROM posts
WHERE (
    SELECT COUNT(*) FROM comments
    WHERE comments.post_id = posts.id
    AND (
        SELECT COUNT(*) FROM votes
        WHERE votes.comment_id = comments.id
    ) >= 1
) >= 1

如果还需要更强大的功能,还可以放入where查询条件到where_has以及or_where_has方法中:

posts = Post.where_has(
    'comments',
    lambda q: q.where('content', 'like', 'foo%')
).get()

动态属性

Orator ORM允许通过动态属性来访问关联。它将自动为你加载其关联。可以通过其关联同名的方式来访问其动态属性。例如,以下的Post模型:

class Phone(Model):

    @belongs_to
    def user(self):
        return User

phone = Phone.find(1)

可以通过如下方式来打印用户的邮箱:

print(phone.user.email)

还有一对多的关联:

class Post(Model):

    @has_many
    def comments(self):
        return Comment

post = Post.find(1)

可以通过如下访问博文评论:

comments = post.comments

如果你需要为评论加入更多的查询过滤,你可以调用comments方法后追加链式查询条件:

comments = post.comments().where('title', 'foo').first()

关联返回的结果类型为Collection类实例。

贪婪加载

贪婪加载用来解决N + 1查询问题。例如,一个Book关联Author

class Book(Model):

    @belongs_to
    def author(self):
        return Author

现在,考虑如下代码:

for book in Book.all():
    print(book.author.name)

该循环会通过一次查询获取所有表中的书籍,然后每一次便利都需要查询作者一次,如果我们有25本书,那么这里的循环会执行26次查询。

为了减少查询次数,我们可以使用贪婪加载。这里使用with_方法来实现。

for book in Book.with_('author').get():
    print(book.author.name)

在该循环,只有两次查询:

SELECT * FROM books

SELECT * FROM authors WHERE id IN (1, 2, 3, 4, 5, ...)

你还可以一次贪婪加载多个关联:

books = Book.with_('author', 'publisher').get()

还可以贪婪加载嵌套关联:

books = Book.with_('author.contacts').get()

本例中,authorcontacts均会被贪婪加载。

贪婪加载查询

有时你想为贪婪加载加入查询条件,例如:

users = User.with_({
    'posts': Post.query().where('title', 'like', '%first%')
}).get()

这里我们仅贪婪加载其标题包含"first"内容的博文。

还可以通过传递回调方式实现:

users = User.with_({
    'posts': lambda q: q.where('title', 'like', '%first%').order_by('created_at', 'desc')
})

延迟贪婪加载

还可以对已经存在模型结果集中进行贪婪加载。这在动态决定加载相关模型或者结合缓存中非常有用。

books = Book.all()

books.load('author', 'publisher')

还可以传入查询条件:

books.load({
   'author': Author.query().where('name', 'like', '%foo%')
})

插入关联模型

我们经常需要新增关联模型,例如为博文增加评论。不用手动设置post_id外键,我们可以通过Post模型直接增加评论:

comment = Comment(message='A new comment')

post = Post.find(1)

comment = post.comments().save(comment)

如果你需要保存多个评论:

comments = [
    Comment(message='Comment 1'),
    Comment(message='Comment 2'),
    Comment(message='Comment 3')
]

post = Post.find(1)

post.comments().save_many(comments)

关联模型 (Belongs To)

当更新belongs_to关联时,我们可以使用关联方法:

account = Account.find(1)

user.account().associate(account)

user.save()

插入关联模型 (Many to Many)

在多对多关联中同样可以直接插入关联模型。例如,UserRoles模型:

附加多对多模型

user = User.find(1)
role = Roles.find(3)

user.roles().attach(role)

# or
user.roles().attach(3)

你还可以传递一个字典,为其添加到第三方表中:

user.roles().attach(3, {'expires': expires})

attach 相对的方法为  detach:

user.roles().detach(3)

attachdetach均可以接收IDs列表:

user = User.find(1)

user.roles().detach([1, 2, 3])

user.roles().attach([{1: {'attribute1': 'value1'}}, 2, 3])

同步多对多关联

你还可以使用sync方法将对象关联到模型。sync方法接收放置到第三方表里的ID列表。执行该方法后,只有列表中的ID保留在第三方表中:

user.roles().sync([1, 2, 3])

同时追加第三方表数据

我们后可以在关联ID时,追加表中其他字段值:

user.roles().sync([{1: {'expires': True}}])

有时你仅想用一个单个简单命令来增加新关联模型,我们可以使用save方法:

role = Role(name='Editor')

User.find(1).roles().save(role)

我们同样可以追加字段值到第三方表中:

User.find(1).roles().save(role, {'expires': True})

更新关联时间戳

当一个模型belongs_to另一个模型,例如Comment属于Post,在更新子模型时同时更新父模型时间戳非常有用。例如,当Comment模型更新,你可能也想同时自动更新所属的Postupdated_at时间戳。要实现该特性,只需要增加包含关联名称的__touches__属性:。

class Comment(Model):

    __touches__ = ['posts']

    @belongs_to
    def post(self):
        return Post

现在,当你更新Comment模型,所属的Post字段updated_at字段同样会更新。

操作第三方表

使用多对多关联表时依赖一个第三方表。Orator提供了简便操作该表的能力。让我们拿UserRoles模型举例,看看我们如何访问第三方表:

user = User.find(1)

for role in user.roles:
    print(role.pivot.created_at)

我们注意到某个查询到的Role模型自动赋予了pivot属性。该属性即为第三方表模型实例,可以提供给任意其他模型。

默认,pivot对象仅包含主键。如果需要追加其他属性,我们需要在关联中进行定义:

class User(Model):

    @belongs_to_many(with_pivot=['foo', 'bar'])
    def roles(self):
        return Role

现在我们还可以通过Role模型,访问pivotfoobar属性了。

如果你希望第三方表自行维护created_atupdated_at时间戳,我们可以通过在关联中定义with_timestamps关键字来实现:

class User(Model):

    @belongs_to_many(with_timestamps=True)
    def roles(self):
        return Role

删除第三发表记录

要通过模型删除第三方表所有记录,可以使用detach方法:

User.find(1).roles().detach()

在第三方表更新记录

有时你可能需要更新第三方表,还不是移除它。使用update_existing_pivot方法实现:

User.find(1).roles().update_existing_pivot(role_id, attributes)

时间戳

默认,ORM会自动维护created_atupdated_at字段。只需表中添加这些timestamp字段。如果你不希望ORM来维护这些字段,只需要赋予__timestamp__属性为False:

class User(Model):

    __timestamps__ = False

0.8.0变更: Orator支持仅维护一个timestamp字段,例如:

class User(Model):

    __timestamps__ = ['created_at']

自定义时时间戳格式

如果你希望在使用serialize或者to_json方法时自定义时间戳各式(默认使用ISO格式),我们可以覆盖其get_date_format方法:

class User(Model):

    def get_date_format(self):
        return 'DD-MM-YY'

查询域

定义查询域

域可以方便你在模型中重用查询逻辑。要定义一个域,简单使用scope装饰器:

from orator.orm import scope

class User(Model):

    @scope
    def popular(self, query):
        return query.where('votes', '>', 100)

    @scope
    def women(self, query):
        return query.where_gender('W')

使用查询域

users = User.popular().women().order_by('created_at').get()

动态域

你的查询域期望接收参数。我们只需在查询域函数增加参数即可:

class User(Model):

    @scope
    def of_type(self, query, type):
        return query.where_type(type)

在调用查询域时传入参数:

users = User.of_type('member').get()

全局域

使用 mixins

有时你想定义一个查询域作用于所有模型查询操作。本质上,这也是Orator提供的“软删除”特性实现方式。全局查询域允许通过定义一个Scope类实现的符合类来实现此功能。

查询,让我们来顶一个复合类。例如,我们使用Orator提供的SoftDeletes类:

from orator import SoftDeletingScope

class SoftDeletes(object):

    @classmethod
    def boot_soft_deletes(cls, model_class):
        """
        Boot the soft deleting mixin for a model.
        """
        model_class.add_global_scope(SoftDeletingScope())

如果一个Orator模型继承一个复合类,并且该复合类匹配拥有boot_name_of_trait命令方法,该复合类将会在Orator模型启动时调用,提供了进行全局域注册的能力,或者做一些其他的事情。一个域必须是Scope类实现,并且实现了apply方法。

apply方法接收Builder查询对象,还有应用的Model对象,可以追加任意where子句。所以,针对我们的SoftDeletingScope,它实现类似如下:

from orator import Scope

class SoftDeletingScope(Scope):

    def apply(self, builder, model):
        """
        Apply the scope to a given query builder.

        :param builder: The query builder
        :type builder: orator.orm.builder.Builder

        :param model: The model
        :type model: orator.orm.Model
        """
        builder.where_null(model.get_qualified_deleted_at_column())

直接使用域

让我们来看一个ActiveScope类实例:

from orator import Scope

class ActiveScope(Scope):

    def apply(self, builder, model):
        return builder.where('active', 1)

现在我们可以覆盖_boot()方法来应用该域:

class User(Model):

    @classmethod
    def _boot(cls):
        cls.add_global_scope(ActiveScope())

        super(User, cls)._boot()

使用回调

全局域同样可以使用回调:

class User(Model):

    @classmethod
    def _boot(cls):
        cls.add_global_scope('active_scope', lambda query: query.where('active', 1))

        cls.add_global_scope(lambda query: query.order_by('name'))

        super(User, cls)._boot()

如你所见,你可以直接传递一个函数,或者为全局域指定一个别名,后期更容易移除它。

Accessors & mutators

Orator提供了简便的方法来设置和获取模型属性。

定义 accessor

使用accessor装饰器在模型中顶一个访问器:

from orator.orm import Model, accessor

class User(Model):

    @accessor
    def first_name(self):
        first_name = self.get_raw_attribute('first_name')

        return first_name[0].upper() + first_name[1:]

上面例子中,first_name字段拥有一个访问器。

装饰器函数必须匹配访问字段的名字。

定义 mutator

设置器使用相同方式定义:

from orator.orm import Model, mutator

class User(Model):

    @mutator
    def first_name(self, value):
        self.set_raw_attribute('first_name', value.lower())

如果要设置器之前已经有了访问器,那么可以使用访问器来完成设置:

from orator.orm import Model, accessor

class User(Model):

    @accessor
    def first_name(self):
        first_name = self.get_raw_attribute('first_name')

        return first_name[0].upper() + first_name[1:]

    @first_name.mutator
    def set_first_name(self, value):
        self.set_raw_attribute('first_name', value.lower())

反过来也可以:

from orator.orm import Model, mutator

class User(Model):

    @mutator
    def first_name(self, value):
        self.set_raw_attribute('first_name', value.lower())

    @first_name.accessor
    def get_first_name(self):
        first_name = self.get_raw_attribute('first_name')

        return first_name[0].upper() + first_name[1:]

日期设置器

版本 0.9 变更:Orator 不再支持 Arrow 处理时间。

它仅支持Pendulum替换标准的datetime类。

默认,ORM会将created_atupdated_at字段转换为 Pendulum实例,类似原生Python date和datetime模块,操作非常简单。

你可以自定义__dates__属性,或者覆盖get_dates方法来配置哪些字段是自动转换的:

class User(Model):

    __dates__ = ['synchronized_at']
class User(Model):

    def get_dates(self):
        return ['created_at']

当一个字段为date类型时,你可以将它的值设置为UNIX时间戳,YYYY-MM-DD日期格式字符串,datetime字符串,原始的date或者datetime,当然还有Pendulum实例。

要完全禁止日期设置器,简单从get_dates方法返回一个空列表。

class User(Model):

    def get_dates(self):
        return []

属性转换

如果有一些属性你想它始终转换为其他数据类型,通过设置模型的__casts__属性可以实现。否则,你就需要为每个要转换的属性定义mutator。以下是__casts__属性实现例子:

__casts__ = {
    'is_admin': 'bool'
}

现在is_admin属性虽然数据库存储的是整型,访问时总是转换为布尔类型。支持的转换类型有:int, float, str, bool, dict, list

dict转换在字段存储为序列化的JSON对象时非常有用。例如,如果数据库使用的是TEXT字段,存储了序列化的JSON,通过dict转换,访问时它将自动解码属性为字典类型:

__casts__ = {
    'options': 'dict'
}

现在,当使用模型时:

user = User.find(1)

# options 转换为了字典
options = user.options

# options 自动序列化为JSON
user.options = {'foo': 'bar'}

Model事件

orator模型会触发几个事件,允许你在模型不同生命周期时加入钩子方法:
creating, created, updating, updated, saving, saved, deleting, deleted, restoring, restored

对象首次保存时,会触发creatingcreated事件。如果save是更新操作的话,那么updating / updated事件触发。两种情况下saving / saved 事件均会触发。

在事件中取消保存操作

如果钩子方法creating, updating, saving, 或者 deleting返回False,操作就会被取消:

User.creating(lambda user: user.is_valid())

模型观察者

要整合对模型事件的处理,我们可以注册一个模型观察者。一个观察者类事件对应模型事件命名的方法:creating, updating, saving

类似如下:

class UserObserver(object):

    def saving(self, user):
        # ...

    def saved(self, user):
        # ...

使用模型的observe方法来注册观察者对象:

User.observe(UserObserver())

转换字典/JSON

转换模型为字典

在构建JSON APIs时,我们经常需要将模型以及它的关联模型转换为字典或者是JSON。所以,Orator提供了一些辅助方法。要转换一个模型以及其它的关联为字典,我们需要使用serialize方法:

user = User.with_('roles').first()

return user.serialize()

注意整个集合均可以转换为字典:

return User.all().serialize()

转换模型为 JSON

要转换模型为JSON, 你可以使用to_json方法:

return User.find(1).to_json()

字典或者JSON转换中隐藏字段

有时你期望在转换后隐藏一些字段,例如密码。通过在模型中定义__hidden__属性来实现:

class User(model):

    __hidden__ = ['password']

还可以使用__visible__属性显示定义要出现的字段:

__visible__ = ['first_name', 'last_name']

追加属性

有时,你也许想追加属性到字典中。简单定义accessor即可:

class User(Model):

    @accessor
    def is_admin(self):
        return self.get_raw_attribute('admin') == 'yes'

一旦定义好了accessor,只需将它加入模型__appends__属性:

class User(Model):

    __append__ = ['is_admin']

    @accessor
    def is_admin(self):
        return self.get_raw_attribute('admin') == 'yes'

一旦属性加入了__appends__列表,它就会在模型字典和JSON转换形式中出现了。__appends__列表同样遵循__visible____hidden__配置规则。

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/masonite/2.3/or...

译文地址:https://learnku.com/docs/masonite/2.3/or...

上一篇 下一篇
贡献者:1
讨论数量: 0
发起讨论 只看当前版本


暂无话题~