Skip to content
Phil Kirlin edited this page Mar 29, 2022 · 19 revisions

Using the Python PostgreSQL interface with HTML templates

In this lesson we will build a nicer view of our blog entries, rather than the "dump" output which is ugly.

First, let's create a new HTML template (our first one for the blog!). Inside the templates/ subfolder, create a file called browse.html:

<html>
    <head>
        <title>Browse entries</title>
    </head>
<body>
    <h1>Browse entries</h1>
    {% for entry in entries %}
        Title: {{ entry.title }}<br>
        Date: {{ entry.date }}<br>
        Content: {{ entry.content }}<p>
    {% else %}
        This blog is empty.
    {% endfor %}
</body>
</html>

To be clear, our directory structure now looks like

Files
+-- main.py
+-- schema.sql
+-- populate.sql
+-- templates/
    +-- browse.html

Let's also add a new route to our main.py code:

@app.route("/browse")
def browse():
    conn = get_db()
    cursor = conn.cursor()
    cursor.execute('select id, date, title, content from entries order by date')
    rowlist = cursor.fetchall()
    return render_template('browse.html', entries=rowlist)

Test this out

Use a browser to visit YOUR-URL/browse. You should see your blog entries!

IMAGE HERE

Breaking this all down

The Flask route for /browse isn't all that different than /dump, though we will move the processing of the SQL rows to the HTML template. Here, we connect to the database, execute the query, turn the rows iterator into a list, and send that list to render_template. The only new thing is we've never seen render_template called with a keyword argument other than a simple integer or string. Here, rowlist is a list of SQL rows. So how does the template handle this? Let's flip to the HTML template and see.

In the HTML template, notice the main logic is a for loop.
Inside the loop, we mingle HTML code with variable insertions using double curly braces. The entry variable is a dictionary, and you can do lookups in three different ways (with a dot or with square brackets, single or double braces). They all work the same way, just pick your favorite and stick with it.

Some explanation is warranted for the else part after the for loop. You may have not seen else attached to a for loop before. What this means is that if the for loop never executed (there were zero entries), then the else block runs. If the for loop runs at least once, the else block is skipped. So we use the else block to print out a message saying the blog is empty.

Testing the empty blog case.

To see what happens when the blog is empty, click the "stop" button to stop the web app. Then in the shell, type flask initdb to reset the database. Then restart Flask with the green run button. If you visit YOUR-URL/browse, you should see the empty blog message. Now go back to the command line and re-populate the database with flask populate. Back in the web browser, reloading will bring back the list of blog entries.

Enhancing formatting

We can use an unordered list to format our entries more nicely. The HTML syntax (as a reminder) looks like

<ul>
  <li>first item
  <li>second item
  <li>third item
</ul>

This will be easy to add to our template; we just need to put <li> in front of each blog entry inside the for loop. There is a caveat, however: where do we put the <ul> and </ul> tags? We can't put them inside the for loop because then they would get printed once for every blog entry. Instead, what we'll do is add an if test that detects whether the entries variable has any rows in it:

<html>
    <head>
        <title>Browse entries</title>
    </head>
<body>
    <h1>Browse entries</h1>
    {% if entries|length > 0 %}
        <ul>
        {% for entry in entries %}
            <li>Title: {{ entry.title }}<br>
            Date: {{ entry.date }}<br>
            Content: {{ entry.content }}<p>
        {% endfor %}
        </ul>
    {% else %}
        This blog is empty.
    {% endif %}
</body>
</html>

You try it!

Modify the HTML template so that the blog entries look like the formatting below:

Specifically, make the titles bold, put the date in parentheses, put the content below with a blank line in between, and separate the entries with a horizontal rule (<hr>). Do not worry about formatting the dates nicely (they won't look exactly like what you see above, but that's ok).

Check below for the answer.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

<html>
    <head>
        <title>Browse entries</title>
    </head>
<body>
    <h1>Browse entries</h1>
    {% if entries|length > 0 %}
        <ul>
        {% for entry in entries %}
            <li><b>{{ entry.title }}</b> ({{ entry.date }})
            <p>
            {{ entry.content }}
            <hr>
        {% endfor %}
        </ul>
    {% else %}
        This blog is empty.
    {% endif %}
</body>
</html>

Use the main wiki page to navigate, not the list of pages directly above, because those are out of order.

Clone this wiki locally