Implementing my Website in Flask - Part 3

Oct 21, 2021

This is part 3 in a series about implementing a personal blog in Python. In this post, I'd like to cover the basics of serving a website with Python, particularly its really lightweight Flask framework, which I've decided to use for this project.

If you're interested, other parts of this series can be found below:

As always, you can find the source of this blog on Github.

Introduction

In my search for the most well supported and well liked Python web frameworks, there were a few that always topped the chart. In the top three most popular on every tutorial I found were always Flask and Django. Flask is marketed as a fast, lightweight "micro" framework that provides just enough to be productive and then "gets out of your way." Django is marketed as "fully loaded" with features in order to handle data-driven websites.

I decided to go with Flask because I knew my website would be simple. I didn't even expect to have a database, so Django seemed like overkill. I think I made the right decision, even if it was just so I wasn't overwhelmed learning all of the features of Django.

The Basic Flask App

I was surprised at how little code is actually needed in order to get a simple app working:

1
2
3
4
5
6
7
8
9
from flask import Flask
app = Flask(__name__)

@app.route('/')
    def index():
        return 'Hello World!'

if __name__ == '__main__':
    app.run()

Then you can run it from the command line like this:

$ python hello.py
* Running on http://localhost:5000/

... and that's it!

In this case, app is the web application instance, created as an instantiation of Flask. Line 4 tells the app that any requests to relative URL "/" should be served the string Hello World!. Of course, this wouldn't include any style information or anything, but Flask does include a default 404 page if any other URLs are requested.

Lines 8 and 9 just tell Flask to run the development server if the script is called directly (opposed to being imported as a module).

Flask Templates

If you'd like to serve real HTML instead of simple strings, Flask provides a couple options, the most straightforward being the render_template method. Most of my URLs are handled this way. The basic usage looks like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import flask
app = flask.Flask(__name__)

@app.route('/')
    def index():
        context = { 'content': 'Hello World!' }
        return flask.render_template('index.html', context)

if __name__ == '__main__':
    app.run()

The first argument in render_template is the template Flask will use to render the content of the page (more on that in a second). The second variable is the 'context', or a dict of all the variables that should be available to the template.

Of course, there's no real benefit to throwing Python into the mix if a website is completely static and unchanging. In that case, its possible to just define the HTML and CSS to be standalone. But if the website is based on data that can change regularly, the HTML will need to be changed on the fly between requests based on the latest data. That's where Python comes in.

To dynamically render HTML, Flask is dependent on the Jinja template engine and its handy HTML templates.

Jinja templates look really similar to HTML, just with some new Python-like syntax thrown in where content needs to be dynamically rendered. Here's an example for the hypothetical Hello World website from earlier:

1
2
3
4
5
6
7
8
9
<!DOCTYPE html>
<html lang='en'>
  <head>
    <title>Hello World!</title>
  </head>
  <body>
    <h1>{{ content }}</h1>
  </body>
</html>

The string between the h1 tags on line 7 is actually referring to the content key of the context dict that was passed as the second argument to render_template in the previous example. Whatever value is stored for the content key (in this case, 'Hello World!') will be substituted between the h1 tags.

Jinja also provides syntax for conditionals and control flow. For example, if a block of HTML should be conditionally included, the {% if %} syntax can be used. An example from this website would be the Next/Prev page navigation links at the bottom of the index page. The code for the Next link looks like this:

1
2
3
4
5
{% if next_page is none %}
  <span>Next</span>
{% else %}
  <a href='/page/{{ next_page }}/'>Next</a>
{% endif %}

This example first checks if there's a valid next page of posts. If that's the case, the Next button is rendered as a link. If not, the Next button still looks the same, but it's rendered as a span so the user can't select it.

Jinja also provides more complex expressions to use in conditionals. For the complete list, see the documentation.

Along with conditionals, Jinja also provides loops. They use a very similar syntax to Python loops. Just like conditionals, they are specified within {% %} blocks. An example from this blog is again from the index page, where I show the latest blog posts:

1
2
3
4
5
6
{% for post in posts %}
  <article>
    {{ post }}
  </article>
  <hr>
{% endfor %}

Jinja then repeats the pattern for each post that the Flask application provides to the template, wrapping each post in an article tag and adding a horizontal line underneath. This can be really helpful when maintaining repetitive pages. Instead of having to copy/paste multiple times, I can just specify the pattern once and Jinja repeats it for each iteration of the loop.

Another great feature of Jinja templates are their ability to extend other templates. For example, each one of my pages includes roughly the same information in the HTML head, the navigation area, and the footer. It would be a real pain to need to maintain them separately for each page. Instead, I can add these features once to a template called base.html:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE html>
<html lang='en'>
  <head>
    <title>{{ title }}</title>
  </head>
  <body>
    <nav>
      <h1>{{ blog-title }}</h1>
      <p>{{ blog-caption }}</p>
      <div>
        <h3><a href='/about'>About Me</a></h3>
        <h3><a href='/archive'>Archive</a>
      </div>
    </nav>
    <main>
      {% block content %}
      {% endblock %}
    </main>
    <footer>
      <p><strong>{{ blog-title }}</strong></p>
      <p>Copyright © 2021 All Rights Reserved</p>
      <br/>
    </footer>
  </body>
</html>

