Back to articles list
- 11 minutes read

Flask Web-App Development. Part II: Application Development

How This Tutorial Is Organized

The tutorial is divided into four articles:

  1. Introduction – Dive into web development with Flask. This article explains the basics of web development with Flask
  2. Part I – Setting up a local development environment
  3. Part II – Application development (You are here)
  4. Part III – Deployment to Heroku

 

Step 1: Database Design

Instead of manually writing CREATE TABLE statements, you can use an online tool for database modeling to automatically write scripts to create your tables.

Log in to or sign up for Vertabelo.

Our model looks like the following:




Each TODO item will have a record in the todos table. Our todo table will have the required id, description, creation_date, etc. We want to link todos to categories. The way to do that is by adding a field to the todo that contains the id of the todo assigned to one of the existing categories (the category_id is a foreign key). We also intend for the task to have one of the defined priorities from the priority table. Each priority also has a value that will be used to sort todos.

Step 2: Create the Initial Database

SQLAlchemy indicates the source of an Engine as a URI in the form:

dialect+driver://username:password@host:port/database

For PostgreSQL it will look as follows:

postgresql://patrycja:mypassword@localhost/todoapp

For other database engines, refer to the documentation: Connection URI format

The connection URI passed toto app config in todoapp.py:

app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://patrycja:mypassword@localhost/todoapp'

Step 3: Prepare Flask-SQLAlchemy Models

Full tutorial

  1. Download the SQLAlchemy models generator into your project directory.

    git clone https://github.com/Vertabelo/vertabelo-flask.git
    
  2. From Vertabelo, download your model as an XML file.
  3. Generate SQLAlchemy models.

    ./vertabelo-flask/vertabelo_flask_sqlalchemy.py -i todoapp.xml -o models.py
    

You now have a new file called models.py with your Flask-SQLAlchemy classes. The models look as follows:

from flask.ext.sqlalchemy import SQLAlchemy

app = Flask(__name__)
db = SQLAlchemy(app)



class Todo (db.Model):
    __tablename__ = "todo"
    id = db.Column('id', db.Integer, primary_key=True)
    category_id = db.Column('category_id', db.Integer, db.ForeignKey('category.id'))
    priority_id = db.Column('priority_id', db.Integer, db.ForeignKey('priority.id'))
    description = db.Column('description', db.Unicode)
    creation_date = db.Column('creation_date', db.Date)
    is_done = db.Column('is_done', db.Boolean)

    priority = db.relationship('Priority', foreign_keys=priority_id)
    category = db.relationship('Category', foreign_keys=category_id)


class Priority (db.Model):
   __tablename__ = "priority"
    id = db.Column('id', db.Integer, primary_key=True)
    name = db.Column('name', db.Unicode)
    value = db.Column('value', db.Integer)


class Category (db.Model):
    __tablename__ = "category"
    id = db.Column('id', db.Integer, primary_key=True)
    name = db.Column('name', db.Unicode)

I intend to have the creation_date and is_done set to the default values when inserting a new TODO into the table.

You can do this in two ways:

In the first approach you have to add to appropriate columns in the Todo model definition’s default keyword.

Add to column creation_date default=datetime.utcnow (When an insert statement executes and no value is passed for creation_date, the datetime.utcnow() is executed).

Finally the line looks like this:

creation_date = db.Column('creation_date', db.Date, default=datetime.utcnow)

The same with is_done:

is_done = db.Column('is_done', db.Boolean, default=False)

The second approach assumes that we don’t touch the models and add a predefined __init__ function.

def __init__(self, category, priority, description):
    self.category = category
    self.priority = priority
    self.description = description
    self.creation_date = datetime.utcnow()
    self.is_done = False

In the generated file there is line that imports SQLAlchemy class from module ext.flask.sqlalchemy and a line that creates an instance of Flask application. Instead of app = Flask(__name__) add import from todoapp app. Line db = SQLAlchemy(app) creates a db object by passing it to the application. The db object is instantiated from class SQLAlchemy. It represents the database and provides access to all the functionality of Flask-SQLAlchemy.

As you can see the data that we store in database are represented by a collection of classes that are referred to as the database models. The SQLAlchemy ORM layer does the translations required to map the objects accordingly: todo, category, priority are converted into rows in the proper database table.

