Dynamic website templates with Flask and Jinja2

Last month, we looked at using Python and Flask to handle the Twitter OAuth process and build requests to obtain tokens. We’ve used Twitter in these tutorials because of the large amount of easily digestible data available. However, since Twitter adheres to the standards set out by OAuth 1.0, the code we’ve used to sign and build requests can be modified to work with any third- party API using the same standard without a great deal of work. For years PHP has been a mainstay of template generation, but now with well-documented frameworks such as Flask, Sinatra and Handlebars, the ability to use powerful scripting languages greatly improves our ability to make great web services.

To continue on from last time, we’re going to use Python, Flask and its templating engine to display tweets. Flask comes with the super-nifty Jinja2 templating engine, If you’re familiar with Node.js or front-end JavaScript, the syntax will look very similar to the Handlebars rendering engine. But, before we dive into that, we need to rearrange some of the code we wrote last time.

The template uses a loop to generate a list of tweets

Resources

Step-by-step

Step 01 Rearranging our code

Server code can get messy and unmaintainable quickly, so the first thing we’re going to do is move our helper functions to another file and import them into our project, much like you would a module. This way, it will be clear which functions are our server logic and endpoints and which are generic Python functions. Open your server.py file (or whatever you decided to call it) that we wrote in our last tutorial and locate the getParameters, sign_request and create_oauth_headers functions. Cut and paste them into a new file called helpers.py in the root of your project folder. At the top of this file we want to import some libraries.

Now we can head back over to server.py and import the helper functions back into our project. We do this by simply calling import helpers. Because Python is smart, It will look in the current directory for a helpers.py file before it looks for a system module. Now every function included in helpers.py is accessible to our project. All we need to do to call them is prepend our the methods we called before with helper.function_name and it will execute. For sign_request, we’ll need to pass our oauth_secret and consumer_secret for each request rather than accessing it from the session. Adjust the function declaration like so:

With a lot of the modules needed in this project having been moved to helpers.py, we can now remove most of them from server.py. If we amend our first import statement to be…

import urllib2, time, random, json

…our project will continue to function as it did before. Note the addition of the json module: we’ll be using that later as we start handling Twitter data.

Having Flask use a rendering engine is super-simple. Flask comes packaged with the Jinja2 template rendering engine, so we’ve nothing to install – we just need to import the package into the project. We can do this by adding render_template to the end of our from flask import […]statement.

Step 03 Our first template

Now that we have a rendering engine, we need to create some templates for it to use. In the root of our project’s folder, create a new folder called templates. Whenever we try to render a template, Flask will look in this folder for the template specified. To get to grips with templating, we’ll rewrite some of our authentication logic to use a template, rather than manually requesting endpoints. In templates, create an index.html file. You can treat this HTML file like any other – included in the resources for this tutorial is an index.html that includes all of the necessary head tags and <!DOCTYPE> declarations for this file.

Step 04 Rendering our template

In server.py, let’s create a route for ‘/’ to handle the authorisation process.

It’s a simple function: all we want to do is check whether or not we have an oauth_token already and create those properties in the Flask session so we don’t throw an error if we try to access it erroneously. In order to send our generated template in response to the request, we return render_template(‘index.html’).

Step 05 Template variables

We can choose to send variables to our template with render_template(‘index.htm’, variableOne=value, variableTwo=Value) but in this instance we don’t need to as each template has access to the request and session variables.

Open index.html. All code executed in a Flask template is contained within {% %}. As this is our homepage, we want to direct users accordingly, So let’s check if we’ve got an access token:

Between the ifs and else of the template is standard HTML. If we want to include some data – for example, the access token – we can just add {{ session[‘oauth_token’] }} in the HTML and it will be rendered in the page. Previously, in our / authorised endpoint, we would display the OAuth token that we received from Twitter; however, now that we have a template, we can redirect our users back our root URL and have a page rendered for us that explains the progress we’ve made.

Step 06 Getting lost (and then found again)

With every server, some things get misplaced or people get lost. So how do we handle this? Rather than defining a route, we can define a handler that deals with getting lost.

If a page or endpoint is requested and triggers a 404, then the fourOhFour function will be fired. In this case, we’ll generate a template that tells the user, but we could also redirect to another page or dump the error message.

Step 07 Static files

