Prototyping a simple flask app

January 1, 0001
flask python

Prototyping a simple flask app

The task at hand is very simple: build a small web app that would enable the client to upload her pictures and manage them in galleries in a straightforward way. As a bonus, we would like to connect a couple of external services / APIs such as flickr, maybe pocket or tumblr or facebook. The app should also be able to do some basic image processing, such as grayscaling (is it even a word?), resizing and renaming and maybe even be able to send email updates.

Although the aforementioned functionality can be accomplished with hundreds of CM systems out-of-the-box, we will make it using flask, from scratch. The flexibility and the minimal architecture will enable future development and switching architecture, should it be needed.

Let us draw a first project draft:

The start

Ok, let’s begin by creating a directory and a virtualenv.

mkdir cms
mkvirtualenv cms

Honestly I just type pip install flask and then I start thinking… flask-sqlalchemy… flask-bootstrap… flask-login is a must… flask-script… flask-wtforms…

At the end, let’s just type

pip freeze>requirements.txt

And the contents of my requirements.txt file is:

Click==7.0 dominate==2.3.4 Flask==1.0.2 Flask-Bootstrap==3.3.7.1 Flask-Login==0.4.1 Flask-Script==2.0.6 Flask-SQLAlchemy==2.3.2 Flask-WTF==0.14.2 itsdangerous==0.24 Jinja2==2.10 MarkupSafe==1.0 SQLAlchemy==1.2.12 visitor==0.1.3 Werkzeug==0.14.1 WTForms==2.2.1

Now we will decide on the app folder structure. I am a firm believer in the Miguel Grinberg way of doing things in flask, so our cms folder will contain the following:

Let’s start from the config.py file. I usually start with something like this:

import os
basedir = os.path.abspath(os.path.dirname(__file__))

class Config:    
    SECRET_KEY = os.environ.get('SECRET_KEY') or 'sdfdsf43tergfdgsfdgsdssgdg'
    SQLALCHEMY_COMMIT_ON_TEARDOWN = True
    UPLOAD_FOLDER = os.path.join(basedir, 'app\\static\\uploads')

class DevelopmentConfig(Config):
    DEBUG = False
    SQLALCHEMY_DATABASE_URI = os.environ.get('DEV_DATABASE_URL') or \
        'sqlite:///' + os.path.join(basedir, 'cms-dev.sqlite')

class TestingConfig(Config):
    TESTING = True
    SQLALCHEMY_DATABASE_URI = os.environ.get('TEST_DATABASE_URL') or \
        'sqlite:///' + os.path.join(basedir, 'data-test.sqlite')

config = {
    'development': DevelopmentConfig,
    'testing': TestingConfig,
    'default': DevelopmentConfig
}

This is something that I just copy and paste across apps and then modify accordingly. Next we need to prepare the basic app structure - a folder named app containing a couple of folders:

And some files, the bare necessities for the beginning of the project:

Now comes the (first) tricky part. You can’t work on files sequentially, at least I cannot. There are three files that have to be synchronized and sort-of edited at the same time in order to be able to get down to the flask shell and actually build the initial database. There is always some trial and error, so bear with me. Let’s start with models. I want to have users, images and galleries, remember. I also want to use flask-login and handle login/logout authentication and so on. Here is what I come up with:

#!/usr/bin/python
# -*- coding: utf-8 -*-

from flask import current_app, request, url_for
from datetime import datetime

from flask_login import UserMixin, AnonymousUserMixin

import hashlib
from werkzeug.security import generate_password_hash, check_password_hash

from . import db, login_manager

@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))

##############################################################
class User(UserMixin, db.Model):

    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key = True)
    email = db.Column(db.String(64), unique = True, index = True)
    username = db.Column(db.String(64), unique = True, index = True)
    password_hash = db.Column(db.String(128))
    is_admin = db.Column(db.Boolean(),default=False)
  
    @property
    def password(self):
        raise AttributeError('password not readable')

    @password.setter
    def password(self,password):
        self.password_hash = generate_password_hash(password)

    def verify_password(self, password):
        try:
            return check_password_hash(self.password_hash, password)
        except AttributeError:
            return False

    def __repr__(self):
        return unicode(self.username)


class Gallery(db.Model):

    __tablename__ = 'galleries'

    id = db.Column(db.Integer, primary_key = True)
    created = db.Column(db.DateTime(), default=datetime.now)
    title = db.Column(db.String)
    description = db.Column(db.Text)
    pictures = db.relationship('Picture', backref='pictures',
                                lazy='dynamic')
    
    def __repr__(self):
        return u"Gallery name: {0}".format(self.title)


class Picture(db.Model):

    __tablename__ = 'pictures'

    id = db.Column(db.Integer, primary_key = True)
    created = db.Column(db.DateTime(), default=datetime.now)
    gallery_id = db.Column(db.Integer, db.ForeignKey('galleries.id'))
    gallery = db.relationship('Gallery',  foreign_keys=[gallery_id])
    title = db.Column(db.String)
    description = db.Column(db.Text)
    filename = db.Column(db.String)
    thumb = db.Column(db.String)
    bw_thumb = db.Column(db.String)

    def __repr__(self):
        return u"Picture name: {0}".format(self.title)

