Simple-flask-app

A simple flask tutorial

Download as .zip Download as .tar.gz View on GitHub

Welcome to the simple Flask App.

Before we get started here is what each color means for when you see the code blocks.

This COLOR code means that the code is to be added
or already exists and does not need to be removed.
This COLOR code means that the code is old and needs to be removed.
This COLOR code means that the code is new and is to be added to the file.

Let's get started creating a simple blog site with Flask. Start by creating a project directory.

mkdir my-flask-app
cd my-flask-app

We will need to install a few things to get started. First we will need a python virtual environment. You can go here for more in depth instructions. If on Mac OSX / Linux the command below should work for you.

sudo pip install virtualenv

To add the virtual environment directory do

virtualenv venv

Cool now you have your virtual environment, let's start it up.

. venv/bin/activate

Now that the python virtual environment is running go ahead and install Flask.

pip install Flask

Writing flask

OK now that you have everything set up it is time to start writing some flask code.

Start by creating a file called app.py in your favorite text editor, I will be using vim.

vim app.py

Once inside of app.py add these lines of code.

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello World!'

if __name__ == '__main__':
    app.run(
        debug=True,
    )

CONGRATULATIONS! You have made a flask app! Now let's start it up.

python app.py

By default it will run on your localhost port 5000.

Adding HTML

Alright so that's cool we have a working flask app, but it is still boring. Let's add some HTML and make it look nice.

Flask looks for the templates directory for html. So let's create it.

mkdir templates
cd templates

I will be using a Bootstrap template for quick and easy setup. To download the bootstrap page I will be using you can use

wget https://gist.githubusercontent.com/kevcoxe/4f9cf8b03dccf033782bc3e85ff12a88/raw/8987733921a74a158bb07926cb3f6d3d6698e848/index.html

Otherwise you can go here view source and download the page and save it as index.html.

Alright now that we have our template ready let's load it. We will add these changes to our app.py.

cd ../
vim app.py

from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello World!'
def index():
    return render_template('index.html')

if __name__ == '__main__':
    app.run(
        debug=True,
    )

Sweet! Reload your app and check it out!

Jinja Templates

That index.html file is great but would be better if it was broken into a couple different files. Let's do that now.

Start by creating the file base.html in the templates directory.

cd templates
vim base.html

Then add this to base.html.

<!doctype html>
<html lang="en">
    <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
    <meta name="description" content="">
    <meta name="author" content="">
    <link rel="icon" href="http://getbootstrap.com/favicon.ico">
    <link rel="canonical" href="https://getbootstrap.com/docs/3.4/examples/jumbotron/">

    <title>Jumbotron Template for Bootstrap</title>

    <!-- Bootstrap core CSS -->
    <link href="https://getbootstrap.com/docs/3.4/dist/css/bootstrap.min.css" rel="stylesheet">

    <!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
    <link href="https://getbootstrap.com/docs/3.4/assets/css/ie10-viewport-bug-workaround.css" rel="stylesheet">

    <!-- Custom styles for this template -->
    <link href="https://getbootstrap.com/docs/3.4/examples/jumbotron/jumbotron.css" rel="stylesheet">

    <!-- Just for debugging purposes. Don't actually copy these 2 lines! -->
    <!--[if lt IE 9]><script src="https://getbootstrap.com/docs/3.4/assets/js/ie8-responsive-file-warning.js"></script><![endif]-->
    <script src="https://getbootstrap.com/docs/3.4/assets/js/ie-emulation-modes-warning.js"></script>

    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
    <!--[if lt IE 9]>
        <script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
        <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
    <![endif]-->
    </head>

    <body>

    {% block body %}{% endblock %}

    <!-- Bootstrap core JavaScript
    ================================================== -->
    <!-- Placed at the end of the document so the pages load faster -->
    <script src="https://code.jquery.com/jquery-1.12.4.min.js" integrity="sha384-nvAa0+6Qg9clwYCGGPpDQLVpLNn0fRaROjHqs13t4Ggj3Ez50XnGQqc/r8MhnRDZ" crossorigin="anonymous"></script>
    <script>window.jQuery || document.write('<script src="https://getbootstrap.com/docs/3.4/assets/js/vendor/jquery.min.js"><\/script>')</script>
    <script src="https://getbootstrap.com/docs/3.4/dist/js/bootstrap.min.js"></script>
    <!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
    <script src="https://getbootstrap.com/docs/3.4/assets/js/ie10-viewport-bug-workaround.js"></script>
    </body>
