owned this note
owned this note
Published
Linked with GitHub
# Server side Web programming with flask and jinja2
[toc]
Titus Brown, ctbrown@ucdavis.edu. This text is under CC0, and the code is under BSD 3-clause.
## How the Web works
### Anatomy of a Web request & response

### Browser sends a request:

### Server sends a response:

### Fun facts

## Building a Web server
See https://github.com/ctb/2025-davis-pug-web-intro-flask, and the quickstart instructions in the README, for the example application we'll be going through.
In brief, this application:
* shows how to implement a basic Web application + index page in flask;
* shows how to serve static content, too;
* implements some minimal jinja2 templating;
* uses a form GET;
* uses a form POST;
* adds CSS;
* uses jinja2 templating to do variable interpolation;
* uses jinja2 templating to do a for loop;
* installs a custom jinja2 filter to display Markdown;
## The basics
[Here](https://github.com/ctb/2025-davis-pug-web-intro-flask/blob/basic_only/main.py) is the most basic Flask app: it just creates an app object, and adds a single route that connects a URL to a function. Here we also take advantage of flask's default static file handling that (implicitly) routes '/static' requests to files in the `static/` directory.
The key code is here:
```python!
@app.route('/', methods=['GET'])
def index():
return """hello, world!
<p>
<img src='/static/vase.jpg'/>
<br>
See: <a href='https://commons.wikimedia.org/wiki/File:33344-Mei%C3%9Fen-2008-Mei%C3%9Fner_Bleikristall_(Vase_und_Burg)-Br%C3%BCck_%26_Sohn_Kunstverlag.jpg'>source for vase image</a>
"""
```
Key points:
* any other URL than '/' errors;
* the image request is to a _separate_ URL;
## Forms and templates
[Here](https://github.com/ctb/2025-davis-pug-web-intro-flask/blob/forms_and_templates/main.py) is a slightly more advanced Web app, that does a number of things to get information from the client side.
### Custom routes with variables in them
One way to send information from the browser is by constructing URLs carefully. Flask has a nice interface for converting URLs into Python function calls; see below, where `/user/somename` rsults in a call `user_fn('somename')`
```python!
@app.route('/user/<username>')
def user_fn(username):
# here you would do some kind of validation for logins, etc.
return f"this is the user page for user '{username}'"
```
### Presenting a form; using templates
This code:
```python!
@app.route('/example_form', methods=['GET'])
def example_form():
return render_template('example_form.html')
```
says, "use jinja2 to find and render the `example_form.html` template". If you look at [templates/example_form.html](https://github.com/ctb/2025-davis-pug-web-intro-flask/blob/forms_and_templates/templates/example_form.html) you'll see:
```html
<h1>Example form</h1>
<form method='GET' action='/example_form_process'>
Input a number: <input type='text' name='the_number'></input>
<input type='submit'>
</form>
<a href='./'>Return to index</a>
```
which is just straight HTML.
This form says, "submit a value called 'the_number' to the URL `/example_form_process`". It's up to the Web app to say what to do with that value!
### Interpreting values submitted via a GET
Form values submitted via GET are available in the `request.args` dictionary; the rest is just Python.
```python!
@app.route('/example_form_process', methods=['GET'])
def example_form_process():
value = request.args.get('the_number')
if value is None:
return redirect(url_for('example_form'))
else:
values=dict(the_number=value)
return render_template('example_form_process.html',
**values)
```
### Interpolating Python values in a template
Above, you'll note:
```python!
values=dict(the_number=value)
return render_template('example_form_process.html',
**values)
```
which says, render the template, with the option to use the values in the passed in dictionary - here, obtained from the form!
The template code itself is in [templates/example_form_process.html](https://github.com/ctb/2025-davis-pug-web-intro-flask/blob/forms_and_templates/templates/example_form_process.html) and looks like this:
```htmlembedded!
Form value was: {{ the_number }}
```
Here, `{{` and `}}` tell Jinja2 that you want to interpolate a Python value - in this case, `the_number`.
Note that the GET values show up in the URL, after the `?`.
### Presenting a POST form; checking values
Let's look at another form - this time a POST form.
The [template code](https://github.com/ctb/2025-davis-pug-web-intro-flask/blob/forms_and_templates/templates/example_form2.html) is just straight HTML, but a bit more complicated than before:
```html!
<h1>Example form 2</h1>
Let's generate a table of every number between 0 and some upper limit,
multiplied by a custom number.
<p>
<form method='POST' action='/example_form_process2'>
Upper limit: <input type='text' name='upper_limit' value='10'></input>
Multiply by: <input type='text' name='multiply_by' value='4'></input>
<input type='submit'>
</form>
<a href='./'>Return to index</a>
```
Note the `POST` instead of the `GET`. This isn't particularly important here - we could do this with a GET! - but it becomes important when you're accepting files, or other arbitrarily large inputs.
The form processing code is here and is now a bit more complicated, because it does error checking:
```python!
@app.route('/example_form_process2', methods=['POST'])
def example_form_process2():
upper_limit = request.form.get('upper_limit')
multiply_by = request.form.get('multiply_by')
# error handling...
try:
upper_limit = int(upper_limit)
multiply_by = int(upper_limit)
except ValueError:
upper_limit = None
multiply_by = None
if upper_limit is None or multiply_by is None:
return redirect(url_for('example_form2'))
else:
values=dict(upper_limit=upper_limit,
multiply_by=multiply_by)
return render_template('example_form_process2.html',
**values)
```
### Using for loops in a template
The [template file](https://github.com/ctb/2025-davis-pug-web-intro-flask/blob/forms_and_templates/templates/example_form_process2.html) is also more interesting here, because it does more of the work:
```html!
Multiplying every number between 0 and {{ upper_limit }} by {{ multiply_by }}.
<p>
<table border=1>
{% for i in range(0, upper_limit) %}
<tr><td>{{ i }}</td><td>{{ multiply_by * i }}</td>
{% endfor %}
</table>
```
Here we're using a jinja2 for loop to produce a table. Let's look at the output HTML in the browser...
## Making things pretty; more advanced jinja2 stuff
Here is our final Web app: [link](https://github.com/ctb/2025-davis-pug-web-intro-flask/blob/more_jinja2/main.py). Let's look at the new stuff!
### Installing and using jinja2 filters
Jinja2 lets you install new functions, implemented in Python. [Here's one](https://github.com/ctb/2025-davis-pug-web-intro-flask/blob/more_jinja2/main.py#L7C1-L8C31) that makes a number into a percent:
```python!
def percentify(num):
return f"{num * 100:.1f}%"
```
and you can install it for Jinja2 use like so:
```python!
# run this function once, to create the Flask applicaton.
def create_app():
app = Flask(__name__)
with app.app_context():
# do any configuration stuff here, including adding jinja2 filters.
# add percentify filter
app.jinja_env.filters['percentify'] = percentify
return app
```
and you can use it in [a template](https://github.com/ctb/2025-davis-pug-web-intro-flask/blob/more_jinja2/templates/example_filter.html) like so:
```html!
<h1>Example 'percentify' filter</h1>
{{ val|percentify }}
```
### A markdown filter
My favorite filter so far is one that just takes Markdown and turns it into HTML. That way you can have [a file that you write in Markdown & renders on GitHub](https://github.com/ctb/2025-davis-pug-web-intro-flask/blob/more_jinja2/templates/example.md) but also is part of your Web app.
Python code implementing the `markdownify` filter:
```python!
def markdownify(md):
return markdown.markdown(md)
```
The template code is then easy:
```htmlembedded!
{% include "_header.html" %}
{% filter markdownify %}
# This is some markdown!
Add your **markdown content** here!!
{% endfilter %}
{% include "_footer.html" %}
```
### Making more use of jinja2
We can also make our HTML more pretty. I hate writing HTML more than once, but it's pretty easy to define files that you just include - take a look at our new [example form](https://github.com/ctb/2025-davis-pug-web-intro-flask/blob/more_jinja2/templates/example_form.html) code:
```
{% include "_header.html" %}
<h1>Example form</h1>
<form method='GET' action='/example_form_process'>
Input a number: <input type='text' name='the_number'></input>
<input type='submit'>
</form>
<a href='./'>Return to index</a>
{% include "_footer.html" %}
```
here [`_header.html`](https://github.com/ctb/2025-davis-pug-web-intro-flask/blob/more_jinja2/templates/_header.html) is just stuff we want at the beginning:
```html!
<html>
<head>
<title>Example Web app in flask</title>
<link rel="stylesheet" href="/static/pico.min.css"/>
</head>
<body>
<main class="container">
```
and the same for the footer:
```html!
</main>
</body>
</html>
```
Then, any changes we make to these files automatically get propogated across the site, as long as the templates use the right include.
Note that here I'm using the [pico CSS library](https://picocss.com/docs) and serving up `pico.min.css` in [the `static/` directory](https://github.com/ctb/2025-davis-pug-web-intro-flask/tree/more_jinja2/static).