The basic idea is to define the database model and to think of all, or at least most of the fields that we’re going to need. For instance, when I store a Picture, I want to know when it was uploaded, in which gallery it belongs, a name, maybe a description… and then I added a couple of text fields for storing the information about the various versions of the image file: a thumbnail version, a black and white version and so on.

Galleries are very similar and the User model is pretty much dictated by the Flask-Login extension. The only interesting part is when we set and get the password with a hashing function - an incredibly simple Miguel’s solution that works.

Now on to the init.py file, where we initialize the app and attach the various pieces, extensions and other goodies.

#!/usr/bin/python
# -*- coding: utf-8 -*-

import os

from flask import Flask, flash, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
from flask_login import login_required, current_user

from config import config

basedir = os.path.abspath(os.path.dirname(__file__))

db = SQLAlchemy()

# start the login manager
login_manager = LoginManager()
login_manager.session_protection = 'strong'
login_manager.login_view = 'auth.login'
login_manager.login_message = u"You have to be logged in!"

# import the models
from .models import User, Gallery, Picture

# create the app with a configuration
def create_app(config_name):
    app = Flask(__name__)
    app.config.from_object(config[config_name])
    config[config_name].init_app(app)
    db.init_app(app)
    login_manager.init_app(app)
    return app

I believe this part was pretty simple, so let’s see the management commands - manage.py:

#!/usr/bin/env python
import os
from app import create_app, db

# import all relevant models from models.py
from app.models import User,Picture, Gallery
from flask_script import Manager, Shell
from flask_migrate import Migrate, MigrateCommand

app = create_app(os.getenv('FLASK_CONFIG') or 'default')
manager = Manager(app)
migrate = Migrate(app, db)

def make_shell_context():
	# add all imported models to dict
    return dict(app=app, db=db, User=User, Gallery=Gallery, Picture = Picture)
manager.add_command("shell", Shell(make_context=make_shell_context))
manager.add_command('db', MigrateCommand)

   
if __name__ == '__main__':
    manager.run()

The manage.py file is straightforward: we have to import the models, the script and migrate commands and then make the shell context - basically just pack all the models and the app in a dictionary and return it.

Finally, we are able to go to the command line and type:

python manage.py shell

If there are no errors, we’ll be able to type db.create_all() and then we should see a cms-dev.sqlite file pop up in the cms directory. We can open it in an SQLiteBrowser and inspect it. It should work.

This is a good moment to take a break, and initialize or git repo. Go to the cms folder and type:

git init

Now that we saved our hard work comes the fun part. First, we want to add database migrations. Jump into the shell and type:

python manage.py db init

This will create a folder migrations in the root cms folder. As per documentation this folder should be under version control, so let’s add it: git add migrations.

Testing - just the setup

While I do not follow the latest recommendation to use a flask script and export it’s path, thus replacing the manage.py command, I follow blindly Miguel’s test structure, as it is quite simple and works very well. Basically we create a new folder tests in the topmost directory and there we put our tests.

For now I will just make one test for every type of test - one for the models, one for the view functions and so on. The rest can easily be built upon.

So…

import unittest
from app.models import User

class UserModelTestCase(unittest.TestCase):

    def test_password_set(self):
        u = User(password='yes')
        self.assertTrue(u.password_hash is not None)

# test all model functionalities here

This is enough for now. Later we will add further testing functionalities, coverage, functional test and so on. It is important, at least in my case, to lay out the foundations and be able to later sit down and know exactly where I am headed - write the tests, make them pass, rinse, repeat. If you are struggling to keep coding for several hours a day, this pattern might come in handy: when you have more time for deep work, prepare the playground, lay out the project, work on the structure and overall setup, then later, you can just sit and say “today I am going to add feature X” and so on.

(Just one) Blueprint

Flask’s main strength is hidden in the blueprints, separate components that encapsulate some logical functionalities and map to urls. Our first blueprint here will be called gallery and it will include all of the views (controllers) and templates related to the pictures, galleries and it will also include the users in some way, since the users are the owners of images and galleries.

We’ll make a new folder gallery (inside the app folder) and make it a python module, by adding a very short __init__.py file.

from flask import Blueprint

gallery = Blueprint('gallery', __name__)

from . import views

Import the Blueprint, define the name and… that’s about it. Then we (relatively) import the views that we’re about to define. Later we’ll add errors and other things. Now we have to hook the fresh blueprint by registering it with the app constructor in the __init__.py function of the app module:

    from .gallery import gallery as gallery_blueprint
    app.register_blueprint(gallery_blueprint, url_prefix = '/img')

Here I am naming the blueprint gallery and giving it a prefix ‘/img’ which means that all the view functions defined within will map to www.site.com/img.

Before writing any functions, and there are going to be quite a few (image uploading, resizing, creating galleries, deleting etc) we should write tests, so I am going to stop with the blueprints here and skip over to one of the last minimum requirements for setting up the workflow - templates.

Basic templates with bootstrap

Nothing fancy here, we will try to keep it as simple as possible by using flask-bootstrap. The setup is simple enough: first we have to… arghhh. I got a little carried away. We’ll have to uninstall flask-bootstrap (pip uninstall flask-bootstrap) and install flask-bootstrap4 in order to have the nice BS4 templates. Ok, the rest should be identical. I am about to make a new git commit now, it should be more than enough for the first post.