ORM 基础
介绍
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_at
和created_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
, roles
和roles_users
。roles_users
表根据字母表顺序排列,且含有user_id
和role_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
模型,它属于Staff
和Order
模型。
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_id
和imageable_type
。其ID所保存对应关联ID。本例中,要么是staff,要么是order。type将会其所属模型的表名。这样以便ORM决定访问imageable
关联时映射哪个模型。
当访问其关联时,Orator会使用imageable_type
字段来获取其相应的类。
默认 这里假定其值为相关联的模型表名,所以本例为staff
或者orders
。如果你想覆盖此默认行为,只需添加其__morph_name__
到关联表中:
class Order(Model):
__morph_name__ = 'order'
多对多多态关联
多对多多态关联表结构
除了通常的多态关联,你还可以指明多对多的多态关联。例如,一个博客的Post
和Video
模型,可共享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
Post
和Video
模型两个都使用了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
的子类。
例如,你想返回用户的所有评论,通过创建日期倒序排序,并且只返回id
和title
字段:
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()
本例中,author
和contacts
均会被贪婪加载。
贪婪加载查询
有时你想为贪婪加载加入查询条件,例如:
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)
在多对多关联中同样可以直接插入关联模型。例如,User
和Roles
模型:
附加多对多模型
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)
attach
和detach
均可以接收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
模型更新,你可能也想同时自动更新所属的Post
的updated_at
时间戳。要实现该特性,只需要增加包含关联名称的__touches__
属性:。
class Comment(Model):
__touches__ = ['posts']
@belongs_to
def post(self):
return Post
现在,当你更新Comment
模型,所属的Post
字段updated_at
字段同样会更新。
操作第三方表
使用多对多关联表时依赖一个第三方表。Orator提供了简便操作该表的能力。让我们拿User
和Roles
模型举例,看看我们如何访问第三方表:
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
模型,访问pivot
的foo
和bar
属性了。
如果你希望第三方表自行维护created_at
和updated_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_at
和updated_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_at
和updated_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
。
对象首次保存时,会触发creating
和created
事件。如果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__
配置规则。
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。