CS50 Week 9: Flask 🧴
Spinning up a web app with Flask and sqlite db.
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
andendraw
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>