</html>

Next we will replace the contents of index.html with the code below.

{% extends 'base.html' %}
{% block body %}

<nav class="navbar navbar-inverse navbar-fixed-top">
    <div class="container">
        <div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
            <span class="sr-only">Toggle navigation</span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="#">Project name</a>
        </div>
        <div id="navbar" class="navbar-collapse collapse">
            <form class="navbar-form navbar-right">
            <div class="form-group">
                <input type="text" placeholder="Email" class="form-control">
            </div>
            <div class="form-group">
                <input type="password" placeholder="Password" class="form-control">
            </div>
            <button type="submit" class="btn btn-success">Sign in</button>
            </form>
        </div><!--/.navbar-collapse -->
    </div>
</nav>

<!-- Main jumbotron for a primary marketing message or call to action -->
<div class="jumbotron">
    <div class="container">
        <h1>Hello, world!</h1>
        <p>This is a template for a simple marketing or informational website. It includes a large callout called a jumbotron and three supporting pieces of content. Use it as a starting point to create something more unique.</p>
        <p><a class="btn btn-primary btn-lg" href="#" role="button">Learn more »</a></p>
    </div>
</div>

<div class="container">
    <!-- Example row of columns -->
    <div class="row">
        <div class="col-md-12">
            <h2>Heading</h2>
            <p>Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui. </p>
            <p><a class="btn btn-default" href="#" role="button">View details »</a></p>
        </div>
    </div>

    <hr>

    <footer>
    <p>© 2016 Company, Inc.</p>
    </footer>
</div> <!-- /container -->
{% endblock %}

OK so that was a lot of copy paste, but what did we get out of it? We just added Jinja templating. The page is now grabbing data from multiple HTML files to create one page.

When index.html is loading it will see

{% extends 'base.html' %}
{% block body %}
...
{% endblock %}

This tells the Jinja template to go look at base.html and find where we should put the "body block". Looking in base.html we will see this line

{% block body %}{% endblock %}

that shows where the "body block" should be loaded.

Adding Dynamic Content From Data

So now that we have a sample of a 'post' for our app let's feed data into our templates to display content. We will modify our index.html so that it excepts an object named "posts".

{% extends 'base.html' %}
{% block body %}

<nav class="navbar navbar-inverse navbar-fixed-top">
    <div class="container">
        <div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
            <span class="sr-only">Toggle navigation</span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="#">Project name</a>
        </div>
        <div id="navbar" class="navbar-collapse collapse">
            <form class="navbar-form navbar-right">
            <div class="form-group">
                <input type="text" placeholder="Email" class="form-control">
            </div>
            <div class="form-group">
                <input type="password" placeholder="Password" class="form-control">
            </div>
            <button type="submit" class="btn btn-success">Sign in</button>
            </form>
        </div><!--/.navbar-collapse -->
    </div>
</nav>

<!-- Main jumbotron for a primary marketing message or call to action -->
<div class="jumbotron">
    <div class="container">
        <h1>Hello, world!</h1>
        <p>This is a template for a simple marketing or informational website. It includes a large callout called a jumbotron and three supporting pieces of content. Use it as a starting point to create something more unique.</p>
        <p><a class="btn btn-primary btn-lg" href="#" role="button">Learn more »</a></p>
    </div>
</div>

<div class="container">
    <!-- Example row of columns -->
    <div class="row">
    {% for post in posts %}
        <div class="col-md-12">
            <h2>Heading</h2>
            <p>Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui. </p>
            <p><a class="btn btn-default" href="#" role="button">View details »</a></p>
            <h2>{{ post['title'] }}</h2>
            <p>{{ post['content'] }}</p>
        </div>
    {% endfor %}
    </div>

    <hr>

    <footer>
    <p>© 2016 Company, Inc.</p>
    </footer>
