top of page

CS50x Week 9: Flask

Introduction


As of yet, we've learned how to create static sites--that is to say, no matter the visitor, the same information would be displayed.


Flask offers us a way to add a degree of dynamism into our sites--now, our websites will be able to display different information based on which account a user logs in with, for instance.



Let's break down Week 9.


Flask in a Nutshell


Put simply, Flask is a Python framework, meaning it was written using Python. What's so special about Flask is that it allows us to host web applications (using the command flask run) dynamically--now, we can access specific routes in our web app.


We can also use Flask's Jinja2 template engine to pass values from Python into our HTML file, interconnecting the two and opening the door for a much more dynamic site.


For instance, we can get the user's name through Flask's user input command in Python, and then display that name in HTML:


from flask import Flask, render_template, request

app = Flask(__name__)


@app.route("/")
def index():
    return render_template("index.html", name=request.args.get("name", "world"))
<!DOCTYPE html>

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

Here, the double curly braces around name allow for a variable name to be passed, @app.route("/") refers to the main website page route (that has a "/" appended by default) and render_template() allows for the html page to be displayed with user-inputted name.


It's also worth noting that since Flask is a library, it should be written in requirements.txt, which stores all libraries and dependencies, as such:

Flask

Instead of having the user input their name in the terminal, however, it would probably be more effective to create a form in index.html, followed by a new html file where name would be passed!

from flask import Flask, render_template, request

app = Flask(__name__)


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


@app.route("/greet")
def greet():
    return render_template("greet.html", name=request.args.get("name", "world"))

Now, our program has two file paths, rendering different html templates.


Abstraction using Layout


One crucial part of writing code is maintaining good design. Flask includes some features that allow us to minimize repetition in our code.


Let's create a template called layout.html.


<!DOCTYPE html>

<html lang="en">
    <head>
        <meta name="viewport" content="initial-scale=1, width=device-width">
        <title>hello</title>
    </head>
    <body>
        {% block body %}{% endblock %}
    </body>
</html>

We can insert code where this placeholder {% block body %}{% endblock %} currently is.


In index.html:


{% extends "layout.html" %}

{% block body %}

    <form action="/greet" method="get">
        <input autocomplete="off" autofocus name="name" placeholder="Name" type="text">
        <button type="submit">Greet</button>
    </form>

{% endblock %}

Using the "extends" syntax, coupled with "block body" and "endblock", we are able to write a much shorter, much more concise version of a page sending user input to the route "/greet" via a form.


As you can imagine, we can also apply this to greet.html:


{% extends "layout.html" %}

{% block body %}
    hello, {{ name }}
{% endblock %}

HTTP Request Methods: GET and POST


Earlier, our form utilized the "get" request to obtain user input. This, however, would add the form contents to the end of the URL, which is not particularly ideal.


In general, GET retrieves data from a specific source, while POST sends data to the server to update a source.


As a rule of thumb:

  • Use GET to retrieve data with no side effects

  • Use POST to update some resource on the site


With this information, let's update our previous code:


from flask import Flask, render_template, request

app = Flask(__name__)


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


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

Now, we're ensuring that any time "/greet" is invoked, it is only ever updating the website by rendering the greet template. Also, we've replaced request.args.get with request.form.get because now we expect user input from a form rather than the terminal.


While this does work, let's tidy up the code to be done in a single routing.


from flask import Flask, render_template, request

app = Flask(__name__)


@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")

Here, request.method simply checks the method used to access the route "/", which now accepts both GET and POST.


Flask (or Jinja, rather) even allows us to loop over data structures like lists or dictionaries within HTML itself!


For instance, Professor Malan passes the dictionary "registrants" into the an HTML file and is able to loop over every "name" (a variable) using the following syntax.


{% for name in registrants %}...{% endfor %}

Combining Data Manipulation and Flask


Reaching the end of the course, nearly everything we've learned is coming full circle. Rewinding to SQL in Week 7, we can execute SQL queries within Python itself thanks to the cs50 library using:


db = SQL("sqlite:///froshims.db")

and

db.execute("//Insert SQL Command")

You can imagine how powerful data manipulation across different routes can be in a website, first retrieving user input using commands like request.form.get in Flask, and then transferring that information into a database.


Session from Flask-Session


While we could manually ask a user which account to login, logout of, or potentially delete with user input, this would be impractical, as other people's data is at risk of being modified or deleted at the user's discretion.


We can improve on this logical error by using cookies--small files that remember your credentials and tell a server that you're an authorized user.


Using flask_session's Session feature, we can now create a session object that works like a dictionary, keeping track of the user's data, while storing this information as a cookie in the browser to have that information remembered.


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

# Configure app
app = Flask(__name__)

# Configure session
app.config["SESSION_PERMANENT"] = False
app.config["SESSION_TYPE"] = "filesystem"
Session(app)


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


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


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

Here, our app is passed to the Session object. Now, treating session as a dictionary, session["name"] = request.form.get("name") will store the user-inputted name as a value in the dictionary, serving as a user's credentials now stored within a cookie.


You can imagine how most web applications and sites utilize session to ensure that each user's data is protected by enforcing that the user log in with an individualized email and password.


{% 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 %}

In this HTML file, Flask checks that the user's name does indeed exist.


API and JSON


An API (application program interface) refers to a method to connect your program with another service--in other words, a means to share data between two systems. In the past, CS50 has used IMDB's API to access their database or shows, movies, and actors.


Upon getting access to a database via an API (typically by following the instructions given by the data provider), we can simply run SQL commands on that database as normal.


JSON (JavaScript Object Notation) is basically a means to get lots of data from a database with efficiency, by packing dicitonaries with keys and values of data into text files.


Final Thoughts


With Flask under our belts, we're nearly ready to tackle the course's final project.


This was Week 9! Thanks for sticking around.


Meanwhile, stay tuned for updates by following my blog and LinkedIn pages.


Comments


bottom of page