Then any template can 'extend' this base template. The key here is the two lines on 16 and 17. This notifies Jinja that the content block has not been defined yet, and it should look for other templates which define content. Note that content is not a reserved word or anything. {% block example %} would work exactly the same, as long as another template actually defined what was in the example block.

To define the content block, create another template called posts.html:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{% extends '_base.html' %}
{% block content %}
  <h2>Latest Posts</h2>
  {% for post in posts %}
    <article>
      {{ post }}
    </article>
    <hr>
  {% endfor %}
{% endblock %}

Now, the contents of each post will be substituted for {% block content %} in the rendered HTML any time render_template is called for posts.html.

One last Jinja templates feature I found myself using a lot was the {% include %} tag. This substitutes the contents of the included template in place of the tag. This allows larger templates to be a composition of smaller, more modular ones. I found this particularly useful when adding the social media buttons to the header and footer. Since both use the same code, it made sense to just factor it out into its own template. Then it can be included into the footer like this:

1
2
3
4
5
6
7
8
9
<footer>
  <p><strong>{{ blog-title }}</strong></p>
  <p>Copyright © 2021 All Rights Reserved</p>
  <br/>
  <p><strong>Want to get in touch?</strong></p>
  <div>
    {% include 'socialmedia.html' %}
  </div>
</footer>

(Note the {% include %} on line 7)

How I Chose My Templates

Each of my page types are defined as a template, based off of the base template. For example, the 404 page, about page, archive page, etc all are their own templates. Then, any features that I found myself using in multiple places are defined as their own template to be {% include %}'ed. The actual blog posts are a special case and also designed to be {% include %}'ed.

Here's a picture describing the relationships between templates. Note the arrows pointing to the left are inheritance ({% extends %}). The arrows pointing to the right are composition ({% include %}).

Relation of templates.

Flask Design Patterns I Implemented

While the basic Flask app at the beginning of this post is impressive at how little code is actually needed, it isn't scalable, even for something as simple as a blog.

I knew that the application would need multiple Python modules, so I followed what's called the application factory design pattern. This made it so that I wouldn't have to rely on access to a global variable in order to access my Flask instance. Instead, I could use the flask.current_app property to access my Flask instance.

This pattern also provides access to the flask command line tool. I could now run my app with the following commands:

$ export FLASK_APP=src
$ export FLASK_ENV=development
$ flask run

I could also add further CLI tools for my app which could be accessed like flask run.

For example, the 'build' tool (which I'll cover in a later blog post) can be defined with the following code:

1
2
self.app = flask.Flask(__name__)
self.app.cli.add_command(cli.build)

...and then accessed from the command line like this:

$ export FLASK_APP=src
$ export FLASK_ENV=development
$ flask build

My application factory didn't turn out exactly as the pattern specifies in the Flask documentation. I wanted to keep my application as object-oriented as possible, so my __init__.py looks like this, instead of containing the actual creation of the flask instance:

1
2
3
4
5
def create_app():
    """ Create and configure blog app """
    config = Settings.instance()
    blog = Blog(config)
    return blog.app

Then, my flask instance is created within the Blog class as Blog.app. Note that the Settings.instance() above is another module in my application which defines configurations like relative routes, the name of the blog, etc.

The Blog constructor needs to look like this to provide create_app with the configured Flask instance:

1
2
3
4
5
6
7
8
9
class Blog:

    def __init__(self, settings):
        self.app = flask.Flask(__name__)
        self.app.cli.add_command(cli.build)

        @self.app.route('/')
        def index():
...

Note that I elected to define all the routes within the Blog constructor. This is similar to the application factory pattern shown in the Flask tutorial, just with all of the Flask-specific stuff wrapped in my Blog class.

Unlike the simple Flask app at the beginning of this post, there are several URLs which need to be dynamically generated, like those associated with blog posts. In those cases, instead of using hard-coded routes, I used Flask's dynamic URL processing feature. These are specified in angle brackets, like the routes of the two methods below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@self.app.route('/')
@self.app.route('/page/<int:page>/')
def index(page=1):
    """ Creates the index page with latest blog posts """
    pass
    
@self.app.route('/post/<name>/')
def blog_post(name=None):
    """ Creates a blog post page """
    pass

This feature is really useful. In the case of the first method, it passes the value stored in page as the first parameter to the method. If a page is not specified (like in the default route /), the page will default to page 1. The int specifier before page tells Flask to only accept integer values here. If a string is passed instead, Flask will render the 404 page.

In the second method on line 7, the name parameter can be any string since there is no specifier in front. It is worth mentioning that Flask escapes these strings and does its best to ensure that there are no characters which may cause a security problem.

In both examples, once Flask checks that the URL matches the template URL, it's up to my application to verify whether page is a valid page and name is a valid blog post.

Since my blog is pretty simple, I didn't use many other advanced Flask design patterns that are mentioned in the documentation. One thing that I regret not implementing, though, is Blueprints. These provide a very modular approach that would have been easier to test and scale.

Instead, I just stuck everything in my Blog class. Initially, this worked great. But as it (unexpectedly) grew, it became a little unwieldy. One thing that I noticed as well was that many of my routes were really similar and ended up sharing data, while others were completely distinct. These lines of shared data would have provided a great boundary to break certain features into new blueprints.

Oh well. If I find myself adding a new feature any time soon, I might roll in blueprints along with it...

That covers everything that I wanted to cover in Flask! Next post, I'd like to discuss what went into actually building the context that Flask renders with each template.