Skip to content

CS50 Week 9: Flask 🧴

Spinning up a web app with Flask and sqlite db.

Written by Eva Dee on (about a 20 minute read).

Flask permalink

  • a web server and a framework (providing you a set of conventions for how it should be used)
  • this note features a lot of code snippets (note: jinja code has to be wrapped with raw and endraw to prevent 11ty from crashing the site)

Typical project structure in flask:

application.py
requirements.txt
static/
templates/

The MVC design pattern permalink

  • model (what technique/software/service you're using for your data, the db)
  • view (anything involving the user, the UI, the templates)
  • controller (files that control your app, the appplication.py)

The simplest flask application permalink

# in application.py

# render_template is similar to open(), read()
# reuest is for reading params
from flask import Flask, render_template, request

# refers to the name of the current file
# Flask, turn the current file into a flask app
app = Flask(__name__)

# python decorator @ is a special way of
# applying one function to another
@app.route("/")
def index():
	# needs templates/index.html file
	# you can plug the  into the template
	# ?name=Eva to use, but with "world" for default
    return render_template("index.html", name=request.args.get("name", "world"))
  • To run the app: flask run

Forms permalink

// index.html
<!DOCTYPE html>

<html lang="en">
    <head>
        <title>hello</title>
    </head>
    <body>
        <form action="/greet" method="post">
            <input name="name" type="text">
            <input type="submit">
        </form>
    </body>
</html>

Back in the controller:

@app.route("/")
def index():
    return render_template("index.html")

# get is the default, other methods you have to pass
# request.args.get for GET and request.form.get for POST
@app.route("/greet", methods=["POST"])
def greet():
    return render_template("greet.html", name=request.form.get("name", "world"))

Layout template permalink

  • Flask uses the jinja templating language:
<!DOCTYPE html>

<html lang="en">
    <head>
        <title>hello</title>
    </head>
    <body>
        
    </body>
</html>

Which is used like this:


{% extends "layout.html" %}

{% block body %}

    hello, {{ name }}

{% endblock %}

We can reuse the same route, of course:

@app.route("/", methods=["GET", "POST"])
def index():
    if request.method == "POST":
        return render_template("greet.html", name=request.form.get("name", "world"))
    return render_template("index.html")

A Simple WebApp permalink

Inside application.py:

from flask import Flask, render_template, request

app = Flask(__name__)

SPORTS = [
    "Dodgeball",
    "Flag Football",
    "Soccer",
    "Volleyball",
    "Ultimate Frisbee"
]

@app.route("/")
def index():
	# passing the sports var to the index template
    return render_template("index.html", sports=SPORTS)
@app.route("/register", methods=["POST"])

@app.route("/register", methods=["POST"])
def register():

	# basic error handling
    if not request.form.get("name") or request.form.get("sport") not in SPORTS:
        return render_template("failure.html")

    	return render_template("success.html")
	return render_template("success.html")


Storing inside a db permalink

from cs50 import SQL
from flask import Flask, redirect, render_template, request

app = Flask(__name__)

# make sure to create a db
db = SQL("sqlite:///froshims.db")


@app.route("/register", methods=["POST"])
def register():

	# better error handling with custom message
    name = request.form.get("name")
    if not name:
        return render_template("error.html", message="Missing name")
    sport = request.form.get("sport")
    if not sport:
        return render_template("error.html", message="Missing sport")
    if sport not in SPORTS:
        return render_template("error.html", message="Invalid sport")

    db.execute("INSERT INTO registrants (name, sport) VALUES(?, ?)", name, sport)

	# redirecting to registered users page
    return redirect("/registrants")


@app.route("/registrants")
def registrants():
    registrants = db.execute("SELECT * FROM registrants")
    return render_template("registrants.html", registrants=registrants)

And inside registrants.html

<tbody>
    
    {% for registrant in registrants %}
        <tr>
            <td>{{ registrant.name }}</td>
            <td>{{ registrant.sport }}</td>
            <td>
                <form action="/deregister" method="post">
                    <input name="id" type="hidden" value="{{ registrant.id }}">
                    <input type="submit" value="Deregister">
                </form>
            </td>
        </tr>
    {% endfor %}
     
</tbody>

Sending email confirmation permalink

import os
import re

from flask import Flask, render_template, request
from flask_mail import Mail, Message

app = Flask(__name__)
app.config["MAIL_DEFAULT_SENDER"] = os.getenv("MAIL_DEFAULT_SENDER")
app.config["MAIL_PASSWORD"] = os.getenv("MAIL_PASSWORD")
app.config["MAIL_PORT"] = 587
app.config["MAIL_SERVER"] = "smtp.gmail.com"
app.config["MAIL_USE_TLS"] = True
app.config["MAIL_USERNAME"] = os.getenv("MAIL_USERNAME")
mail = Mail(app)

@app.route("/register", methods=["POST"])
def register():
	# we changed the name to email
    email = request.form.get("email")
    if not email:
        return render_template("error.html", message="Missing email")
    sport = request.form.get("sport")
    if not sport:
        return render_template("error.html", message="Missing sport")
    if sport not in SPORTS:
        return render_template("error.html", message="Invalid sport")

    message = Message("You are registered!", recipients=[email])
    mail.send(message)

    return render_template("success.html")

Sessions permalink

from flask import Flask, redirect, render_template, request, session
from flask_session import Session

app = Flask(__name__)
app.config["SESSION_PERMANENT"] = False
app.config["SESSION_TYPE"] = "filesystem"
Session(app)


@app.route("/")
def index():
	# if not logged in, redirect to login
    if not session.get("name"):
        return redirect("/login")
    return render_template("index.html")

# remember to support both methods
@app.route("/login", methods=["GET", "POST"])
def login():
    if request.method == "POST":

		# saving the name inside the globally
		# available session const
        session["name"] = request.form.get("name")
        return redirect("/")
    return render_template("login.html")


@app.route("/logout")
def logout():
	# deleting the 🍪
    session["name"] = None
    return redirect("/")

And index.html:


{% extends "layout.html" %}

{% block body %}

    {% if session.name %}
        You are logged in as {{ session.name }}. <a href="/logout">Log out</a>.
    {% else %}
        You are not logged in. <a href="/login">Log in</a>.
    {% endif %}

{% endblock %}

Adding JS to the mix permalink

@app.route("/search")
def search():
    shows = db.execute("SELECT * FROM shows WHERE title LIKE ?", "%" + request.args.get("q") + "%")
	# make sure to import the jsonify library from flask
	# this will convert a python dict into a json object
    return jsonify(shows)

And finally this is how you'd dynamically display the search results (using jQuery 😱):

<!DOCTYPE html>

<html lang="en">
    <head>
        <meta name="viewport" content="initial-scale=1, width=device-width">
        <title>shows</title>
    </head>
    <body>

        <input autocomplete="off" autofocus placeholder="Query" type="search">

        <ul></ul>

        <script crossorigin="anonymous" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
        <script>

            let input = document.querySelector('input');
            input.addEventListener('keyup', function() {
                $.get('/search?q=' + input.value, function(shows) {
                  let html = '';
                  for (let id in shows)
                  {
                      let title = shows[id].title;
                      html += '<li>' + title + '</li>';
                  }

                  document.querySelector('ul').innerHTML = html;
                });
            });

        </script>

    </body>
</html>