</div> <!-- /container -->
{% endblock %}

OK now reload your app. Since we never passed in any posts nothing appears.

Time to create some posts. Open up our app.py and change it to look like this.

from flask import Flask, render_template
app = Flask(__name__)

@app.route('/')
def index():
    return render_template('index.html')
    posts = [
        {'title': 'Test 1', 'content': 'This is the content for Test 1.'},
        {'title': 'Test 2', 'content': 'This is the content for Test 2.'},
        {'title': 'Test 3', 'content': 'This is the content for Test 3.'},
        ]
    return render_template('index.html', posts=posts)

if __name__ == '__main__':
    app.run(
        debug=True,
    )

Cool so now we can pass in objects and use them to populate the HTML. Lets break our index.html up into even more files.

Create a new HTML file in the templates directory named macros.html.

cd templates
vim macros.html

{% macro navbar() -%}
<nav class="navbar navbar-inverse navbar-fixed-top">
    <div class="container">
        <div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
            <span class="sr-only">Toggle navigation</span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="#">Simple-Flask-App</a>
        </div>
    </div>
</nav>
{%- endmacro %}

{% macro list_items(posts) -%}
<div class="row">
{% for post in posts %}
<div class="col-md-12">
    <h2>{{ post['title'] }}</h2>
    <p>{{ post['content'] }}</p>
</div>
{% endfor %}
</div>
{%- endmacro %}

{% macro footer() -%}
<footer>
    <p>UMBC Hackers</p>
</footer>
{%- endmacro %}

Now we just need to modify our index.html so that it uses these macros.

{% extends 'base.html' %}
{% import 'macros.html' as macros %}
{% block body %}
<nav class="navbar navbar-inverse navbar-fixed-top">
    <div class="container">
        <div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
            <span class="sr-only">Toggle navigation</span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="#">Project name</a>
        </div>
        <div id="navbar" class="navbar-collapse collapse">
            <form class="navbar-form navbar-right">
            <div class="form-group">
                <input type="text" placeholder="Email" class="form-control">
            </div>
            <div class="form-group">
                <input type="password" placeholder="Password" class="form-control">
            </div>
            <button type="submit" class="btn btn-success">Sign in</button>
            </form>
        </div><!--/.navbar-collapse -->
    </div>
</nav>
<!-- navbar from macros.hmtl -->
{{ macros.navbar() }}

<!-- Main jumbotron for a primary marketing message or call to action -->
<div class="jumbotron">
    <div class="container">
    <h1>My First Flask App</h1>
    <p>Let's learn some Flask!</p>
    <p><a class="btn btn-success btn-lg" href="#" role="button">Write Post</a></p>
    </div>
</div>

<div class="container">
    <!-- Example row of columns -->
    <div class="row">
    {% for post in posts %}
        <div class="col-md-12">
            <h2>{{ post['title'] }}</h2>
            <p>{{ post['content'] }}</p>
        </div>
    {% endfor %}
    </div>
    {{ macros.list_items(posts=posts) }}
    <hr>
    <footer>
    <p>2016 Company, Inc.</p>
    </footer>
    {{ macros.footer() }}
</div> <!-- /container -->
{% endblock %}

So we have now replaced our 'navbar', 'post items', and 'footer' with Jinja tags that will go to our macros.html file and grab the content that is needed.

SQLite3

Let's add a database to our app. SQLite works well with Flask. If you need more info on how to set up SQLite check out this page.

If you are using linux check your package manager for sqlite3.

If you are using Mac OSX and have homebrew then do the following.

brew install sqlite3

If you do not have homebrew then you can download sqlite here. And you can find instructions on how to get homebrew here.

If on windows you can download it here.

Once you have SQLite you can create a database with the following command.

sqlite3 my_database.db
sqlite> create table my_posts (
   ...> id integer primary key autoincrement,
   ...> title text,
   ...> content text
   ...> );
sqlite> .tables
my_posts
sqlite> insert into my_posts values(null, "Test title", "This post was made from command line");
sqlite> select * from my_posts;
1|Test title|This post was made from command line
sqlite> .quit