In order to create the initial database, run an interactive Python shell. Try ipython (To install type: pip install ipython) instead of custom Python shell. It’s much more friendly.

  • Import objects from your app.

    from todoapp import app
    from models import db
    

    This brings our database into memory.

  • Create the database and tables.

    db.create_all()

Now we can add some example values. Let’s add some categories to our todoapp. (Note that ‘u’ before the category name indicates unicode type. It’s not necessary to add it. If you don’t do it, you’ll receive a warning).

Inserting data can be performed in three steps.

  1. Create a Python object
  2. Add it to the session
  3. Commit the session

Let’s add some categories to our todoapp.

from models import Category
work = Category(name=u'work')
home = Category(name=u’home’)

db.session.add(work)
db.session.add(home)
db.session.commit()

Any changes performed on a database are done in the context of a session. Mulitiple changes can be grouped in a session via db.session.add(object). To register all the changes in a database issue a statement db.session.commit(). The session here is the Flask-SQLAlchemy session, a beefed up version of a database transaction. Sessions guarantee that the database will never be left in an inconsistent state.

Now you can check if it indeed exists in the database. Run your psql command and select categories or check in pgadmin.

select * from category;

 id | name 
----+------
  1 | home
  2 | work
(2 rows)

Our todo list is prioritized, that’s why we need to populate the priority table.

from models import Priority

high = Priority(name=u’high’, value=3)
medium = Priority(name=u’medium’, value=2)
low = Priority(name=u’low’, value=1)

db.session.add(high)
db.session.add(medium)
db.session.add(low)
db.session.commit()

Step 4: Define View Functions

For now we have our model. What we need to do is define view functions.

A view function is simply a Python function that takes a http request and returns a http response. This response can be the HTML page or redirect.

I’ll start by showing a simple view that lists all of our TODO items sorted in descending order by a priority value and all categories.

from flask import render_template, request
from models import Category, Todo, Priority, db

