Python Flask

4 minute read

Using this!

MAKE APP

  1. Make & cd project directory
  2. Make and start virtual environment
python3 -m venv v-env
source v-env/bin/activate

NOTE: You can exit from the virtualenv using exit command, or by pressing Ctrl+d.

  1. Install packages
pip3 install flask flask-sqlalchemy
  1. Make app.py
from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return "Hello, world!"

if __name__ == "__main__":
    app.run(debug=True)
  1. Create two folders: static and templates

5b. Add this line to app.py

from flask import Flask, render_template
  1. Add index.html to templates folder (with basic html) and update app.py to return render_template instead of just ‘hellow world’
def index():
    # return "Hello, world!"
    return render_template('index.html')
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    Hello cats!
</body>
</html>
  1. Add base.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    {% block head %}{% endblock %}
</head>
<body>
    {% block body %}{% endblock %}
</body>
</html>

7b. Update index.html

{% extends 'base.html' %}

{% block head %}
<h1>What is this? Todos for cats?!</h1>
{% endblock %}

{% block body %}

{% endblock %}
  1. Make a css file and folder within staticstatic/css/main.css
body {
    margin: 0;
    font-family: sans-serif;
}
  1. Link the stylesheet

9a. Update app.py

from flask import Flask, render_template, url_for

9b. Add link to base.html

<link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}">

ADD DATABASE

  1. Database time!

app.py

from flask import Flask, render_template, url_for
from flask_sqlalchemy import SQLAlchemy
# NEW LINE!! ^^^

app = Flask(__name__)

app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db'
db = SQLAlchemy(app)
# NEW LINES!! ^^^
# Telling our app where our DB is located

@app.route('/')
def index():
    # return "Hello, world!"
    return render_template('index.html')

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

NOTE: sqlite:/// //// = absolute path /// = relative path, what we want

  1. Add models
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db'
db = SQLAlchemy(app)

class Todo(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    content = db.Column(db.String(200), nullable=False)
    date_created = db.Column(db.DateTime, default=datetime.utcnow)

    def __repr__(self):
        return '<Task %r>' % self.id

and add this to the top

from datetime import datetime
  1. Activate environment

12a. Make sure you are still in virtual environment

python3
>> from app import db
>> db.create_all()
  1. Add methods to routes

app.py

# @app.route('/')
@app.route('/', methods=['POST', 'GET'])
def index():
    # return "Hello, world!"
    return render_template('index.html')

if __name__ == "__main__":
    app.run(debug=True)
  1. Add actions to index.html
    <form actions="/" method="POST">
        <input type="text" name="content" id="content">
        <input type="submit" value="Add Task">
    </form>
  1. Add requests to app.py

from flask import Flask, render_template, url_for, request
.
.
.
@app.route('/', methods=['POST', 'GET'])
def index():
    if request.method == 'POST':
        pass
    else:
        return render_template('index.html')

15b. Try hitting submit – we get error because we’re not passing anything (try return 'hello' instead of pass)

  1. Add ability to add todo (and add redirect to the top) and…
from flask import Flask, render_template, url_for, request, redirect
.
.
.
@app.route('/', methods=['POST', 'GET'])
def index():
    if request.method == 'POST':
        # get `content` from form
        task_content = request.form['content']
        # create new instance of model
        new_task = Todo(content=task_content)

        # add to db
        try:
            db.session.add(new_task)
            db.session.commit()
            return redirect('/')
        except:
            return 'There was an issue adding your task'
    else:
        return render_template('index.html')
  1. …add query for non-post requests (just arriving at the page)
def index():
    if request.method == 'POST':
        # get `content` from form
        task_content = request.form['content']
        # create new instance of model
        new_task = Todo(content=task_content)

        # add to db
        try:
            db.session.add(new_task)
            db.session.commit()
            return redirect('/')
        except:
            return 'There was an issue adding your task'
    else:
        # querying our database and ordering them by date created 
        tasks = Todo.query.order_by(Todo.date_created).all()
        # pass this variable to our template
        return render_template('index.html', tasks=tasks)
  1. Add more Jinja to index.html so we can view all of our tasks with this fun for-loop!
    <h1>Task Master</h1>
    <table>
        <tr>
            <th>Task</th>
            <th>Added</th>
            <th>Actions</th>
        </tr>
        {% for task in tasks%}
            <tr>
                <td>{{ task.content }}</td>
                <td>{{ task.date_created.date() }}</td>
                <td>
                    <a href="">Delete</a>
                    <br>
                    <a href="">Update</a>
                </td>
            </tr>
        {% endfor %}
    </table>

  1. Add delete route (& function!) to app.py
@app.route('/delete/<int:id>')
def delete(id):
    task_to_delete = Todo.query.get_or_404(id)

    # delete from database
    try:
        db.session.delete(task_to_delete)
        db.session.commit()
        return redirect('/')
    except:
        return 'There was a problem deleting that task'
  1. Add route to html, and while we’re there, add the update route!
        {% for task in tasks%}
            <tr>
                <td>{{ task.content }}</td>
                <td>{{ task.date_created.date() }}</td>
                <td>
                    <a href="/delete/{{ task.id }}">Delete</a>
                    <br>
                    <a href="/update/{{ task.id }}">Update</a>
                </td>
            </tr>
        {% endfor %}
  1. Add update template update.html (pretty much copied from index.html with the table removed)

{% extends 'base.html' %}

{% block head %}
<title>Task Master</title>
{% endblock %}

{% block body %}

<div class="content">

    <h1>Update Task</h1>

    <form actions="/update/{{ task.id }}" method="POST">
        <input type="text" name="content" id="content">
        <input type="submit" value="Add Task">
    </form>
</div>


{% endblock %}
  1. Add update route (& function!) to app.py
@app.route('/update/<int:id>', methods=['GET', 'POST'])
def update(id):
    task = Todo.query.get_or_404(id)
    
    if request.method == 'POST':
        task.content = request.form['content']
        try:
            # db.session.update(task_to_update)
            # commit() does the update for us don't need ^^
            db.session.commit()
            return redirect('/')
        except:
            return 'There was a problem updating that task'
    else:
        return render_template('update.html', task=task)
  1. Update the update form
    <form actions="/update/{{ task.id }}" method="POST">
        <input type="text" name="content" id="content" value="{{ task.content }}">
        <input type="submit" value="Update">
    </form>
  1. Add if statement to index.html to account for no tasks
    <h1>Task Master</h1>
    {% if tasks|length < 1 %}
        <h4>There are no tasks. Create one below!</h4>
    {% else %}
    <table>
        <tr>
            <th>Task</th>
            <th>Added</th>
            <th>Actions</th>
        </tr>
        {% for task in tasks%}
            <tr>
                <td>{{ task.content }}</td>
                <td>{{ task.date_created.date() }}</td>
                <td>
                    <a href="/delete/{{ task.id }}">Delete</a>
                    <br>
                    <a href="/update/{{ task.id }}">Update</a>
                </td>
            </tr>
        {% endfor %}
    </table>
    {% endif %}

PUSH IT LIVE!!

  1. Sign up for / sign in to Heroku

  2. Install Heroku cli

  3. Login via cli & create heroku app
    heroku login
    
  4. Freeze Requirements
    pip3 install gunicorn
    pip3 freeze > requirements.txt
    
  5. OH WAIT ADD PROCFILE This starts a webserver and tells webserver what to run
    touch Procfile
    

Procfile

web: gunicorn app:app
  1. Create Heroku app and GIT IT
    heroku create
    git add .
    git commit -m "WHEE FLASK!"
    git push heroku master
    heroku open
    

Updated: