In Flask-Diamond, a Model is a way of reading and writing a database. If our application is a chess game, then we’re modeling chess objects in a database. If our application is a social network, then we’re modeling people 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 person who has a name and an age. Also, let’s model the fact that every person has two biological parents (here called a mother and a father). The following example demonstrates one way this model might be accomplished. [1]
from flask.ext.diamond.utils.mixins import CRUDMixin
from .. import db
class Person(db.Model, CRUDMixin):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255))
age = db.Column(db.Integer)
mother_id = db.Column(db.Integer, db.ForeignKey('person.id'))
mother = db.relationship('Person',
primaryjoin=('Person.mother_id == Person.id'),
remote_side="Person.id")
father_id = db.Column(db.Integer, db.ForeignKey('person.id'))
father = db.relationship('Person',
primaryjoin=('Person.father_id == Person.id'),
remote_side="Person.id")
def __str__(self):
return self.name
Now, we can model a Person and specify their name and age. Once we create Person objects for a mother and a father, we can model the parent-child relationships. The model above could be used in Python like so:
from models import Person
me = Person.create(name="Me", age=34)
mom = Person.create(name="Mom", age=65)
dad = Person.create(name="Dad", age=64)
me.mother = mom
me.father = dad
me.save()
Usually, data models are created for entities that are changing. A person’s age increases every year, so we might create a birthday method that increments a person’s age. A simplified version of our person model has been extended with the birthday method below:
class Person(db.Model, CRUDMixin):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255))
age = db.Column(db.Integer)
def birthday(self):
self.age += 1
self.save()
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 each Person lives in a House, which is modeled as:
class House(db.Model, CRUDMixin):
id = db.Column(db.Integer, primary_key=True)
address = db.Column(db.String(255))
class Person(db.Model, CRUDMixin):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64))
house_id = db.Column(db.Integer, db.ForeignKey("house.id"))
house = db.relationship('House',
backref=db.backref('persons', lazy='dynamic')
)
The following code example uses the classes above to create two people who live at one house.
our_house = House(address="1600 Pennsylvania Ave")
myself = Person("Me", house=our_house)
mom = Person("Mom", house=our_house)
print(myself.house)
Based on the Person class, a simple query that finds a person named “Me” looks like:
myself = models.Person.find(name="Me")
print(myself.name)
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 with Flask-Migrate to learn about altering a production database.
What good is a data model without any data to put in it? Data fixtures are a way of easily adding data to your database, which is helpful when you are frequently rebuilding your database with make db. Data fixtures can be placed into bin/manage.py within the populate_db() function. If you find yourself continually re-creating certain model objects in your database so you can test your application, then consider using populate_db() to automate the creation of these objects.
For example, in order for make db to automatically create a Person object based on the Person class above, construct populate_db() like this:
@manager.command
def populate_db():
"insert a default set of objects"
from models import Person
me = Person.create(name="Me", age=34)
mom = Person.create(name="Mom", age=65)
dad = Person.create(name="Dad", age=64)
me.mother = mom
me.father = dad
me.save()
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 as_hash(self):
pass
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 with Flask-Diamond. |