The ORM provides a simple ActiveRecord implementation for working with your databases. Each database table has a corresponding Model which is used to interact with that table.
Before getting started, be sure to have configured a DatabaseManager as seen in the Basic Usage section.
from orator import DatabaseManager
config = {
'mysql': {
'driver': 'mysql',
'host': 'localhost',
'database': 'database',
'user': 'root',
'password': '',
'prefix': ''
}
}
db = DatabaseManager(config)
To actually get started, you need to tell the ORM to use the configured DatabaseManager for all models
inheriting from the Model class:
from orator import Model
Model.set_connection_resolver(db)
And that’s pretty much it. You can now define your models.
class User(Model):
pass
Note that we did not tell the ORM which table to use for the User model. The plural “snake case” name of the
class name will be used as the table name unless another name is explicitly specified.
In this case, the ORM will assume the User model stores records in the users table.
You can specify a custom table by defining a __table__ property on your model:
class User(Model):
__table__ = 'my_users'
Note
The ORM will also assume that each table has a primary key column named id.
You can define a __primary_key__ property to override this convention.
Likewise, you can define a __connection__ property to override the name of the database
connection that should be used when using the model.
Once a model is defined, you are ready to start retrieving and creating records in your table.
Note that you will need to place updated_at and created_at columns on your table by default.
If you do not wish to have these columns automatically maintained,
set the __timestamps__ property on your model to False.
Note
You can also create a model class with the make:model command:
orator make:model User
will create a user.py file in the models directory (the path can be overidden with the -p/--path option).
You can also tell Orator to create a migration file with the -m/--migration option:
orator make:model User -m
users = User.all()
user = User.find(1)
print(user.name)
Note
All methods available on the Query Builder are also available when querying models.
Sometimes it may be useful to throw an exception if a model is not found.
You can use the find_or_fail method for that, which will raise a ModelNotFound exception.
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)
You can also use the query builder aggregate functions:
count = User.where('votes', '>', 100).count()
If you feel limited by the builder’s fluent interface, you can use the where_raw method:
users = User.where_raw('age > ? and votes = 100', [25]).get()
If you need to process a lot of records, you can use the chunk method to avoid
consuming a lot of RAM:
for users in User.chunk(100):
for user in users:
# ...
You can specify which database connection to use when querying a model by using the on method:
user = User.on('connection-name').find(1)
If you are using Read / Write connections, you can force the query to use the “write” connection with the following method:
user = User.on_write_connection().find(1)
When creating a new model, you pass attributes to the model constructor. These attributes are then assigned to the model via mass-assignment. Though convenient, this can be a serious security concern when passing user input into a model, since the user is then free to modify any and all of the model’s attributes. For this reason, all models protect against mass-assignment by default.
To get started, set the __fillable__ or __guarded__ properties on your model.
The __fillable__ property specifies which attributes can be mass-assigned.
class User(Model):
__fillable__ = ['first_name', 'last_name', 'email']
The __guarded__ is the inverse and acts as “blacklist”.
class User(Model):
__guarded__ = ['id', 'password']
Warning
When using __guarded__, you should still never pass any user input directly since
any attribute that is not guarded can be mass-assigned.
You can also block all attributes from mass-assignment:
__guarded__ = ['*']
To create a new record in the database, simply create a new model instance and call the save method.
user = User()
user.name = 'John'
user.save()
Note
Your models will probably have auto-incrementing primary keys. However, if you wish to maintain
your own primary keys, set the __incrementing__ property to False.
You can also use the create method to save a model in a single line, but you will need to specify
either the __fillable__ or __guarded__ property on the model since all models are protected against
mass-assigment by default.
After saving or creating a new model with auto-incrementing IDs, you can retrieve the ID by accessing
the object’s id attribute:
inserted_id = user.id
# 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()
You can also run updates as queries against a set of models:
affected_rows = User.where('votes', '>', 100).update(status=2)
Sometimes you may wish to save not only a model, but also all of its relationships.
To do so, you can use the push method:
user.push()
To delete a model, simply call the delete model:
user = User.find(1)
user.delete()
User.destroy(1)
User.destroy(1, 2, 3)
You can also run a delete query on a set of models:
affected_rows = User.where('votes', '>' 100).delete()
If you want to only update the timestamps on a model, you can use the touch method:
user.touch()
When soft deleting a model, it is not actually removed from your database.
Instead, a deleted_at timestamp is set on the record.
To enable soft deletes for a model, make it inherit from the SoftDeletes mixin:
from orator import Model, SoftDeletes
class User(SoftDeletes, Model):
__dates__ = ['deleted_at']
To add a deleted_at column to your table, you may use the soft_deletes method from a migration (see Schema Builder):
table.soft_deletes()
Now, when you call the delete method on the model, the deleted_at column will be
set to the current timestamp. When querying a model that uses soft deletes,
the “deleted” models will not be included in query results.
To force soft deleted models to appear in a result set, use the with_trashed method on the query:
users = User.with_trashed().where('account_id', 1).get()
The with_trashed method may be used on a defined relationship:
user.posts().with_trashed().get()
Changed in version 0.7.1: As of version 0.7.1, the decorator notation is the only one supported.
The previous notation (via properties) is now deprecated and is no longer supported. It will be removed in the next major version.
Orator makes managing and working with relationships easy. It supports many types of relationships:
A one-to-one relationship is a very basic relation. For instance, a User model might have a Phone.
We can define this relation with the ORM:
from orator.orm import has_one
class User(Model):
@has_one
def phone(self):
return Phone
The return value of the relation is the class of the related model. Once the relationship is defined, we can retrieve it using Dynamic properties:
phone = User.find(1).phone
The SQL performed by this statement will be as follow:
SELECT * FROM users WHERE id = 1
SELECT * FROM phones WHERE user_id = 1
The Orator ORM assumes the foreign key of the relationship based on the model name. In this case,
Phone model is assumed to use a user_id foreign key. If you want to override this convention,
you can pass a first argument to the has_one decorator. Furthermore, you may pass a second argument
to the decorator to specify which local column should be used for the association:
@has_one('foreign_key')
def phone(self):
return Phone
@has_one('foreign_key', 'local_key')
def phone(self):
return Phone
To define the inverse of the relationship on the Phone model, you can use the belongs_to decorator:
from orator.orm import belongs_to
class Phone(Model):
@belongs_to
def user(self):
return User
In the example above, the Orator ORM will look for a user_id column on the phones table. You can
define a different foreign key column, you can pass it as the first argument of the belongs_to decorator:
@belongs_to('local_key')
def user(self):
return User
Additionally, you can pass a third parameter which specifies the name of the associated column on the parent table:
@belongs_to('local_key', 'parent_key')
def user(self):
return User
An example of a one-to-many relation is a blog post that has many comments:
from orator.orm import has_many
class Post(Model):
@has_many
def comments(self):
return Comment
Now you can access the post’s comments via Dynamic properties:
comments = Post.find(1).comments
Again, you may override the conventional foreign key by passing a first argument to the has_many decorator.
And, like the has_one relation, the local column may also be specified:
@has_many('foreign_key')
def comments(self):
return Comment
@has_many('foreign_key', 'local_key')
def comments(self):
return Comment
To define the inverse of the relationship on the Comment model, we use the belongs_to method:
from orator.orm import belongs_to
class Comment(Model):
@belongs_to
def post(self):
return Post
Many-to-many relations are a more complicated relationship type.
An example of such a relationship is a user with many roles, where the roles are also shared by other users.
For example, many users may have the role of “Admin”. Three database tables are needed for this relationship:
users, roles, and roles_users.
The roles_users table is derived from the alphabetical order of the related table names,
and should have the user_id and role_id columns.
We can define a many-to-many relation using the belongs_to_many decorator:
from orator.orm import belongs_to_many
class User(Model):
@belongs_to_many
def roles(self):
return Role
Now, we can retrieve the roles through the User model:
roles = User.find(1).roles
If you want to use an unconventional table name for your pivot table, you can pass it as the first argument
to the belongs_to_many method:
@belongs_to_many('user_role')
def roles(self):
return Role
You can also override the conventional associated keys:
@belongs_to_many('user_role', 'user_id', 'foo_id')
def roles(self):
return Role
Of course, you also can define the inverse of the relationship on the Role model:
class Role(Model):
@belongs_to_many
def users(self):
return User
The “has many through” relation provides a convenient short-cut
for accessing distant relations via an intermediate relation.
For example, a Country model might have many Post through a User model.
The tables for this relationship would look like this:
countries
id - integer
name - string
users
id - integer
country_id - integer
name - string
posts
id - integer
user_id - integer
title - string
Even though the posts table does not contain a country_id column, the has_many_through relation
will allow access to a country’s posts via country.posts:
from orator.orm import has_many_through
class Country(Model):
@has_many_through(User)
def posts(self):
return Post
If you want to manually specify the keys of the relationship, you can pass them as the second and third arguments to the decorator:
@has_many_through(User, 'country_id', 'user_id')
def posts(self):
return Post
Polymorphic relations allow a model to belong to more than one other model, on a single association.
For example, you might have a Photo model that belongs to either a Staff model or an Order model.
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
Now, we can retrieve the photos for either a staff member or an order:
staff = Staff.find(1)
for photo in staff.photos:
# ...
You can also, and this is where polymorphic relations shine, access the staff or
order model from the Photo model:
photo = Photo.find(1)
imageable = photo.imageable
The imageable relation on the Photo model will return either a Staff or Order instance,
depending on which type of model owns the photo.
To help understand how this works, let’s explore the database structure for a polymorphic relation:
staff
id - integer
name - string
orders
id - integer
price - integer
photos
id - integer
path - string
imageable_id - integer
imageable_type - string
The key fields to notice here are the imageable_id and imageable_type on the photos table.
The ID will contain the ID value of, in this example, the owning staff or order,
while the type will contain the class name of the owning model.
This is what allows the ORM to determine which type of owning model
to return when accessing the imageable relation.
Note
When accessing or loading the relation, Orator will retrieve the related class using
the imageable_type column value.
By default it will assume this value is the table name of the related model,
so in this example staff or orders. If you want to override this convention, just add the __morph_name__
attribute to the related class:
class Order(Model):
__morph_name__ = 'order'
In addition to traditional polymorphic relations, you can also specify many-to-many polymorphic relations.
For example, a blog Post and Video model could share a polymorphic relation to a Tag model.
First, let’s examine the table structure:
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
The Post and Video model will both have a morph_to_many relationship via a tags method:
class Post(Model):
@morph_to_many('taggable')
def tags(self):
return Tag
The Tag model can define a method for each of its relationships:
class Tag(Model):
@morphed_by_many('taggable')
def posts(self):
return Post
@morphed_by_many('taggable')
def videos(self):
return Video
Note
If you want to apply permanent query conditions on your relationships
you can do so by returning a Builder instance instead of a Model
subclass.
For example, let’s say you want all comments of a user to be ordered by
date of creation in descending order and only the id and title columns
of each comment:
class User(Model):
@has_many
def comments(self):
return (
Comment
.select('id', 'title', 'user_id')
.order_by('created_at', 'desc')
)
Don’t forget to include the column you’re joining on.
When accessing the records for a model, you may wish to limit the results based on the exeistence
of a relationship. For example, you may wish to retrieve all blog posts that have at least one comment.
To actually do so, you can use the has method:
posts = Post.has('comments').get()
This would execute the following SQL query:
SELECT * FROM posts
WHERE (
SELECT COUNT(*) FROM comments
WHERE comments.post_id = posts.id
) >= 1
You can also specify an operator and a count:
posts = Post.has('comments', '>', 3).get()
This would execute:
SELECT * FROM posts
WHERE (
SELECT COUNT(*) FROM comments
WHERE comments.post_id = posts.id
) > 3
Nested has statements can also be constructed using “dot” notation:
posts = Post.has('comments.votes').get()
And the corresponding SQL query:
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
If you need even more power, you can use the where_has and or_where_has methods
to put “where” conditions on your has queries:
posts = Post.where_has(
'comments',
lambda q: q.where('content', 'like', 'foo%')
).get()
The Orator ORM allows you to access your relations via dynamic properties.
It will automatically load the relationship for you. It will then be accessible via
a dynamic property by the same name as the relation. For example, with the following model Post:
class Phone(Model):
@belongs_to
def user(self):
return User
phone = Phone.find(1)
You can then print the user’s email like this:
print(phone.user.email)
Now, for one-to-many relationships:
class Post(Model):
@has_many
def comments(self):
return Comment
post = Post.find(1)
You can then access the post’s comments like this:
comments = post.comments
If you need to add further constraints to which comments are retrieved,
you may call the comments method and continue chaining conditions:
comments = post.comments().where('title', 'foo').first()
Note
Relationships that return many results will return an instance of the Collection class.
Eager loading exists to alleviate the N + 1 query problem. For example, consider a Book that is related
to an Author:
class Book(Model):
@belongs_to
def author(self):
return Author
Now, consider the following code:
for book in Book.all():
print(book.author.name)
This loop will execute 1 query to retrieve all the books on the table, then another query for each book to retrieve the author. So, if we have 25 books, this loop will run 26 queries.
To drastically reduce the number of queries you can use eager loading. The relationships that should be
eager loaded can be specified via the with_ method.
for book in Book.with_('author').get():
print(book.author.name)
In this loop, only two queries will be executed:
SELECT * FROM books
SELECT * FROM authors WHERE id IN (1, 2, 3, 4, 5, ...)
You can eager load multiple relationships at one time:
books = Book.with_('author', 'publisher').get()
You can even eager load nested relationships:
books = Book.with_('author.contacts').get()
In this example, the author relationship will be eager loaded as well as the author’s contacts
relation.
Sometimes you may wish to eager load a relationship but also specify a condition for the eager load. Here’s an example:
users = User.with_({
'posts': Post.query().where('title', 'like', '%first%')
}).get()
In this example, we’re eager loading the user’s posts only if the post’s title contains the word “first”.
You can also use a callback:
users = User.with_({
'posts': lambda q: q.where('title', 'like', '%first%').order_by('created_at', 'desc')
})
It is also possible to eagerly load related models directly from an already existing model collection. This may be useful when dynamically deciding whether to load related models or not, or in combination with caching.
books = Book.all()
books.load('author', 'publisher')
You can also pass conditions:
books.load({
'author': Author.query().where('name', 'like', '%foo%')
})
When a model belongs_to another model, like a Comment belonging to a Post, it is often helpful
to update the parent’s timestamp when the chil model is updated. For instance, when a Comment model is updated,
you may want to automatically touch the updated_at timestamp of the owning Post. For this to actually happen,
you just have to add a __touches__ property containing the names of the relationships:
class Comment(Model):
__touches__ = ['posts']
@belongs_to
def post(self):
return Post
Now, when you update a Comment, the owning Post will have its updated_at column updated.
Working with many-to-many reationships requires the presence of an intermediate table. Orator makes it easy to
interact with this table. Let’s take the User and Roles models and see how you can access the pivot table:
user = User.find(1)
for role in user.roles:
print(role.pivot.created_at)
Note that each retrieved Role model is automatically assigned a pivot attribute. This attribute contains e model
instance representing the intermediate table, and can be used as any other model.
By default, only the keys will be present on the pivot object. If your pivot table contains extra attributes,
you must specify them when defining the relationship:
class User(Model):
@belongs_to_many(with_pivot=['foo', 'bar'])
def roles(self):
return Role
Now the foo and bar attributes will be accessible on the pivot object for the Role model.
If you want your pivot table to have automatically maintained created_at and updated_at timestamps,
use the with_timestamps keyword argument on the relationship definition:
class User(Model):
@belongs_to_many(with_timestamps=True)
def roles(self):
return Role
To delete all records on the pivot table for a model, you can use the detach method:
User.find(1).roles().detach()
Sometimes you may need to update your pivot table, but not detach it.
If you wish to update your pivot table in place you may use update_existing_pivot method like so:
User.find(1).roles().update_existing_pivot(role_id, attributes)
By default, the ORM will maintain the created_at and updated_at columns on your database table
automatically. Simply add these timestamp columns to your table. If you do not wish for the ORM to maintain
these columns, just add the __timestamps__ property:
class User(Model):
__timestamps__ = False
Changed in version 0.8.0: Orator supports maintaining only one timestamp column, like so:
class User(Model):
__timestamps__ = ['created_at']
If you whish to customize the format of your timestamps (the default is the ISO Format) that will be returned when using the serialize
or the to_json methods, you can override the get_date_format method:
class User(Model):
def get_date_format(self):
return 'DD-MM-YY'
Scopes allow you to easily re-use query logic in your models.
To define a scope, simply use the scope decorator:
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()
Sometimes you may wish to define a scope that accepts parameters. Just add your parameters to your scope function:
class User(Model):
@scope
def of_type(self, query, type):
return query.where_type(type)
Then pass the parameter into the scope call:
users = User.of_type('member').get()
Sometimes you may wish to define a scope that applies to all queries performed on a model.
In essence, this is how Orator’s own “soft delete” feature works.
Global scopes can be defined using a combination of mixins and an implementation of the Scope class.
First, let’s define a mixin. For this example, we’ll use the SoftDeletes that ships with Orator:
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())
If an Orator model inherits from a mixin that has a method matching the boot_name_of_trait
naming convention, that mixin method will be called when the Orator model is booted,
giving you an opportunity to register a global scope, or do anything else you want.
A scope must be an instance of the Scope class, which specify an apply method.
The apply method receives an Builder query builder object and the Model it’s applied to,
and is responsible for adding any additional where clauses that the scope wishes to add.
So, for our SoftDeletingScope, it would look something like this:
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())
Let’s take the example of an ActiveScope class:
from orator import Scope
class ActiveScope(Scope):
def apply(self, builder, model):
return builder.where('active', 1)
You can now override the _boot() method of the model to apply the scope:
class User(Model):
@classmethod
def _boot(cls):
cls.add_global_scope(ActiveScope())
super(User, cls)._boot()
Global scopes can also be set using callables:
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()
As you can see, you can directly pass a function or specify an alias for the global scope to remove it more easily later on.
Orator provides a convenient way to transform your model attributes when getting or setting them.
Simply use the accessor decorator on your model to declare an 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:]
In the example above, the first_name column has an accessor.
Note
The name of the decorated function must match the name of the column being accessed.
Mutators are declared in a similar fashion:
from orator.orm import Model, mutator
class User(Model):
@mutator
def first_name(self, value):
self.set_raw_attribute('first_name', value.lower())
Note
If the column being mutated already has an accessor, you can use it has a mutator:
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())
The inverse is also possible:
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:]
Changed in version 0.9: Orator no longer officially supports Arrow instances as datetimes.
It will now only supports Pendulum instances which are a drop-in replacement
for the standard datetime class.
By default, the ORM will convert the created_at and updated_at columns
to instances of Pendulum,
which eases date and datetime manipulation while behaving exactly like the native Python date and datetime.
You can customize which fields are automatically mutated, by either adding them with the __dates__ property or
by completely overriding the get_dates method:
class User(Model):
__dates__ = ['synchronized_at']
class User(Model):
def get_dates(self):
return ['created_at']
When a column is considered a date, you can set its value to a UNIX timestamp, a date string YYYY-MM-DD,
a datetime string, a native date or datetime and of course an Pendulum instance.
To completely disable date mutations, simply return an empty list from the get_dates method.
class User(Model):
def get_dates(self):
return []
If you have some attributes that you want to always convert to another data-type,
you may add the attribute to the __casts__ property of your model.
Otherwise, you will have to define a mutator for each of the attributes, which can be time consuming.
Here is an example of using the __casts__ property:
__casts__ = {
'is_admin': 'bool'
}
Now the is_admin attribute will always be cast to a boolean when you access it,
even if the underlying value is stored in the database as an integer.
Other supported cast types are: int, float, str, bool, dict, list.
The dict cast is particularly useful for working with columns that are stored as serialized JSON.
For example, if your database has a TEXT type field that contains serialized JSON,
adding the dict cast to that attribute will automatically deserialize the attribute
to a dictionary when you access it on your model:
__casts__ = {
'options': 'dict'
}
Now, when you utilize the model:
user = User.find(1)
# options is a dict
options = user.options
# options is automatically serialized back to JSON
user.options = {'foo': 'bar'}
Orator models fire several events, allowing you to hook into various points in
the model’s lifecycle using the following methods:
creating, created, updating, updated, saving, saved, deleting, deleted,
restoring, restored.
Whenever a new item is saved for the first time, the creating and created events will fire.
If an item is not new and the save method is called, the updating / updated events will fire.
In both cases, the saving / saved events will fire.
If False is returned from the creating, updating, saving, or deleting events,
the action will be cancelled:
User.creating(lambda user: user.is_valid())
To consolidate the handling of model events, you can register a model observer.
An observer class can have methods that correspond to the various model events.
For example, creating, updating, saving methods can be on an observer,
in addition to any other model event name.
So, for example, a model observer might look like this:
class UserObserver(object):
def saving(self, user):
# ...
def saved(self, user):
# ...
You can register an observer instance using the observe method:
User.observe(UserObserver())
When building JSON APIs, you may often need to convert your models and relationships to dictionaries or JSON.
So, Orator includes methods for doing so. To convert a model and its loaded relationship to a dictionary,
you may use the serialize method:
user = User.with_('roles').first()
return user.serialize()
Note that entire collections of models can also be converted to dictionaries:
return User.all().serialize()
To convert a model to JSON, you can use the to_json method:
return User.find(1).to_json()
Sometimes you may wish to limit the attributes that are included in you model’s dictionary or JSON form,
such as passwords. To do so, add a __hidden__ property definition to you model:
class User(model):
__hidden__ = ['password']
Alternatively, you may use the __visible__ property to define a whitelist:
__visible__ = ['first_name', 'last_name']
Occasionally, you may need to add dictionary attributes that do not have a corresponding column in your database.
To do so, simply define an accessor for the value:
class User(Model):
@accessor
def is_admin(self):
return self.get_raw_attribute('admin') == 'yes'
Once you have created the accessor, just add the value to the __appends__ property on the model:
class User(Model):
__append__ = ['is_admin']
@accessor
def is_admin(self):
return self.get_raw_attribute('admin') == 'yes'
Once the attribute has been added to the __appends__ list, it will be included in both the model’s dictionary and JSON forms.
Attributes in the __appends__ list respect the __visible__ and __hidden__ configuration on the model.