Build Your App on OpenShift Using Flask, SQLAlchemy, and PostgreSQL 9.2

Deploy Flask Python Apps on OpenShift

Let me start this blog by confessing that I am a Java guy who first learned Python three years back but haven't used it much in my day to day work. So, after three long years, I have decided to brush up on my Python skills by developing a simple web application. By simple I don't mean "Hello World" application but an application which does some work like storing data to a database. After spending some time googling "best web framework in Python," I zeroed in on Flask. Flask is a microframework for Python based on Werkzeug and Jinja 2. It is a very easy to learn framework and is based on convention over configuration, which means that many things are preconfigured with sensible defaults.

In this blog, we will write a todo application using Flask , Flask-SQLAlchemy , PostgreSQL , and Twitter Bootstrap. The purpose of this blog is not just to help developers write Python Flask web applications but to also help them deploy their application in the Cloud. In this blog, we will deploy our Flask todo application on OpenShift. OpenShift provides scalable hosting for Python web applications. Another purpose of this blog is to help newbie OpenShift Python application developers who want to get started with Python development on OpenShift. Finally, this blog will also show how to connect a Postgresql database from a Python application. By the end of this blog you will be able to see how using a Platform as a Service can quickly get you going with Python and PostgreSQL and we will have a todo application running on OpenShift as shown below.

Todo App Running on OpenShift

Prerequisite

Before we can start building the application, we'll have to do few setup tasks :

  1. Basic Python knowledge is required.

  2. Sign up for an OpenShift Account. It is completely free and instant . Red Hat gives every user three free Gears on which to run your applications. At the time of this writing, the combined resources allocated for each user is 1.5 GB of memory and 3 GB of disk space.

  3. Install the rhc client tool on your machine. The rhc is a ruby gem so you need to have ruby 1.8.7 or above on your machine. To install rhc, just typesudo gem install rhc. If you already have one, make sure it is the latest one. To update your rhc, execute the command shown below.sudo gem update rhc. For additional assistance setting up the rhc command-line tool, see the following page: https://openshift.redhat.com/community/developers/rhc-client-tools-install.

  4. Setup your OpenShift account using rhc setup command. This command will help you create a namespace and upload your ssh keys to the OpenShift server.

Source code of the application that we will be developing in this blog is on github https://github.com/shekhargulati/todo-flask-openshift-quickstart

Step 1 : Create an OpenShift Python Application

We will start by creating an OpenShift Python 2.7 application. OpenShift also supports Python 2.6 and Python 3.3, but for this blog we will be sticking with Python 2.7. To learn more about the Python 3.3 cartridge, please refer to this blog.

The OpenShift Python 2.7 cartridge by default uses mod_wsgi Apache HTTP Server module that provides a WSGI compliant interface for hosting Python based web applications under Apache.To create a Python 2.7 application named todo, type the command shown below.

$ rhc app create todo python-2.7 postgresql-9.2

The command shown above will create an application container for us, called a gear, and setup all of the required SELinux policies and cgroup configuration. Next, it will install all the required software on your gear. It will also install PotsgreSQL 9.2 on your application gear and will create a database with the same name as the application name. OpenShift will also setup a private git repository with some template code, and then clone the repository to your local system. Finally, OpenShift will propagate the DNS to the outside world.

You can view the application details using the command shown below.

$ rhc show-app --app todo
 
todo @ http://todo-xxxxx.rhcloud.com/ (uuid: 522425cd500446b3ec000294)
-------------------------------------------------------------------------------
  Domain:  xxxxx
  Created: 11:14 AM
  Gears:   1 (defaults to small)
  Git URL: ssh://522425cd500446b3ec000294@todo-xxxxx.rhcloud.com/~/git/todo.git/
  SSH:     522425cd500446b3ec000294@todo-xxxx.rhcloud.com
 
  python-2.7 (Python 2.7)
  -----------------------
    Gears: Located with postgresql-9.2
 
  postgresql-9.2 (PostgreSQL Database 9.2)
  ----------------------------------------
    Gears:          Located with python-2.7
    Connection URL: postgresql://$OPENSHIFT_POSTGRESQL_DB_HOST:$OPENSHIFT_POSTGRESQL_DB_PORT
    Database Name:  todo
    Password:       AXtK_CELQXJK
    Username:       adminiid3lsl

Step 2 : Look at Default Template Application

