Skip to content

CS50 Week 9: Flask 🧴

Spinning up a web app with Flask and sqlite db.

Written by Eva Dee on (about a 19 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>