If you did not see "my_posts" after the command .tables then the table was not created. More information on how to create sqlite tables can be found here or here.

You will now have a new file my_database.db.

We can now add the link to the database to our Flask app. We will now edit out app.py to look like this.

import sqlite3
from flask import Flask, render_template, g, redirect
app = Flask(__name__)


DATABASE = 'my_database.db'


@app.route('/')
def index():
    posts = [ 
        {'title':'Test 1','content':'This is the content for Test 1.'},
        {'title':'Test 2','content':'This is the content for Test 2.'},
        {'title':'Test 3','content':'This is the content for Test 3.'},
        ]
    posts = query_db('select * from my_posts')
    return render_template('index.html', posts=posts)

# delete items from the database
    # using their 'id'
    @app.route('/delete_post/', methods=['POST'])
    def remove_post(id=0):
        delete_post(id)
        return redirect('/')
    
    
    def delete_post(id):
        g.db.execute('delete from my_posts where(id == "%s");' % (
            id,
            ))
        g.db.commit()
    
    
    # insert a 'post' into the database
    def insert_post(post):
        g.db.execute('insert into my_posts values(%s,"%s","%s");' % (
            post['id'],
            post['title'],
            post['content'],
            ))
        g.db.commit()
    
    
    # take a 'query' string and execute it
    def query_db(query, args=(), one=False):
        cur = g.db.execute(query, args)
        rv = [dict((cur.description[idx][0], value)
                    for idx, value in enumerate(row)) for row in cur.fetchall()]
        return (rv[0] if rv else None) if one else rv
    
    
    # connect to the database
    def connect_db():
        return sqlite3.connect(DATABASE)
    
    
    @app.before_request
    def before_request():
        g.db = connect_db()
    
    
    @app.after_request
    def after_request(response):
        g.db.close()
        return response


if __name__ == '__main__':
    app.run(
        debug=True,
    )

Sweet! You have successfully connected to your sqlite database. If you reload the page you will see that it now displays the post you created from the command line.

Let's add the ability to create a post. To start you will need to create a file add_post.html in the templates directory.

cd templates
vim add_post.html

{% extends 'base.html' %}
{% import 'macros.html' as macros %}
{% block body %}
<!-- navbar from macros.hmtl -->
{{ macros.navbar() }}
<div class="container">
<!-- Main jumbotron for a primary marketing message or call to action -->
<div class="jumbotron">
  <div class="container">
    <h1>Write A Post</h1>
    <p>Enter the information below to post.</p>
    <form role="form" action="/write_post" method="post">
      <div class="input-group input-group-lg">
        <span class="input-group-addon" id="sizing-addon1">Title</span>
        <input type="text" class="form-control" placeholder="My Title" aria-describedby="sizing-addon1" name="title">
      </div>
      <div class="form-group">
        <label for="comment">Content</label>
        <textarea class="form-control" rows="5" id="content" name="content"></textarea>
      </div>
      <p><input type="submit" class="btn btn-lg btn-success" value="post"></input></p>
    </form>
  </div>
</div>
<hr>
{{ macros.footer() }}
</div> <!-- /container -->
{% endblock %}

Now we need to add a route to our app.py that will link to add_post.html and we will need to add a route for "/write_post".

import sqlite3
from flask import Flask, render_template, g, request, redirect
app = Flask(__name__)

DATABASE = 'my_database.db'

@app.route('/')
def index():
    posts = query_db('select * from my_posts')
    return render_template('index.html', posts=posts)


@app.route('/start_post')
def form_post():
    return render_template('add_post.html')


@app.route('/write_post', methods=['POST'])
def add_post():
    title = request.form['title']
    content = request.form['content']
    post = {'id': 'null', 'title': title, 'content': content}
    insert_post(post)
    return redirect('/')


# delete items from the database
# using their 'id'
@app.route('/delete_post/<int:id>', methods = ['POST'])
def remove_post(id=0):
    delete_post(id)
    return redirect('/')


def delete_post(id):
    cur = g.db.execute('delete from my_posts where(id == "%s");' % (
        id,
        ))
    g.db.commit()