The default structure of the template application created by OpenShift is shown below.

todo
    wsgi/                    Externally exposed wsgi code goes here
    wsgi/static/          Public static content gets served here
    libs/                      Additional libraries
    data/                    For not-externally exposed wsgi code
    setup.py               Standard setup.py, specify deps here
       app.py.disabled     This file may be used instead of Apache mod_wsgi to run your python web application in a different framework
    .openshift/            Location for OpenShift specific files
        action_hooks/      Various scripts to hook into application lifecycle
        markers/          Marker files for hot deployment , debugging etc

All the application code will be placed in the wsgi folder and application dependencies will be added to setup.py.

Step 3 : Adding Flask and Flask-SQLAlchemy Dependencies

OpenShift uses Setuptools which is a collection of enhancements to the Python distutils , that allow developers to more easily build and distribute Python packages, especially ones that have dependencies on other packages. We will add Flask and Flask-SQLAlchemy dependencies to setup.py as shown below.

from setuptools import setup
 
setup(name='TodoApp',
      version='1.0',
      description='Todo Application',
      author='Shekhar Gulati',
      author_email='',
      url='http://www.python.org/sigs/distutils-sig/',
     install_requires=['Flask==0.7.2', 'MarkupSafe' , 'Flask-SQLAlchemy==0.16'],
     )

The key attribute in the code shown above is install_requires=['Flask==0.7.2', 'MarkupSafe' , 'Flask-SQLAlchemy==0.16']. The install_requires attribute is used to specify a list of strings that represent python modules that your app needs. If you need other modules that are not listed you can just add new elements to setup.py. The reason we pegged to a certain version is 1) this prevents the build from checking versions with every git push and 2) it also prevents a build from putting in a version that breaks our code without our knowledge.

Step 4 : Make Flask Say Hello

We will start developing our todo application by creating a new file called todoapp.py in wsgi folders. On windows you can just create a new file named todoapp.py, by right clicking in explorer and saying new text file, then change .txt extension with .py extension.

$ cd wsgi
$ touch todoapp.py

Open your favorite editor and add following lines to it.

from flask import Flask
 
app = Flask(__name__)
 
@app.route('/')
@app.route('/hello')
def index():
    return "Hello from OpenShift"
 
if __name__ == '__main__':
    app.run()

The code shown above does following :

  1. Import the Flask class from the flask module and then create an instance of Flask class. This instance will be our WSGI application.
  2. Next we define a route which tells Flask that on root('/') and home('/home') url, it should invoke index() function. The index() function just simply returns "Hello from OpenShift" string which will be rendered by the browser.
  3. Finally, if the name of the application module is equal to "_ _main_ _" then run method is invoked to run the server.

The last change needed to make this "Hello World" application work on OpenShift is to update a file named application which OpenShift created under wsgi folder. Change the content of the file with the one shown below.

#!/usr/bin/python
import os
 
virtenv = os.environ['OPENSHIFT_PYTHON_DIR'] + '/virtenv/'
os.environ['PYTHON_EGG_CACHE'] = os.path.join(virtenv, 'lib/python2.7/site-packages')
virtualenv = os.path.join(virtenv, 'bin/activate_this.py')
try:
    execfile(virtualenv, dict(__file__=virtualenv))
except IOError:
    pass
 
from todoapp import app as application

The application file is required by OpenShift and it basically calls the todoapp file that we created earlier.

After all the code changes are done, add the code to the git repository, commit it, and push it to OpenShift gear.

$ git add .
$ git commit -am "hello world from flask"
$ git push

The application will be accessible at http://todo-{domain-name}.rhcloud.com. Replace {domain-name} with your domain name.

Step 5 : Defining your Model

In this blog, we are using Flask-SQLAlchemy which is a Flask extension that adds SQLAlchemy support to our todoapp application. SQLAlchemy is the Python SQL toolkit and Object Relational Mapper that gives application developers the full power and flexibility of SQL.

Open the todoapp.py and add Todo model class to it as shown below.

from datetime import datetime
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
 
app = Flask(__name__)
 
app.config.from_pyfile('todoapp.cfg')
db = SQLAlchemy(app)
 
