Hello, Flask
Learning Goals
- Build a simple flask app to gain an understanding of how to use this framework
- Become familiar with Jinja, a templating language used with Flask
Background
Flask, a microframework used for web development, can be confusing when starting out. There are multiple files of different types, and they must reside in specific folders. In this problem, we’ll start with the absolute minimum—a single Python file. From there we will move on to create your first full-fledged web app!
Getting Started
- Log into cs50.dev using your GitHub account.
- Click inside the terminal window and execute
cd
. - Execute
wget https://cdn.cs50.net/2022/fall/labs/9/helloflask.zip
followed by Enter in order to download a zip calledhelloflask.zip
in your codespace. Take care not to overlook the space betweenwget
and the following URL, or any other character for that matter! - Now execute
unzip helloflask.zip
to create a folder calledhelloflask
. - You no longer need the ZIP file, so you can execute
rm helloflask.zip
and respond with “y” followed by Enter at the prompt.
Implementation Details
The most simple Flask app uses just one file, app.py
. This will create an html page and say hello. Add the following into app.py
to get started.
from flask import Flask
app = Flask(__name__)
@app.route("/")
def index():
return "Hello, World!"
This bit of code starts by importing Flask
from the flask
library. Though we don’t need to get too far into the weeds here, Flask
is what programmers call a “class”. Suffice to say for now, it’s like a template for the application we’ll be building. The line app = Flask(__name__)
tells Python to create a particular app, henceforth called “app”, from the template. To create the particular application, we give the Flask
class the name of the current Python file (represented by __name__
), which allows our application to find other files we might later add to it. In this case, the name of the file is also “app” for consistency.
The most mysterious line in the application you’ve written is probably the one where you wrote:
@app.route("/")
This kind of function is called a decorator. Decorators allow you to take a basic function, like app.route
, and extend its functionality with something custom: the function you write after the decorator. You can read more about decorators if you want, or you can just keep using them for now.
This line tells Flask that if an HTTP request comes in for “/”, the index
function should be run. Note that, if we renamed the function to be homepage
, this line would tell Flask that—every time it receives an HTTP request for “/”—it should run the homepage
function.
Notice that the index
function returns a piece of text, “Hello, World!”. This text is what Flask will render to the user when their request to the “/” route is complete. The returned text could be (and often is!) the text of an entire HTML file, which the browser then renders accordingly. But it’s just a piece of text for now, for brevity’s sake.
To run your app, type flask run
into the terminal, and you will get a link to click, similar to when you run http-server
. You’ve written a Flask app! Try returning different text, like <h1>Hello, World!</h1>
. How does this change what you see?
Templates
Instead of putting HTML code right into our Python programs, it’s best to move our HTML into separate files, to keep separate concerns separate. Since our app will likely include a number of similar HTML files, we can create one layout.html
file which all our HTML files “extend”. “Extending” an HTML file means to keep all of the content of that HTML file while also adding some custom content, specific to a particular page.
By convention, Flask puts all HTML files inside of a templates
folder. Inside the helloflask
folder, make a folder called templates
using mkdir templates
. All your HTML files will go in this templates
folder.
Inside templates
, create a layout.html
file that looks like this:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Hello, Flask</title>
</head>
<body>
<h1>Hello, Flask</h1>
{% block body %}{% endblock %}
</body>
</html>
The {% block body %}{% endblock %}
syntax comes from Jinja, which is a “template engine” for Python. The line with {% block body %}{% endblock %}
is what makes this a layout file. If another HTML file extends layout.html
and defines a block called “body”, we’ll insert that block into this section of the layout.html
. A layout file can contain as many blocks as you want. Just give each one a different name.
Now create index.html
so that it extends the template layout.html
, like this:
{% extends "layout.html" %}
{% block body %}
<p>Next we'll put a form here and get some POST action!</p>
{% endblock %}
Finally, we’ll go back to app.py
to see these changes come to life. In the initial import, we’ll need a few more functions from Flask (as shown below). Then, replace the direct return of text with a call to Flask’s render_template
function. The purpose of the render_template
function is to preprocess index.html
such that, when we give it to the browser, it also includes the HTML it “inherits” from layout.html
.
from flask import Flask, render_template, request
app = Flask(__name__)
@app.route("/")
def index():
return render_template("index.html")
When you run your application now (type flask run
!), you’ll get a little more out of your app. But it isn’t really interactive yet!
Forms
HTML forms allow the user to enter data into input fields and send the data to the server, often using the POST method of the HTTP protocol. You can learn more about HTML Forms here.
Add a form to index.html
so that it looks like this:
{% extends "layout.html" %}
{% block body %}
<form action="/" method="post">
What is your favorite color?
<select name="color">
<option value="red">Harvard Crimson</option>
<option value="blue">Yale Blue</option>
</select>
<button type="submit">Submit</button>
</form>
{% endblock %}
Note that each form element has both a name and a value. The name an element is what we’ll use to refer to that element. Its value is the value submitted by the form. Each get passed into app.py
for our use when the form is submitted.
Run your app, visit the /
route, and submit the form. Oops! You should see the message “Method not allowed”. So we’ll first need to make sure Flask allows the use of the POST method on /
.
POST
In app.py
, we need to adjust our route to handle the POST request. Here is what our new app.py
looks like.
from flask import Flask, render_template, request
app = Flask(__name__)
@app.route("/", methods=["GET", "POST"])
def index():
if request.method == "GET":
return render_template("index.html")
else:
print("Form submitted!")
color = request.form.get("color")
return render_template("color.html", color=color)
This example has some logic in it: if the user submits a GET request, we’ll show them the basic index.html
with a form to submit. If, though, they submit a POST request, we’ll do a few different things:
print
“Form submitted!” to the terminal, for debugging’s sake. We should later remove this once we’re satisfied our program works as intended.- Store the value of the “color” input in a variable also called
color
, as viacolor = request.form.get("color")
. - Render a new HTML file,
color.html
, and pass the value of thecolor
variable into the file under the very same name,color
.
Let’s create color.html
for our application to work:
{% extends "layout.html" %}
{% block body %}
<p style="color: {{ color }}">Your favorite color is {{ color }}.</p>
{% endblock %}
Notice how we can insert the value of color
anywhere in our HTML we’d like—whether as text in a sentence, or as a placeholder for the value of some CSS property.
We’ll likely want to add some additional input validation, however, instead of simply trusting our users to send only the values “blue” or “red” via POST. What additional code could you add to ensure the colors we receive are valid colors?
How to Test Your Code
Your program should behave as per the examples above.
No check50
for this one!
How to Submit
No need to submit. This is a practice problem!
Adapted from 27 Classrooms, by Mark Sobkowicz, Lincoln Sudbury Regional High School