# insert a 'post' into the database
def insert_post(post):
    cur = g.db.execute('insert into my_posts values(%s,"%s","%s");' % (
        post['id'],
        post['title'],
        post['content'],
        ))
    g.db.commit()


# take a 'query' string and execute it
def query_db(query, args=(), one=False):
    cur = g.db.execute(query, args)
    rv = [dict((cur.description[idx][0], value)
               for idx, value in enumerate(row)) for row in cur.fetchall()]
    return (rv[0] if rv else None) if one else rv


# connect to the database
def connect_db():
    return sqlite3.connect(DATABASE)


@app.before_request
def before_request():
    g.db = connect_db()


@app.after_request
def after_request(response):
    g.db.close()
    return response


if __name__ == '__main__':
    app.run(
        debug=True,
    )

Oops! We need a way to get to our new page. Let's add some links to it. We will need to add a href to our button in index.html and to the item in our 'navbar' in macros.html.

index.html
{% extends 'base.html' %}
{% import 'macros.html' as macros %}
{% block body %}
<!-- navbar from macros.hmtl -->
{{ macros.navbar() }}

<!-- Main jumbotron for a primary marketing message or call to action -->
<div class="jumbotron">
    <div class="container">
    <h1>My First Flask App</h1>
    <p>Let's learn some Flask!</p>
    <p><a class="btn btn-success btn-lg" href="#" role="button">Write Post</a></p>
    <p><a class="btn btn-success btn-lg" href="/start_post" role="button">Write Post</a></p>
    </div>
</div>

<div class="container">
    {{ macros.list_items(posts=posts) }}
    <hr>
    {{ macros.footer() }}
</div> <!-- /container -->
{% endblock %}

macros.html
{% macro navbar() -%}
<nav class="navbar navbar-inverse navbar-fixed-top">
    <div class="container">
    <div class="navbar-header">
        <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
        <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        </button>
        <a class="navbar-brand" href="/">Flask Posts</a>
    </div>
    <div id="navbar" class="navbar-collapse collapse">
      <ul class="nav navbar-nav">
        <li class="active"><a href="/">Home</a></li>
        <li class=""><a href="#">Write Post</a></li>
        <li class=""><a href="/start_post">Write Post</a></li>
      </ul>
    </div><!--/.nav-collapse -->
</nav>
{%- endmacro %}

{% macro list_items(posts) -%}
<div class="row">
{% for post in posts %}
<div class="col-md-12">
    <div class="well">
        <h2>{{ post['title'] }}</h2>
        <p>{{ post['content'] }}</p>
    </div>
</div>
{% endfor %}
</div>
{%- endmacro %}

{% macro footer() -%}
<footer>
    <p>UMBC Hackers</p>
</footer>
{%- endmacro %}

Whoo! Check it out you can now add a post to the page!

OK so now we should also add the ability to delete posts. Let's add a "delete" button. All we need is to change our macro "list_items(posts)" in macros.html.

{% macro navbar() -%}
<nav class="navbar navbar-inverse navbar-fixed-top">
    <div class="container">
    <div class="navbar-header">
        <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
        <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        </button>
        <a class="navbar-brand" href="/">Flask Posts</a>
    </div>
    <div id="navbar" class="navbar-collapse collapse">
      <ul class="nav navbar-nav">
        <li class="active"><a href="/">Home</a></li>
        <li class=""><a href="/start_post">Write Post</a></li>
      </ul>
    </div><!--/.nav-collapse -->
</nav>
{%- endmacro %}

{% macro list_items(posts) -%}
<div class="row">
{% for post in posts %}
<div class="col-md-12">
    <div class="well">
        <h2>{{ post['title'] }}</h2>
        <p>{{ post['content'] }}</p>
        <form action="/delete_post/{{ post['id'] }}" method="post">
            <p><input type="submit" class="btn btn-lg btn-danger" value="delete"></input></p>
        </form>
    </div>
</div>
{% endfor %}
</div>
{%- endmacro %}

{% macro footer() -%}
<footer>
    <p>UMBC Hackers</p>
</footer>
{%- endmacro %}

Done!

YAY! You have finished your first Flask app! Hopefully it is just the first of many to come. Thank you for following along.