Catching up

If you haven't already, please run the self-check script:

~cs304/pub/bin/check-my-account

If that shows errors, you may have trouble with today's activities, not to mention the problem sets.

You should also copy the folder from last time if you don't have a copy:

cd ~/cs304/ 
cp -r ~cs304/pub/downloads/flask2 flask2

Templating

Let's return to the end of last class and discuss Jinja2, template inheritance and static files. That will be in the flask2 folder.

Template Inheritance

Our first example is template inheritance.

The routes are /base/, /reading/ and /inclass/.

The files are:

  • example_template_inheritance.py shows an example of template inheritance. It refers to these templates:

    • templates/base.html
    • templates/reading.html
    • templates/inclass.html
  • and static/inheritance.css

Things to note:

  • the base temples define complete web pages, but include
  • blocks and their contents
  • the child templates just re-define the contents of blocks.
  • They don't have to re-define every block.
  • They don't do anything with the code outside blocks.
  • They can add holes to be filled by render_template()

GET vs POST

We talked about this in the context of forms, so we can skip this if you want.

One way to think about GET versus POST is that GET is like putting the request on a post card or the outside of an envelope: visible to all and limited in size. POST is like putting the request inside the envelope: less visible and unlimited in size.

Moreover, they have semantic differences:

  • GET is supposed to be used when reading information from the web server
  • POST is supposed to be used when updating information on the web server

These previous facts have some consequences:

  • With GET, since all the data is in the URL, and URLs aren't infinite in length, GET is unsuitable for long things, like submitting a blog post.
  • Because GET is for reading data, browsers and other computers can cache the results, rather that bothering the web server again.
  • Since all the data is in the URL, a GET request that retrieves something useful (some search results), the URL can be bookmarked, saved in a browser history, emailed to a friend, etc. Google maps used to do this, and it was useful for sending someone a particular map.
  • If there is sensitive information being submitting (e.g. SSN or credit card number), having the sensitive information end up in the URL by using the GET method should probably be avoided.
  • Because POST is for updating information, the browser will typically prevent you (with a warning) from re-submitting a form. This is a good thing. For example, you wouldn't want to accidentally resubmit an order to Amazon.com. But you should think about this. If re-submitting is harmless; don't use POST.

Example: One Route (ln)

Our next example is an import variation in which we use only one route. In this variation, we'll only submit data using POST (a price to pay), and we'll use GET to retrieve an empty form. This is pretty nice and avoids ugly URLs, though we can't re-submit without a warning.

Look at the code in example_form_processing_one_route.py. Related file is templates/ln.html. Let's look at that first.

<!doctype html>
<html lang='en'>
<head>
    <meta charset='utf-8'>
    <title>Natural Logs</title>
</head>
<body>

<h1>Compute a Natural Logarithm</h1>

{% if msg %}
<p>{{msg}}</p>
{% endif %}

{% if y %}
<p>The Natural Log of {{x}} is {{y}}</p>
{% endif %}

<form method="POST" action="">
    <p><label>Number to Logify
        <input name="x" type="number" step="any">
    </label></p>
    <p><input type="submit" value="POST"></p>
</form>

<h2>Menu</h2>
<form method="POST" action="">
    <p><label>Number (radicand)
        <select name="x">
            <option>Choose One</option>
            <option value="2.718281828459">e</option>
            <option value="7.389">e squared</option>
        </select></label></p>
    <p><input type="submit" value="submit"></p>
</form>

</body>
</html>
  • The ACTION is the empty string, defaulting to the current URL
  • We create optional places to put messages and the answer

And the corresponding Flask route in example_form_processing_one_route.py:

from flask import (Flask, url_for, render_template, request,
                   redirect, flash)
import math
from all_examples import app

@app.route('/ln/', methods=['GET','POST'])
def ln():
    if request.method == 'GET':
        return render_template('ln.html')
    else:
        x = request.form.get('x')
        try:
            y = math.log(float(x))
            return render_template(
                'ln.html',x=x,y=y)
        except:
             return render_template(
                 'ln.html',
                 msg=('Error computing ln of {x}'
                      .format(x=x)))
  • Now the ACTION is easier
  • There is only one route
  • We use GET to request an empty form
  • We use POST to request an answer

Example: POST-Redirect-GET (Countries)

We've seen the use of parameterized URLs to look up info. That's a GET request. We can answer a POSTed request using those GET requests, by using redirects.

Look at the code in example_post_redirect_get.py. This code refers to two templates, one of which we saw earlier, templates/msg.html. The other is the form, templates/city_form.html:

<!doctype html>
<html lang='en'>
<head>
    <meta charset='utf-8'>
    <title>Country Lookup</title>
</head>
<body>

<h1>Look up a Country</h1>

<h2>POST</h2>

<form method="POST" action="">
    <p><label>City
        <input type="text" id="city1" name="city">
    </label></p>
    <p><input type="submit" value="POST"></p>
</form>


<h2>Menu</h2>
<form method="POST" action="">
    <p><label>City
        <select id="city2" name="city">
            <option>Choose One</option>
            {% for city in cities %}
            <option>{{city}}</option>
            {% endfor %}
            </select></label></p>
    <p><input type="submit" value="submit"></p>
</form>

</body>
</html>
  • Note how the menu is generated from data!

Here are the Flask routes:

from flask import (Flask, url_for, render_template, request,
                   redirect, flash)
from all_examples import app
import countries

@app.route('/ask_city/', methods=['GET','POST'])
def ask_city():
    if request.method == 'GET':
        return render_template(
            'city_form.html',
            cities=countries.known.keys())
    else:
        city = request.form.get('city')
        # redirect is a new import from flask
        return redirect(url_for('country',city=city))

@app.route('/country/<city>')
def country(city):
    if city in countries.known:
        return render_template(
            'msg.html',
            msg=('{city} is the capital of {country}'
                 .format(city=city,
                         country=countries.known[city])))
    else:
        return render_template(
            'msg.html',
            msg=('''I don't know the country whose capital is {city}'''
                 .format(city=city)))
  • There's a nice division of labor between the two routes.
  • This technique also avoids certain anomalies when answer are cached, or retrieved again. See: POST/REDIRECT/GET.