A model is the single, definitive source of information about your data. It contains the essential all the informations and behaviors of the data you’re storing. Practically speaking, a model maps a database table and define what should happen to the data it contains.
So, how an Emmett model looks like? Thinking of a post inside a blog, an example model would be like this:
from emmett.orm import Field, Model
class Post(Model):
author = Field()
title = Field()
body = Field.text()
validation = {
"title": {'presence': True},
"body": {'presence': True}
}
As you can see, we defined three fields for our model, two of type string (is the default type in Emmett) and one of type text, which will map the corresponding columns in the table, and added some validation rules for them, so that we avoid to store posts missing titles or bodies.
As you will see in the next paragraphs, Emmett models have some reserved attributes, like validation
which define some options for the fields inside your models. All the options listed in the next sections are available also as parameters of the Field
class, and you can choose how to organize your code depending on your needs.
In order to use the model just defined in your application you must register it using the define_models()
method of the Database
class of Emmett, as we seen in the first example:
from emmett.orm import Database
db = Database(app)
db.define_models(Post)
This will create a Table
object on your Database
instance accessible both with model name and table name:
db.Post
db.posts
Note:
AccessingModel
refers to the model itself, whiledb.Model
refers to the table instance you created with your model. While these two classes shares the fields of your models, so accessingModel.fieldname
anddb.Model.fieldname
ordb.tablename.fieldname
will produce the same result, they have different properties and methods, and you should remember this difference.
Under default behavior, Emmett will create the table using the name of the class and making it plural, so that the class Post
will create the table posts, Comment
will create table comments and so on.
If you want to customize the name of the table, you can use the tablename
attribute inside your model:
class Post(Model):
tablename = "myposts"
just ensure the name is valid for the DBMS you're using.
Warning:
Emmett doesn't have a real pluralization system to evaluate names, so in case the name you've chosen for your model doesn't have a regular plural in english, you should write down the correct plural with thetablename
attribute. Just as an example, a model namedMouse
will be translated in the horrible"mouses"
tablename, so you should assign:
tablename = "mice"
Field
objects define your entity's properties, and will map the appropriate columns inside your tables, so in general you would write the name of the property and its type:
started = Field.datetime()
Available type methods for Field definition are:
Field type | mapped to python object |
---|---|
string | str |
text | str |
blob | str |
bool | bool |
int | int |
float | float |
decimal(precision,scale) | decimal.Decimal |
date | datetime.date |
time | datetime.time |
datetime | datetime.datetime |
password | str |
upload | str |
int_list | List[str] |
string_list | List[int] |
json | Union[Dict,List] |
jsonb | Union[Dict,List] |
geography(type,srid,dimension) | str |
geometry(type,srid,dimension) | str |
If you don't specify a type for the Field
class, and create an instance directly, it will be set as string as default value.
Using the right field type ensure the right columns types inside your tables, and allows you to benefit from the default validation implemented by Emmett.
Note: some fields' types are engine specific, for instance
jsonb
field is valid only with PostgreSQL engine, andgeometry
andgeography
fields require spatial APIs.
To implement a validation mechanism for your fields, you can use the validation
parameter of the Field
class, or the mapping dict
with the name of the fields at the validation
attribute inside your Model. Both method will produce the same result, just pick the one you prefer:
title = Field(validation={'presence': True})
body = Field.text(validation={'presence': True})
validation = {
'title': {'presence': True},
'body': {'presence': True}
}
The validation rules you define will be used to validate the forms created from the models on the user input and inserts.
While you can find the complete list of available validators in the appropriate chapter of the documentation, here we list the default validation implemented by Emmett on fields:
Field type | default validation | allow blank value |
---|---|---|
string | {'len': {'lt': 255}} |
yes |
text | {'len': {'lt': 65536}} |
yes |
bool | {'in': (False, True)} |
no |
int | {'is': 'int'} |
no |
float | {'is': 'float'} |
no |
decimal | {'is': 'decimal'} |
no |
date | {'is': 'date'} |
no |
time | {'is': 'time'} |
no |
datetime | {'is': 'datetime'} |
no |
int_list | {'is': 'list:int'} |
no |
json | {'is': 'json'} |
no |
password | {'len': {'gte': 6}, 'crypt': True}} |
no |
Tip:
When you want to allow your fields been empty, you can use the allow validator:
{'allow': 'blank'}
or{'allow': 'empty'}
Sometimes you may want to disable the default validation implemented by Emmett. Depending on your needs, you have two different ways.
When you need to disable the default validation on a single Field
, you can use the auto_validation
parameter:
a = Field(auto_validation=False)
Otherwise, if you want to disable the default validation on every field of your Model
, the auto_validation
attribute is handy:
class MyModel(Model):
auto_validation = False
Emmett models have a default_values
attribute that helps you to set the default value for the field on record insertions:
default_values = {
'started': lambda: request.now
}
Which is the same of the default
parameter of Field
class:
started = Field.datetime(default=lambda: request.now)
The values defined in this way will be used on the insertion of new records in the database if no other value is given during the operation.
As for the default_values
attribute we've seen before, update_values
helps you to set the default value for the field on record updates:
update_values = {
'started': lambda: request.now
}
Or you can use the update
parameter of Field
class:
started = Field.datetime(update=lambda: request.now)
The values defined in this way will be used on the update of existing records in the database if no other value is given during the operation.
Changed in version 1.0
The fields_rw
attribute of Model
class helps you defining the access rules to the fields. It might be useful, for example, to hide some attributes to users when you create forms:
fields_rw = {
'started': False,
'open': (True, False)
}
Any item of the dictionary can be a tuple
, where the first value defines if the field should be readable by the user and the second value defines if the field should be writable, or bool
that will set both values to the one given. By default, all fields are defined with rw at True
.
You may prefer to explicit passing read-writes values to the fields, using rw
parameter:
started = Field.datetime(rw=False)
New in version 0.7
Emmett provides an indexes
attribute on models which helps you define indexes on your tables:
indexes = {
'field1': True,
('field1', 'field2'): True,
'custom_index_name': {'fields': ['fields3', 'fields4']}
}
Note: indexes are available only when using Database with migrations enabled.
As you can see, Emmett supports different formats for indexes, since we defined:
Practically speaking, the rules Emmett apply on the indexes definition are:
bool
, the key must be a field of your model or a tuple of fields of your modeldict
, the key will be the name of the indexNote: every index defined in Emmett models will have its name starting with
modelname_widx__name
.
When using the dict
notation, you can also specify the unique
option as a boolean, which is False
on default behavior.
Emmett supports some advanced options on defining indexes, see the advanced chapter of the documentation for further informations.
Sometimes you need to give a better representation for the value of your entity, for example rendering dates or shows only a portion of a text field. In these cases, the repr_values
attribute of your models will help:
repr_values = {
'started': lambda row, value: prettydate(value)
}
Once defined this, you can render the value using:
MyModel.started.represent(record, record.started)
And if you may prefer to explicit passing representation rules to the single fields instead of writing down in the model, you can use the representation
parameter:
started = Field.datetime(representation=lambda row, value: prettydate(value))
The Model
attributes listed in this section are intended to be used for forms generation.
Labels are useful to produce good titles for your fields in forms:
form_labels = {
'started': T("Opening date:")
}
Labels will decorate the input fields in your forms. In this example we used the Emmett translator object to automatically translate the string in the correct language.
You can also use the label
parameter of Field
class:
started = Field.datetime(label=T("Opening date:"))
As for the labels, form_info
attribute is useful to produce hints or helping blocks for your fields in forms:
form_info = {
'started': T("Insert the desired opening date for your event in YYYY-MM-DD format.")
}
You can also use the info
parameter of Field
class:
started = Field.datetime(info=T("some description here"))
Widgets are used to produce the relevant input part in the form produced from your model. Every Field
object has a default widget depending on the type you defined, for example the datetime has an <input>
html tag of type text. When you need to customize the look of your input blocks in the form, you can use your own widgets and pass them to the model with the appropriate attribute:
form_widgets = {
'started': my_custom_widget
}
where my_custom_widget
usually look like this:
def my_custom_widget(field, value):
# some processing
return myhtmlinput
And you can also use the widget
parameter of Field
class:
started = Field.datetime(widget=my_custom_widget)
In case you want to use additional widgets in FormStyle
class, simply pass the relevant method in form_widgets
dictionary:
form_widgets = {
'field': FormStyle.widget_radio
}
Sometimes you need to access your model attributes when defining other features, but, until now, we couldn't access the class or the instance itself. To avoid this problem, you can use the setup
method of the model:
def setup(self):
# you can access the database, the table and its fields
db = self.db
table = self.table
field = self.table.fieldname
You can also define methods that will be available on the Model class itself. For instance, every Emmett model comes with some pre-defined methods, for example:
MyModel.form()
will create the form for the entity defined in your model.
Here is the list of all pre-defined methods in Emmett:
method | description |
---|---|
new | returns a new record instance with specified parameters (field=value) |
create | insert a new record with specified parameters (field=value) if validation succeed |
validate | validates the specified parameters (field=value) and returns a sdict of errors (that would be empty if the validation passed) |
form | returns a new form instance for the current model |
But how can you define additional methods?
Let's say, for example that you want a shortcut in your Notification
model to set all the records to be read for a specific user, without writing down the query manually every time:
class Notification(Model):
user = Field()
message = Field.text()
read = Field.bool()
@classmethod
def read_all(cls, user):
return cls.where(
lambda n: n.user == user
).update(read=True)
now you can easily set user's notification as read:
>>> Notification.read_all(my_user)
3
As you observed, you can just use the standard classmethod
decorator of the python language.
Note:
AccessingModel.method()
refers to the model itself, whiledb.Model.attribute
refers to the table instance you created with your model.