Pretty much every webpage uses JavaScript, CSS and images, but where do we keep them? With Flask we can define a folder for use with static content. For Flask, we create a static folder in the root of our project and access files by calling /static/css/styles.css or /static/js/core.js. The tutorial resources include a CSS file for styling this project.

Step 08 Let’s get some tweets

So now we know how to build templates, let’s grab some tweets to display. In server.py define a new route, get-tweets,like so:

You’ll notice that unlike our other authentication endpoints, we’ve made two declarations. The first is a standard route definition: it will intercept and handle the path get-tweets. The second lets us define a parameter that we can use as a value in our getTweets function. By including count=0 in our function declaration, we ensure that there will always be a default value when the function is executed; this way we don’t have to check the value is present before we access it. If a value is included in the URL, it will override the value in the function. The string inside the <variable name> determines the name of the variable. If you want the variable passed to the function to have a specific type, you can include a converter with the variable name. For example, if we wanted to make sure that <count> was always an integer instead of a float or string, we’d define our route like so:

@app.route(‘/get-tweets/<int:count>’)

Step 09 Checking our session and building our request

Before we start grabbing tweets, we want to run a quick check to make sure we have the necessary credentials and if not, redirect the user back the authorisation flow. We can do this by having Flask respond to the request with a redirection header, like so:

You’ll notice that the nonce value is different from that in our previous requests. Where the nonce value in our authenticate and authorise requests can be any random arrangement of characters that uniquely identify the request, for all subsequent requests the nonce needs to be a 32-character hexadecimal string using only the characters a-f. If we add the following function to our helpers.py file, we can quickly build one for each request.

Before we create the authorisation headers, we need to remove the count and user_id values from the tweetRequestParams dictionary, otherwise the signature we just created won’t be valid for the request. We can achieve this with the del keyword. Unlike our token requests, this request is a GET request, so instead of including the parameters in the request body, we define them as query parameters.

Now we’re ready to fire off the request and we should get a JSON response back from Twitter. This is where we’ll use the json module we imported earlier. By using the json.loads function, we can parse the JSON into a dictionary that we can access and we’ll pass through to our tweets.html template.

Whereas before, we accessed the session to get data into our template, this time we’re explicitly passing a value through to our template.

Step 12 Displaying our tweets

Let’s create that template now, exactly the same as index.html but this time, instead of using a conditional, we’re going to create a loop to generate a list of tweets we’ve received.

First, we check that we actually received some data from our request to Twitter. If we’ve got something to render, we’re ready to work through it, otherwise we’ll just print that we didn’t get anything.

Once again, any template logic that we want to use to generate our page is included between {% %}. This time we’re creating a loop; inside the loop we’ll be able to access any property we have of that object and print it out. In this template we’re going to create an <li> element for each tweet we received and display the user’s profile picture and text of the tweet:

In our template we can access properties using either dot notation (.) or with square brackets ([]). They behave largely the same; the [] notation will check for an attribute on the dictionary or object defined whereas the . notation will look for an item with the same name. If either cannot find the parameter specified, it will return undefined. If this occurs, the template will not throw an error, it will simply print an empty string. Keep this in mind if your template does not render the expected data: you’ve probably just mis-defined the property you’re trying to access.

Unlike traditional Python, we need to tell the template where the for loop and if/ else statements end, so we do that with {% endfor %} and {% endif %}.

Step 13 Flask filters

Sometimes, when parsing from JSON, Python can generate erroneous characters that don’t render particularly well in HTML. You may notice that after tweet[‘text’] there is |forceescape, This is an example of a Flask filter; it allows us to effect the input before we render – in this case it’s escaping our values for us. There are many, many different built- in filters that come included with Flask. Your advisor recommends a full reading of all the potential options.

Step 14 Wrapping up

That’s pretty much it for templating with Flask. As we’ve seen, it’s insanely quick and easy to build and deploy dynamic sites. Flask is great tool for any Python developer looking to run a web service. Although we’ve used Twitter to demonstrate Flask’s power, all of the techniques described can be used with any third-party service or database resource. Flask can work with other rendering engines, such as Handlebars (which is superb), but Jinja2 still needs to be present to run Flask and conflicts can occur between the two engines. With such great integration between Flask and Jinja2, it makes little sense to use another engine outside of very specific circumstances.