class Todo(db.Model):
    __tablename__ = 'todos'
    id = db.Column('todo_id', db.Integer, primary_key=True)
    title = db.Column(db.String(60))
    text = db.Column(db.String)
    done = db.Column(db.Boolean)
    pub_date = db.Column(db.DateTime)
 
    def __init__(self, title, text):
        self.title = title
        self.text = text
        self.done = False
        self.pub_date = datetime.utcnow()
 
@app.route('/')
@app.route('/hello')
def index():
    return "Hello from OpenShift"
 
if __name__ == '__main__':
    app.run()

In the code shown above we made the following additions.

  1. First we imported SQLAlchemy class from flask_sqlalchemy module. This is required to work with Flask-SQLAlchemy.
  2. Then we created an instance of SQLAlchemy class by passing it application object. The application object was loaded with database configuration which we specified in todoapp.cfg file. We will be creating todoapp.cfg later in this post.
  3. Next we defined our Todo model by extending db.Model class and declaring all the Todo model attributes.

Next, create a new file called todoapp.cfg in wsgi folder. It will house all our application configuration. On windows you can just create a new file named todoapp.cfg, by right clicking in explorer and saying new text file, then change .txt extension with .cfg extension.

$ cd wsgi
$ touch todoapp.cfg

Add following lines to todoapp.cfg

import os
SQLALCHEMY_DATABASE_URI = os.environ['OPENSHIFT_POSTGRESQL_DB_URL']
SQLALCHEMY_ECHO = False
SECRET_KEY = 'secret key'
DEBUG = True

Once you go into production, you will probably want to turn off DEBUG until you run into problems. This will help with performance since you won't be writing as much to files.

Update the application file under wsgi folder so that it creates the database. Add the two lines at the end of file.

from todoapp import *
db.create_all()

Step 6 : Persisting Todo Items to PostgreSQL

Next we will add a route "/new" which will render a form when a user makes a get request to http://todo-{domain-name}.rhcloud.com/new. And when user submit the form using POST method it will write the todo item to database.

Add following lines to todoapp.py.

from flask import Flask, request, flash, url_for, redirect, render_template, abort
 
@app.route('/new', methods=['GET', 'POST'])
def new():
    if request.method == 'POST':
            todo = Todo(request.form['title'], request.form['text'])
            db.session.add(todo)
            db.session.commit()
            return redirect(url_for('index'))
    return render_template('new.html')

Flask uses Jinja2 as its templating language. For those of you new to the flask framework, templates basically facilitate the seperation of presentation and processing of your data. Templates are web pages that have mostly static elements and integrate programming logic which produces dynamic content. In the code snippet shown above we are rendering a template called 'new.html'. Create a new folder called templates under wsgi folder and create a new.html in it and add the following lines.

$ cd wsgi
$ mkdir templates
$ cd template
$ touch new.html

Next copy the content shown below in new.html.

{% extends "layout.html" %}
{% block body %}
  <form action="" method=post class="form-horizontal">
    <h2>Create New Todo</h2>
    <div class="control-group">
        <div class="controls">
          <input type="text" id="title" name="title" class="input-xlarge"
            placeholder="Please give title to todo item" value="{{ request.form.title }}"
            required>
        </div>
    </div>
 
    <div class="control-group">
        <div class="controls">
          <textarea name="text" rows=10 class="input-xlarge" placeholder="describe the todo item" required>{{ request.form.text }}</textarea>
        </div>
    </div>
 
    <div class="control-group">
        <div class="controls">
          <button type="submit" class="btn btn-success">Create Todo</button>
          <a href="{{ url_for('index') }}">Back to list</a>
        </div>
    </div>
  </form>
{% endblock %}

The new.html template extends another template called layout.html. The layout.html is where we will define the layout of our web application. Create a new file called layout.html in the templates folder under wsgi and following content. Basically, template inheritance makes it possible to keep certain elements on each page like header, footer, etc.

<!doctype html>
<title>TodoApp -- Store your Todo items</title>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no">
<meta charset="utf-8">
<style>
body {
    padding-top: 60px;
    padding-bottom: 100px;
}
</style>
<link href="/static/bootstrap.css" rel="stylesheet">
<link href="/static/bootstrap-responsive.css" rel="stylesheet">
<script src="/static/jquery.js"></script>
<script src="/static/bootstrap.js"></script>
 
