Defining Models¶
In this section we explore resource model definition in more detail.
To define a resource model, first declare a class that inherits from Model and set the from_ attribute to an SQLAlchemy Table object:
from jsonapi.model import Model
from jsonapi.tests.db import users_t
UserModel(Model):
from_ = users_t
By default, the primary key column is mapped to the reserved id field.
Note
Mapped tables must have single column primary keys. Composite primary key columns are not supported.
No additional attributes are required.
By default, the type of the resource is generated automatically from the model class name. If you wish to
override it, you can set it using the type_ attribute:
UserModel(Model):
type_ = 'users'
from_ = users_t
The fields attribute can be used to define the set of attributes and relationships for the
resource. To define attributes that map directly to table columns of the same name, simply list their names as string
literals:
class UserModel(Model):
type_ = 'user'
from_ = users_t
fields = 'email', 'created_on'
The fields email and created_on will be created and mapped to the database columns: users_t.c.email and
users_t.c.created_on, respectively.
Custom Fields¶
The datatype of the field is determined automatically from the datatype of the database column it maps to. To
override datatype auto-detection, you can explicitly set the datatype using the Field
class:
from jsonapi.fields import Field
from jsonapi.datatypes import Date
class UserModel(Model):
from_ = users_t
fields = ('email', 'status', Field('created_on', field_type=Date))
The type of created_on field, which maps to a timestamp database column, is set to Date instead of
DateTime.
To define a field that maps to a number of columns (a column expression):
class UserModel(Model):
from_ = users_t, user_names_t
fields = 'email', Field('name', lambda c: c.first + ' ' + c.last)
The second argument takes a function that accepts an SQLAlchemy ImmutableColumnCollection representing the join
result of the mapped tables listed in the from_ attribute. The function must return a valid column expression
(including function calls):
Field('age', lambda c: func.extract('year', func.age(c.birthday)), Integer))
If you wish to map a field to a database column of a different name:
class UserModel(Model):
from_ = users_t
fields = 'status', Field('email_address', lambda c: c.email)
You can also pass an SQLAlchemy Column explicitly. This is useful when mapping to multiple tables that share the
same column names (see Multiple Tables):
class UserModel(Model):
from_ = users_t
Field('email-address', users_t.c.email)
Multiple Tables¶
A resource model can be mapped to more than one database table:
from jsonapi.tests.db import user_names_t
class UserModel(Model):
from_ = users_t, user_names_t
In the following example, we define three fields. One maps to users_t.c.email column, and two map to
user_names_t.c.first and user_names_t.c.last columns:
class UserModel(Model):
type_ = 'user'
from_ = users_t, user_names_t
fields = 'email', 'first', 'last'
If a left outer join or an explicit on clause is needed, a FromItem object can be
passed in place of the table object. For example:
from jsonapi import FromItem
UserModel(Model):
from_ = users_t, FromItem(user_names_t, left=True)
Relationships¶
The Relationship class can be used to define relationships between resource
models:
from jsonapi import ONE_TO_MANY, MANY_TO_ONE
from jsonapi.tests.db import articles_t
class UserModel(Model):
from_ = users_t, user_names_t
fields = ('email',
Derived('name', lambda c: c.first + ' ' + c.last)
Relationship('articles', 'ArticleModel',
ONE_TO_MANY, articles_t.c.author_id))
class ArticleModel(Model):
from_ = articles_t
fields = ('title', 'body', 'created_on',
Relationship('author', 'UserModel',
MANY_TO_ONE, articles_t.c.author_id))
In this example, a user can author multiple article s, and an article is authored by one user. This
relationship is represented by two Relationship objects, each defined in it’s
respective model.
The first argument to Relationship is the name of the field. The second
arguments is the target model class name. The third argument is cardinality of the relationship.
The fourth and possibly fifth arguments depend on the cardinality of the relationship.
Both arguments, if provided must be SQLAlchemy Column objects that are part of a foreign key and are not a
primary key of any model.
- For
ONE_TO_ONErelationships, no argument is required in most cases. The only exception is when the foreign key of the relationship lives in a standalone one-to-one join table.- For
ONE_TO_MANYandMANY_TO_ONErelationships, one argument is required.- For
MANY_TO_MANYrelationships, two arguments are required and the second must be the one referencing the primary key of the related resource model
Aggregate Fields¶
The Aggregate class can be used to define fields that map to aggregate functions:
class UserModel(Model):
from_ = users_t
fields = ('email', 'name', 'created_on',
Relationship('articles', 'ArticleModel',
ONE_TO_MANY, articles_t.c.author_id),
Aggregate('article_count', 'articles', sa.func.count))
Note
The second argument to Aggregate must match a name of a Relationship field defined in the same model, as
shown above.