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 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)
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)
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 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.
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)
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.
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:
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.
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
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. |