Facet: Database

In Flask-Diamond, a Model is a way of reading and writing a database. If our application is a model of the solar system, then we’re modeling planet objects in a database. A fundamental assumption of the Model-View-Controller architecture is that our application deals with objects, and our objects are modeled after the things our application deals with.

This document will demonstrate a model, then discuss some of the ways Flask-Diamond makes it easier to work with models.

A Basic Model

A model is actually written in Flask-Diamond using Python. Models are represented using SQLAlchemy, which is a very powerful Python library for working with databases. Since we are storing our models in a database, SQLAlchemy provides a strong foundation for getting the job done.

Let’s create a model of a planet with a name and mass. Let us also create a model of a satellite that orbits a planet. The following example demonstrates one way this model might be accomplished. [1]

from flask_diamond import db
from flask_diamond.mixins.crud import CRUDMixin

class Planet(db.Model, CRUDMixin):
    "A Planet is a celestial body"
    id = db.Column(db.Integer(), primary_key=True)
    name = db.Column(db.String(80), unique=True)
    mass = db.Column(db.Float())

Now, we can model a Planet and a Satellite. Notice that satellites specify a relationship to a planet. Let’s use our data model to create a few objects in our database.

from planets import models
earth = models.Planet.create(name="Earth", mass=100.0)
mars = models.Planet.create(name="Mars", mass=90.0)

Model Methods

Now let’s assume our planets can be bombarded by asteroids with a certain mass. We can extend our planet model to incorporate this feature

class Planet(db.Model, CRUDMixin):
    "A Planet is a celestial body"
    id = db.Column(db.Integer(), primary_key=True)
    name = db.Column(db.String(80), unique=True)
    mass = db.Column(db.Float())

    def bombard(self, mass=1.0):
        self.mass += mass
        self.save()

Now we can send an asteroid at a planet and our data model will add the mass of the asteroid to the planet:

from planets import models
earth = models.Planet.create(name="Earth", mass=100.0)
earth.bombard(mass=15)
print(earth.mass)

Only The Model Controls the Data

It is important to contain any data-altering methods within your Model, or else there may be confusion down the road. For example, if there is code within the Controller that directly alters the data Model, you should move that code from the Controller to a new Model method that achieves the same task. Finally, in the controller, invoke the Model method.

SQLAlchemy

SQLAlchemy and Flask-SQLAlchemy are used to provide an Object Relation Mapper (ORM), which reads/writes a database and makes the data easy to access using Python. By using an ORM such as SQLAlchemy, it is possible to avoid many pitfalls of directly using SQL, such as SQL injections or schema mismatches. One of the best resources to learn about writing models is the SQLAlchemy documentation itself, which is both excellent and extensive.

Model Relationships with SQLAlchemy

The SQLAlchemy Basic Relationships document provides an excellent overview of different relationship patterns, including:

To demonstrate a basic relationship, let’s say a planet can have a satellite orbiting it:

from flask_diamond import db
from flask_diamond.mixins.crud import CRUDMixin

class Planet(db.Model, CRUDMixin):
    "A Planet is a celestial body"
    id = db.Column(db.Integer(), primary_key=True)
    name = db.Column(db.String(80), unique=True)
    mass = db.Column(db.Float())

class Satellite(db.Model, CRUDMixin):
    "A Satellite orbits a Planet"
    id = db.Column(db.Integer(), primary_key=True)
    name = db.Column(db.String(80), unique=True)
    mass = db.Column(db.Float())
    planet = db.relationship('Planet', backref=db.backref('satellites', lazy='dynamic'))
    planet_id = db.Column(db.Integer(), db.ForeignKey("planet.id"))

The following code example uses the classes above to create a planet called Earth with a moon.

from planets import models
earth = models.Planet.create(name="Earth", mass=100.0)
moon = models.Satellite.create(name="Moon", mass=25.0, planet=earth)

Querying with SQLAlchemy

Based on the Planet class, a simple query that finds a planet named “Earth” looks like:

earth = models.Planet.find(name="Earth")
print(earth.mass)

However, the SQLAlchemy Query API is extremely powerful, and its documentation is the authoritative source.

When the Model Changes

There is a close correspondence between the Model and the database tables. If an attribute is added to a model, then we need a new column in our database to store the values for this attribute. If the model changes, the database must also change. There are two ways of updating your database:

  • the clean slate: delete the old database and creating a new one that reflects the latest changes to the model. This is accomplished with make db on the command line. It’s easy and quick.
  • schema migrations: analyze your updated model to determine what parts are different from your old database, and then add/remove those parts to a live database. This is tricky, but it is necessary for databases in production. Read more in Database Schema Migration.

As long as you are actively developing, it is recommended to use make db each time you update your model. However, when your application is live, you will need to read Database Schema Migration to learn about altering a production database.

Another model example

For the sake of illustration, the following is a recursive model that is able to link to itself, creating a friendship graph of individuals.

from flask_diamond import db
from flask_diamond.mixins.crud import CRUDMixin

class Individual(db.Model, CRUDMixin):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(255))

    friend_id = db.Column(db.Integer, db.ForeignKey('individual.id'))
    friend = db.relationship('Individual',
        primaryjoin=('Individual.friend_id == Individual.id'),
        remote_side="Individual.id")

    def set_friend(self, obj):
        self.friend = obj
        self.save()

    def __str__(self):
        return self.name

Further Reading

Footnotes

[1]Note the use of CRUDMixin, which provides us with a create() method. For more information about CRUDMixin, see CRUD: Create, Read, Update, Delete.

Table Of Contents

Topic Navigation

Version