Flask Web Apps
Flask web framework
flask is a Python package which can help you to:
- quickly create dynamic web applications
- add functionality with many extensions available
- scale up in complexity when required
It is a lightweight web application framework which is popular, flexible, and easy to get started with.
'Hello, World' example
Take a look at this repl to see how we can serve a web page with Flask:
from flask import Flask
app = Flask(__name__)
- we imported the
Flaskclass definition, and assigned an instance of it toapp
Things we don't need to worry about for now, but:
__name__is a special Python variable which determines where Flask should look for files such as templates__main__will be the value of__name__if this file is itself being run directly rather than by another file
@app.route('/')
- this is a decorator, which acts as a 'wrapper' around the subsequent function
- in short this tells Flask to use the function to determine what will be shown at the 'root URL', i.e. mysite.com
/
def index():
return 'Hello, World'
- our function returns some text, which is shown on the web page
- it could be called anything, but usually
indexis used for the root URL
if __name__ == '__main__':
app.run(host='0.0.0.0', debug=True)
__name__will equal__main__because we are running the file directly (unimportant for now)- the
.run()method ofappwill serve the page at the givenhost(required for replit) debug=Truemeans we can see changes in the page without running the code again
Try changing the 'Hello, World' text and refreshing the page.
Jinja templates
jinja is a Python package which is used for templating. We can use it to:
- create reusable structures for web pages (such as the header and footer)
- combine blocks of content into a single document or web page
- conditionally include elements or specific data based on user inputs
The syntax used in Jinja templates is similar to Python.
Jinja syntax example
This code snippet is taken from the example we'll see later, showing a part of the template where rows in a template are created based on data in a list of dictionaries:
{% for activity in activities %}
<tr>
<th scope="row">{{ loop.index }}</th>
<td>{{ activity['activity'] }}</td>
<td>{{ activity['price'] }}</td>
</tr>
{% endfor %}
- we can see statements within
{% ... %}tags (here designating aforloop) - we can see expressions within
{{ ... }}tags (here looking up values in a dictionary)
Flask & Jinja templates
Rather than our page function to simply return text, we can use the flask.render_template() function to:
- return a jinja template instead to be displayed (which itself may incorporate other templates)
- pass Python data structures to be used by that template (such as lists and dictionaries)
This is a powerful combination which is central to making our web pages dynamic.
By default, Flask will look for templates in the /templates directory next to the application code.
Code walkthrough - Flask
Here's a Flask example which uses Jinja templating, including the code we've just seen:
site_name = 'Bucket List'
activities = []
votes = [0, 0, 0, 0, 0]
for i in range(5):
resp = requests.get('https://www.boredapi.com/api/activity?type=recreational')
activities.append(resp.json())
- we've set
site_nameto a string, which we'll use in our web page - we've used
requests.get()as seen previously to collect data from an API - in this case, the Bored API, which doesn't require credentials
activitiesnow has a list of dictionaries containing data for our pagevotesis a list of values which we can subsequently modify
Using request
flask.request allows us to determine how a web page we've served with Flask was requested
- this isn't to be confused with the
requestspackage we just used to fetch data
... but the terminology is the same, i.e.GETandPOSTrequests
@app.route('/', methods=['GET', 'POST'])
- the
methodsparameter determines which types ofrequestfor the givenroutei.e. URL are allowed; by default this is onlyGET
def index():
if request.method == 'POST':
choice = int(request.form['choice'])
votes[choice] += 1
- if the page is served due to a
POSTrequest, we'll record the input by modifying ourvoteslist - if there's a
GETrequest, this will be skipped andvotesleft unmodified
Using render_template()
return render_template('index.html', site_name=site_name, activities=activities, votes=votes)
- we've called the
render_template()function, giving our template file as the first argument, followed by two keyword arguments containing our data to be used by the template - the variable names used in the Python logic are typically (but don't have to be) the same as those used in the Jinja templates
Code walkthrough - Jinja templates
Take a look at base.html:
<title>{{ site_name }}</title>
<body>
{% block content %}
{% endblock %}
</body>
- the value passed for
site_namewill be used as thetitle(shown in the browser tab) - any templates which extend this template can insert a block labelled
contentin the given location _
The rest of base.html is simply a 'boilerplate' or 'blank canvas' for using the Bootstrap CSS framework:
- the viewport metadata ensures the page will be responsive
- the Bootstrap CSS is imported from a CDN (Content Delivery Network)
Blocks and extensions
Take a look at index.html:
{% extends "base.html" %}
{% block content %}
...
{% endblock %}
-
the
{% extends ... %}statement tells Jinja to usebase.htmlas a starting point for this template
... placing what's between the{% block ... %}and{% endblock ...%}tags in the position with the same label (herecontent) -
there could be several such blocks within
base.htmlandindex.html, using different labels
Loops and dictionaries
{% for activity in activities %}
<tr>
<th scope="row">{{ loop.index }}</th>
<td>{{ activity['activity'] }}</td>
<td>{{ activity['price'] }}</td>
...
</tr>
{% endfor %}
- as we saw earlier,
{% for...%}and{% endfor... %}designate loops to be completed for each item in a list - we can use the same syntax as Python for getting values from dictionaries:
dictionary['key'] loop.indexreturns the current iteration count; note that this isn't zero-indexed, and the first value in the table is1- zero-indexed values can be obtained using
loop.index0
- zero-indexed values can be obtained using
Forms and POST requests
<td>
<form action="." method="POST">
<button type="submit" class="btn btn-outline-primary" value="{{ loop.index0 }}" name="choice">
<span class="badge badge-light">{{ votes[loop.index0] }}</span>
</button>
</form>
</td>
- in the last column of the table, we use a
formwith amethodattribute ofPOST - the
actionattributes tells the browser where to send the data, with.meaning 'the current page' - notice how the
nameandvalueattributes of thebuttonmatch with our logic in theindexfunction