<div class="navbar navbar-inverse navbar-fixed-top">
        <div class="navbar-inner">
            <div class="container">
                <button type="button" class="btn btn-navbar" data-toggle="collapse"
                    data-target=".nav-collapse">
                    <span class="icon-bar"></span> <span class="icon-bar"></span> <span
                        class="icon-bar"></span>
                </button>
                <a class="brand" href="/">TodoApp</a>
                <div class="nav-collapse collapse">
                    <ul class="nav">
                        <li class="active"><a href="/">Home</a></li>
                        <li><a href="/new">New Todo</a></li>
                    </ul>
                </div>
                <!--/.nav-collapse -->
            </div>
        </div>
</div>
 
{%- for category, message in get_flashed_messages(with_categories=true) %}
  <p class="flask {{ category }}-flash">{{
    "Error: " if category == 'error' }}{{ message }}</p>
{%- endfor %}
<div id="main" class="container">
    {% block body %}{% endblock %}
    <hr>
    <footer id="footer">
                <p>Todo App built using Flask, SQLAlchemy, PostgreSQL , and Twitter Bootstrap</p>
                <p><a href="https://www.openshift.com/" target="_blank"><img alt="Powered by OpenShift" src="https://www.openshift.com/sites/default/files/images/powered-transparent-black.png"></a></p>     
    </footer>
</div>

Flask will look for templates in the templates folder. Templates have access to request, session, g objects, as well as the get_flashed_messages() function. We have used get_flashed_messages() method above. This method pulls all flashed messages from the session and returns them. As Flask configured Jinja2 templating engine for our application, HTML escaping is also enabled for the application. This makes sure the application is secure. You should head over to Jinja2 official documentation for more information.

The project uses Twitter bootstrap to beautify the application. Copy the files from https://github.com/shekhargulati/todo-flask-openshift-quickstart/tree/master/wsgi/static and put in static folder under wsgi. The static folder is for serving static files.

$ cd ../static
$ wget https://raw.github.com/shekhargulati/todo-flask-openshift-quickstart/master/wsgi/static/css/bootstrap-responsive.css
$ wget https://raw.github.com/shekhargulati/todo-flask-openshift-quickstart/master/wsgi/static/css/bootstrap.css
$ wget https://raw.github.com/shekhargulati/todo-flask-openshift-quickstart/master/wsgi/static/js/bootstrap.js
$ wget https://github.com/shekhargulati/todo-flask-openshift-quickstart/blob/master/wsgi/static/js/jquery.js

Commit the code and push it to OpenShift gear

$ git add .
$ git commit -am "added functionality to create a new todo"
$ git push

Now if you go to http://todo-{domain-name}.rhcloud.com/new you will see a form where you can create todo items as shown below.

Create New Todo

Next, create a new Todo item by entering some details and pressing "Create Todo" button. The application will first save the todo item and then redirect you to index "/" page. To view the persisted todo item we will log into our application gear and run psql PostgreSQL client.

$ rhc ssh --app todo
 
$ [todo-xxxx.rhcloud.com 5204d6c75973cac7a00001ef]\> psql
psql (9.2.4)
Type "help" for help.
 
todo1=# \dt
           List of relations
 Schema | Name  | Type  |    Owner     
--------+-------+-------+--------------
 public | todos | table | adminwrqfzbx
(1 row)
 
todo1=# 

To view the created todo item we will run select query as shown below.

todo1=# select * from todos;
 todo_id |         title         |           text           | done |          pub_date          
---------+-----------------------+--------------------------+------+----------------------------
       1 | Learn Flask framework | Read Flask Documentation | f    | 2013-08-10 02:51:43.007073
(1 row)
 
todo1=# 

Step 7 : View all Todo Items on Index Page

The next feature that we are going to implement is to show all the todo items on the index page. So, if a user goes to http://tood-{domain-name}.rhcloud.com/ then he/she will see all the todo items.

Update the index() function in todoapp.py with the following lines:

@app.route('/')
def index():
    return render_template('index.html',
        todos=Todo.query.order_by(Todo.pub_date.desc()).all()
    )

Create a new file called index.html in the templates directory and add the following content:

