07 - Models

Inside the logic directory _/, there's a file named models.py. This is where objects are defined and could be generally thought of as ZenTools' interface to the MySQL database. For each table in the database, we should create a corresponding class in the models file. Then, using the syncdb script, ZenTools will even create your database tables for you.

Class definition basics

Here's what a typical model looks like:

class Customer(Model):
    first_name = 'char(50)'
    last_name  = 'char(50)'
    age        = 'int(3)'
    email      = 'email'
    phone      = 'char(10)'
    fax        = 'char:opt'
    newsletter = 'bool'

In this example, we have a list of seven attributes which will directly correspond to the columns in the database. Setting a primary key column is not necessary because ZenTools will automatically create an auto-increment column named id. It is possible to create your own primary keys too, but 99% of the time, this is all you need.

Each attribute is assigned a string which will determine what type of column it is, as well as determining some validation settings. For example, our first_name attribute corresponds to an SQL column which will be defined as VARCHAR(50) NOT NULL. By default, attributes are required. You can make them optional by adding the opt keyword as seen in the fax attribute. In addition to providing a definition for the SQL column, this also allows ZenTools to handle validation. If a customer object is instantiated, and the age attribute is set to 1535, then the save() method will fail for this object because age must be an integer with a maximum length of three. Also notice the email attribute which is set to the column type email. This is the same as char except that the value assigned to this attribute must validate as an email address or the save() method will fail.

Column types

Here's a complete list of valid column types:

char

char: SQL "varchar" column. Can also be used with maximum length setting: char(50). Default is max 255.

text

text: SQL "text" column. Can also be used with maximum length setting: text(500).

int

int: SQL "integer" column. Can also be used with maximum length setting: int(5). Default length is 11.

bigint

int: SQL "bigint" column. Can also be used with maximum length setting: bigint(30). Default length is 20.

decimal

int: SQL "decimal" column. Can also be used with two maximum length settings: decimal(7,2).

bool

bool: SQL single integer column stored in db as 0 or 1 but handled by ZenTools as Boolean type.

email

email: SQL "varchar" column. Can also be used with maximum length setting: email(50). Default is max 255. This is the same as char with the exception that the value assigned must be in valid email address form in order to validate.

domain

domain: SQL "varchar" column. Can also be used with maximum length setting: domain(50). Default is max 255. This is the same as char with the exception that the value assigned must be in valid domain name form in order to validate.

date

date: SQL "date" column. Can also be used with future/past setting: date(future) or date(past). Date must be either a valid date formatted string (ex: '2008-08-14') or a ZenTools zdate object or Python date object. If future/past settings are used, then the date must also pass that test in order to validate.

auto

auto: Intelligently sets column type based on the attribute name. Currently used only for auto timestamp columns (see below).

Auto timestamp columns

Records can be very easily timestamped using the auto setting when combined with one of four specially named attributes:

  • created_on
  • updated_on
  • created_at
  • updated_at

Timestamp example

created_on = 'auto'

This column will automatically be set to date type and will store the date the database record was created. The updated_on is similar except that the date will be updated to reflect the date this record was last updated. The other two created_at and updated_at are identical to created_on and updated_on except that the column type is datetime and stores the date and time. All four of these will use the server's local time by default to determine the date and time. Optionally, you can supply the utc setting to record timestamps in utc rather than local time like this:

updated_at = 'auto(utc)'

The utc setting can be applied to any of the four auto timestamp columns.

Using options keywords with column types

There are a number of options which can be used with column types. The most commonly used is probably the opt keyword which makes the column optional or NULL in SQL terms. Here are the options allowed:

opt

opt: Sets the attribute as optional. The object will validate even if no value is assigned to this attribute.

unique

unique: Requires that the value for this attribute be unique (doesn't already exist for this column).

re

re: Used as re('pattern'), validates the attribute value against the supplied regular expression pattern.

valid

valid: Used as valid('function_name'), validates the attribute value using the supplied function which should return True or False.

Option keywords are separated by a colon. For example, username = 'char(20):unique'

Meta attributes

In addition to attributes which represent column names, there are also a few "meta attributes" which can be assigned:

_order

_order: Accepts a string to designate the column(s) to be used for the order of returned results. Ex: _order = 'last_name'. You can also append the SQL keyword desc if descending order is preferred, _order = 'last_name desc'.

_limit

_limit: Accepts an integer to designate the maximum number of results to be returned.

_table

_table: If set, the syncdb script will use this name for the SQL table which corresponds to this class. By default, the lowercase pluralized version of the class name will be used.

Syncing models to the database using syncdb

Once we've defined our models in models.py, it's a snap to create the database tables.

Using syncdb from the Terminal

cd /path/to/my-project
zentools/script/syncdb

This will create all the database tables using connection parameters found in _/config.py (which were probably set in the first place by the init script).

By default, the tables created will be named as lowercase pluralized forms of the class name. For example, the class Customer would result in a table named customers. And ZenTools is smart enough to handle most English pluralization including irregulars such as the class name Person which would result in a table named people. If you want to provide your own table name, you can. Just supply the _table meta attribute to the class like this:

_table = 'my_table_name'

It's important to note too that this script will also attempt to intelligently sync any changes to existing tables when used on an existing database. If the potential exists for lost data, the script will warn you and prompt for confirmation before continuing. During the initial development process (when data is probably disposable anyway), this makes it trivially easy to modify your models and database whenever you feel like it. Just make the changes in models.py and run the syncdb script again.

Custom methods

It's often very convenient to make "processed" information available as methods of our model. For example, using the model defined at the top of this page, we might like to make the customer's year of birth available. To do so, we could simply define the method adding to the class as follows:

class Customer(Model):
    first_name = 'char(50)'
    last_name  = 'char(50)'
    age        = 'int(3)'
    email      = 'email'
    phone      = 'char(10)'
    fax        = 'char:opt'
    newsletter = 'bool'

    def birthyear():
        this_year = zdate('today').year
        return int(this_year) - self.age

Defining these types of calculations as methods of the model allow us to keep our controllers nice and clean.

Using custom methods in HTML templates

In the example above, we created the model method birthyear(). This is available not only to the controller, but also from the HTML templates directly as an attribute of a customer object. For example, if we're populating the template from the controller with something like this:

def customer_info():
    html.content.format_with( customer = models.Customer.get(123) )

...then we can access the birthyear in the template like this:

<p>Year of Birth: (customer.birthyear)</p>

Next: 08 - Model Objects