@app.route('/')
def list_all():
   return render_template(
       'list.html',
       categories=Category.query.all(),
       todos=Todo.query.join(Priority).order_by(Priority.value.desc())

Let’s step through this code one line at a time:

  • Additionaly add in views.py required imports.
  • Add route decorator defined by the application instance. It registers the decorated function as a URL. The return value of this function is called the response and is what the browser receives.

Issue with generating HTML Flask gives to Jinja2 template engine. The integration between Flask and Jinja2 is via the render_template function. All you have to do is provide the name of the template and the variables you want to pass to the template engine as keyword arguments (They are key/value pairs, where, as in the function above, ‘categories’ and ‘todos’ are placeholders written in the template to which the current values are assigned. These current values are accurate values retrieved from the database).

If you want to see the corresponding template go to next step.

Each view will be responsible for a particular action, so there will be a different view for creating a new task, updating, or marking that the task is done.

I’ll explain the views responsible for creating and updating a task. The rest of the views are similar.

CREATE TODO View

This view function needs to talk to database to get the new todo added and then generate a response to send back to the browser. In the route decorator specify the scope for HTTP methods to ‘POST’ - this means that the function responds to a POST request.

In simple words, if the the server received POST from the browser, the server will get the category and priority from the database with an id equivalent to the one selected in the form. We will insert a new todo in the same way that we inserted values in the Python shell. When the operation ends successfully, the server redirects us to the main page (This response does not include a page document; it just gives the browser a new URL from which to load a new page).

If something went wrong, the template for a new todo is rendered once again.

@app.route('/new-task', methods=['POST'])
def new():
   if request.method == 'POST':
       category = Category.query.filter_by(id=request.form['category']).first()
       priority = Priority.query.filter_by(id=request.form['priority']).first()
       todo = Todo(category, priority, request.form['description'])
       db.session.add(todo)
       db.session.commit()
       return redirect('/')
   else:
       return render_template(
           'new-task.html',
           page='new-task',
           categories=Category.query.all(),
           priorities=Priority.query.all()
       )

UPDATE TODO View

Updating a particular element looks similar to adding a new one. If the server received GET from the browser (the browser wants to retrieve data from the server) it renders the template for new-task and supplements it with retrieved data for a particular TODO. Then, if we submit other data, the server receives POST and takes data from forms and updates values in database.

@app.route('/', methods=['GET', 'POST'])
def update_todo(todo_id):
   todo = Todo.query.get(todo_id)
   if request.method == 'GET':
       return render_template(
           'new-task.html',
           todo=todo,
           categories=Category.query.all(),
           priorities=Priority.query.all()
       )
   else:
       category = Category.query.filter_by(id=request.form['category']).first()
       priority = Priority.query.filter_by(id=request.form['priority']).first()
       description = request.form['description']
       todo.category = category
       todo.priority = priority
       todo.description = description
       db.session.commit()
       return redirect('/')

Step 5: Templates

Templates enable you to keep the logic of the application separate from the layout of the web page.They are HTML files that contain the text of a response, with placeholder variables for the dynamic parts that will be known only in the context of a request. Purpose with rendering templates in Flask is given to Jinja2 template engine. For more information about Jinja2 refer to the documenation.

For now, for the purpose of this article, we should know these kinds of delimiters:

  • {{ variable }} for variables
  • {% control structures %} for control structures

Let’s see how templates work from one of our view functions. This is aimed to generate categories and todos that will be displayed at the main page.

@app.route('/')
def list_all():
   return render_template(
       'list.html',
       categories=Category.query.all(),
       todos=Todo.query.join(Priority).order_by(Priority.value.desc())

The corresponding template looks as presented below. To get all categories and TODOs we have to iterate in a for loop inside a block dedicated to control statements {% ... %} and for a particular category, display its name.

Category

{%- for category in categories %} {%- endfor %}
{{ category.name }}

The same with TODO table.

My TODOs

{%- for todo in todos %} {%- endfor %}
# Description Category Priority Creation date
{{ loop.index }} {{ todo.description }} {{ todo.category.name }} {{ todo.priority.name }} {{ todo.creation_date }}

Template Inheritance

Our TODO application needs to have a navigation bar at the top of the main page with links to the create_category page and the create_todo page as well as a footer. I want these components to be repeated on each page. In order to keep common elements on all pages, I’ll use template inheritance. With inheritance comes block control statements. In this structure you can define where the derived templates can insert their content. The layout.html takes care of general structure. The extends block establishes the inheritance between three templates (it is used to extend a template from another one). In this way, the template engine knows that it has to render the derived template and include the base one. Each block has unique name, so Jinja2 knows which blocks are matched.

Jinja2: Template inheritance in Flask todoapp

Step 6: Beautify Web-App With Twitter Bootstrap

Bootstrap is the most popular front-end framework for web development.

  1. You can add Bootstrap to your project by downloading a zip file from the project’s website
  2. Unzip the compressed file and move the files to the /static folder in the todoapp directory.

Layout.html

The template called layout.html is the base layout from for the rest of templates. I’ve organized layout.html in the way presented below. Accordingly, I’ve put menu, messages and a footer in different sections. To each section I’ve added the container-fluid class - it lets the page fill the whole screen.

For now our menu is a simple list. To build a nice navigation bar I’ll use the navbar element. More about Navbar

Menu


Messages
Footer

List.html

List.html takes responsibility for listing TODOs and categories. This template inherits from layout.html. Here I’ve added the Bootstrap grid system.

The grid system scales up to 12 columns by default, which can be altered for four devices - large desktops, desktops, tablets, and phones. Since each row has 12 units, I made two columns, one 3 unit, the second one 9 unit. The grid system requires that rows be placed within a .container or .container-fluid for proper allignment and padding.(Learn more about Bootstrap Grid system)

Basic Structure

To create the layout:

  • create a container
  • create a row
  • add the desired number of columns in a row.

The structure looks as follows:

Table with categories
Table with TODOs

new-task.html

A form to pick a category and a priority for todo is a simple select list with customized .selectpicker class.

  • Download Bootstrap-select
  • Unpack the compressed file and move the files to the /static folder in the project directory
  • Enable Bootstrap-Select via JavaScript in layout.html.

     	 $('.selectpicker').selectpicker();
    

« Previous Part Next Part »

go to top