{% extends "layout.html" %}
{% block body %}
<div id="main" class="container">
    <h2>All Items</h2>
    <table class="table table-hover">
      <tr>
        <th>#
        <th>Title
        <th>Date
        <th>Text
      {%- for todo in todos %}
      <tr class={{ "success" if todo.done }}>
        <td><a href="/todos/{{ todo.id }}">{{ todo.id }}</a>
        <td style={{ "text-decoration:line-through" if todo.done }}>{{ todo.title }}
        <td>{{ todo.pub_date.strftime('%Y-%m-%d %H:%M') }}
        <td>{{ todo.text }}</td>
      {%- endfor %}
    </table>
    <p>
      <a href="{{ url_for('new') }}" class="btn btn-large btn-primary">New Todo</a>
 
 
</div>
{% endblock %}

Commit the changes and push to OpenShift gear.

$ git add .
$ git commit -am "added index()"
$ git push

Now if you go to http://todo-{domain-name}.rhcloud.com , you will see as shown below. Please replace {domain-name} with your own domain name.

View all todos

Step 8 : View and Update a Todo Item

The next functionality that we are going to implement is viewing and updating a specific todo item. When a user goes to http://todo-{domain-name}.rhcloud.com/todos/1 then he/she should see a form filled with details of todo item with id 1. A user can change the values and submit the form again. This will update the values of the todo item. A user can update the todo item to mark todo as done. Add a new function to todoapp.py as shown below.

@app.route('/todos/<int:todo_id>', methods = ['GET' , 'POST'])
def show_or_update(todo_id):
    todo_item = Todo.query.get(todo_id)
    if request.method == 'GET':
        return render_template('view.html',todo=todo_item)
    todo_item.title = request.form['title']
    todo_item.text  = request.form['text']
    todo_item.done  = ('done.%d' % todo_id) in request.form
    db.session.commit()
    return redirect(url_for('index'))

The view.html template is shown below. Create a new file with name view.html in templates directory and place the content shown below in it.

{% extends "layout.html" %}
{% block body %}
  <form action="" method=post class="form-horizontal">
    <h2>Create New Todo</h2>
    <div class="control-group">
        <div class="controls">
          <input type="text" id="title" name="title" class="input-xlarge"
            placeholder="Please give title to todo item" value="{{ todo.title }}"
            required>
        </div>
    </div>
 
    <div class="control-group">
        <div class="controls">
          <textarea name="text" rows=10 class="input-xlarge" placeholder="describle the todo item" required>{{ todo.text }}</textarea>
        </div>
    </div>
 
    <div class="control-group">
        <div class="controls">
          <input type=checkbox name=done.{{ todo.id }}{{ " checked" if todo.done }}>
        </div>
    </div>
 
 
    <div class="control-group">
        <div class="controls">
          <button type="submit" class="btn btn-success">Update Todo</button>
          <a href="{{ url_for('index') }}">Back to list</a>
        </div>
    </div>
  </form>
{% endblock %}

Commit the code and push it to OpenShift gear.

$ git add .
$ git commit -am "added view or update functionality"
$ git push

Now if you go to http://todo-{domain-name}.rhcloud.com/todos/1 then you will see a todo item as shown below.

View a Todo Item

You can mark the checkbox and press "Update todo" and you will see the todo marked done on the index page.

Todo Marked Done

Conclusion

In this blog we covered how developers can build web applications in Python using Flask framework and PostgreSQL database and deploy it to OpenShift. If you are looking to host your Python application then give OpenShift a try.

What's Next?

I must say that I really enjoy this concise tutorial. Everything are clear. But I think if you added how to work both in local and in cloud (Openshift), this tutorial will perfect, meaning that by using this sole tutorial, a new comer in Openshift can easily wander his/her way to this awesome platform.

I have already done that using this tutorial : https://www.openshift.com/blogs/how-to-install-and-configure-a-python-flask-dev-environment-deploy-to-openshift (which I think its only missing the "pip install psycopg2" as it didn't use postgresql).

Thanks Shekhar for your prompt answer in Github! Encourage me a lot!

Thank you for this brilliant tutorial. I have managed to do everything, but I am getting an error that will not accept:

from todoapp import app as application

in my application file.

What are some general reasons behind this?

Hi, I'd like to use MYSQL instead of PostgreSQL.

To do that, I changed SQLALCHEMY_DATABASE_URI = os.environ['OPENSHIFT_POSTGRESQL_DB_URL'] to SQLALCHEMY_DATABASE_URI = os.environ['OPENSHIFT_MYSQL_DB_URL'] in "todoapp.cfg", but It didn't work.

So, how could I configure SQLALCHEMY_DATABASE_URI to use mysql ?

Thanks