Python Falcon - Jinja2 Template



The Falcon library is primarily used to build APIs and microservices. Hence, by default, a Falcon responder returns a JSON response. However, if the content type is changed to falcon.MEDIA_HTML, it is possible to render HTML output.

Rendering a HTML content with variable data is very tedious. For this purpose, web templating libraries are used. Many Python web frameworks are bundled with specific template library. But Falcon being a minimalist micro framework doesn't come pre-bundled with anyone.

Jinja2 is one of the most popular template libraries used by many python frameworks. In this section, we shall see how to use inja2 with Falcon application. The jinja2 is a fast and designer-friendly templating language that is easy to configure and debug. Its sandboxed environment makes it easy to prevent the execution of untrusted code, prohibit potentially unsafe data, and prevent cross-site scripting attacks (called XSS attacks).

Another very powerful feature of jinja2 is the template inheritance, wherein You can define a base template having common design features which child templates can override.

First of all, install jinja2 in the current Python environment with the use of PIP utility.

pip3 install jinja2

Hello World Template

The jinja2 module defines a Template class. A Template object is obtained by reading the contents of a file containing HTML script (one with .html extension). By invoking the render() method of this Template object, HTML response can be rendered to the client browser. The content_type property of Response object must be set to falcon.MEDIA_HTML.

Let us save the following HTML script as hello.py in the application folder.

<html>
   <body>
      <h2>Hello World</h2>
   </body>
</html>

Example

The on_get() responder in the resource class below reads this file and renders it as HTML response.

import uvicorn
import falcon
import falcon.asgi
from jinja2 import Template
class HelloResource:
   async def on_get(self, req, resp):
      resp.status = falcon.HTTP_200
      resp.content_type = 'text/html'
      fp=open("hello.html","r")
      tempobj=Template(fp.read())
      resp.body=tempobj.render()
app = falcon.asgi.App()
hello = HelloResource()
app.add_route('/hello', hello)
if __name__ == "__main__":
   uvicorn.run("hello:app", host="0.0.0.0", port=8000, reload=True)

Output

Run the above Python code and visit http://localhost:8000/hello link in the browser.

Jinja2

Template Variable

jinja2 is a server-side templating library. The web page is constructed as a template by putting various elements of jinja2 templating language as place-holders within appropriate delimiters inside the HTML script. The template engine reads the HTML script, substitutes the place-holders with context data on the server, reassembles the HTML, and renders it to the client.

The Template.render() function has an optional context dictionary parameter. The key attributes of this dictionary become the template variables. This helps in rendering the data passed by the responders in the web page.

Example

In the following example, the route /hello/nm is registered with the resource object, where nm is the path parameter. The on_get() responder passes it as a context to the template object obtained from a web page.

import uvicorn
import falcon
import falcon.asgi
from jinja2 import Template
class HelloResource:
   async def on_get(self, req, resp, nm):
      resp.status = falcon.HTTP_200
      resp.content_type = 'text/html'
      fp=open("hello.html","r")
      tempobj=Template(fp.read())
      resp.body=tempobj.render({'name':nm})
app = falcon.asgi.App()
hello = HelloResource()
app.add_route('/hello/{nm}', hello)
if __name__ == "__main__":
   uvicorn.run("hello:app", host="0.0.0.0", port=8000, reload=True)

The hello.html reads the path parameter in a template variable name. It acts as a place holder in the HTML script. It is put in {{ and }} symbols so that its value appears as a HTML response.

<html>
   <body>
      <h2>Hello {{ name }}</h2>
   </body>
</html>

Output

Run the Python code and enter http://localhost:8000/hello/Priya as the URL. The browser displays the following output −

Jinja2 Hello

Loop in jinja2 Template

If the responder passes any Python iterable object such as a list, tuple or a dictionary, its elements can be traversed inside the jinja2 template using its looping construct syntax.

{% for item in collection %}
HTML block
{% endfor %}

In the following example, the on_get() responder sends students object which is a list of dict objects, to the template list.html. It in turn traverses the data and renders it as a HTML table.

import falcon
import json
from waitress import serve
from jinja2 import Template
students = [
   {"id": 1, "name": "Ravi", "percent": 75.50},
   {"id": 2, "name": "Mona", "percent": 80.00},
   {"id": 3, "name": "Mathews", "percent": 65.25},
]
class StudentResource:
   def on_get(self, req, resp):
      resp.status = falcon.HTTP_OK
      resp.content_type = falcon.MEDIA_HTML
      fp=open("list.html","r")
      tempobj=Template(fp.read())
      resp.body=tempobj.render({'students':students})

list.html is a jinja2 template. It receives the students object as list of dictionary objects and puts the value of each key inside <td>..<.td> element of a table.

<html>
<body>
<table border=1>
   <thead> <tr>
      <th>Student ID</th> <th>Student Name</th>
      <th>percentage</th>
      <th>Actions</th>
   </tr> </thead>
   <tbody>
   {% for Student in students %}
   <tr> <td>{{ Student.id }}</td> <td>{{ Student.name }}</td>
      <td>{{ Student.percent }}</td>
      <td>
         <a href="#">Edit</a>
         <a href="#">Delete</a>
      </td> </tr>
   {% endfor %}
   </tbody>
</table>
</body>
</html>

Visit the /students route in the browser's address bar. The list of students is rendered in the browser.

Jinja2 Image

HTML Form Template

In this section, we shall see how Falcon reads the data from HTML form. Let us save the following HTML script as myform.html. We shall use it for obtaining Template object and render it.

<html>
<body>
   <form method="POST" action="http://localhost:8000/students">
   <p>Student Id: <input type="text" name="id"/> </p>
   <p>student Name: <input type="text" name="name"/> </p>
   <p>Percentage: <input type="text" name="percent"/> </p>
   <p><input type="submit"> </p>
</body>
</html>

The Falcon App object is declared in Hello.py file which also has a resource class mapped to /adddnew route. The on_get() responder reads the myform.html and renders the same. The HTML form will be displayed. The form is submitted to /students route by POST method.

To be able to read the form data, the auto_parse_form_urlencoded property of falcon.RequestOptions class must be set to True.

app = falcon.App()
app.req_options.auto_parse_form_urlencoded = True

Here, we also import StudentResource class from student.py. The on_get() responder renders the list of students.

The on_post() responder will be called when the user fills and submits the form. This method collects the form data in the req.params property, which is nothing but a dictionary of form elements and their values. The students dictionary is then appended.

def on_post(self, req, resp):
   student=req.params
   students.append(student)

The complete code of hello.py is as follows −

import falcon
import json
from waitress import serve
from jinja2 import Template
from student import StudentResource
class MyResource:
   def on_get(self, req, resp):
      resp.status = falcon.HTTP_200
      resp.content_type = 'text/html'
      fp=open("myform.html","r")
      tempobj=Template(fp.read())
      resp.body=tempobj.render()
app = falcon.App()
app.req_options.auto_parse_form_urlencoded = True
form = MyResource()
app.add_route('/addnew', form)
app.add_route("/students", StudentResource())
if __name__ == '__main__':
   serve(app, host='0.0.0.0', port=8000)

The student.py having StudentResource class and on_get() and on_post() responders is as follows −

import falcon
import json
from waitress import serve
from jinja2 import Template
students = [
   {"id": 1, "name": "Ravi", "percent": 75.50},
   {"id": 2, "name": "Mona", "percent": 80.00},
   {"id": 3, "name": "Mathews", "percent": 65.25},
]
class StudentResource:
   def on_get(self, req, resp):
      resp.status = falcon.HTTP_OK
      resp.content_type = falcon.MEDIA_HTML
      fp=open("list.html","r")
      tempobj=Template(fp.read())
      resp.body=tempobj.render({'students':students})

   def on_post(self, req, resp):
      student = req.params
      students.append(student)
      resp.text = "Student added successfully."
      resp.status = falcon.HTTP_OK
      resp.content_type = falcon.MEDIA_JSON

Run hello.py from the command line. Open the HTML form in the browser by entering http://locLhost:8000/addnew.

Jinja2 Host

The students database dictionary will be appended. Visit /students route. You will find a new row appended.

Jinja2 Example

Multipart Forms

In order to let the user select files from the local filesystem, the enctype attribute of HTML form must be set to multipart/form-data. Falcon uses MultipartFormHandler to handle the multipart/form-data media type, allowing it to iterate over the body parts in the form.

The BodyPart class has the following properties −

  • stream − stream wrapper just for the current body part

  • data − body part content bytes

  • content_type would default to text/plain if not specified, as per RFC

  • text − the current body part decoded as text string (only provided it is of type text/plain, None otherwise)

  • media − automatically parsed by media handlers in the same way as req.media

  • name, filename − relevant parts from the Content-Disposition header

  • secure_filename − sanitized filename that could safely be used on the server filesystem.

The following HTML script (index.html) is a multi-part form.

<html>
   <body>
      <form action="http://localhost:8000/hello" method="POST" enctype="multipart/form-data">
         <h3>Enter User name</h3>
         <p><input type='text' name='name'/></p>
         <h3>Enter address</h3>
         <p><input type='text' name='addr'/></p>
         <p><input type="file" name="file" /></p>
         <p><input type='submit' value='submit'/></p>
      </form>
   </body>
</html>

This form is rendered by the on_get() responder of the HelloResource class in the code below. The form data is submitted to on_post() method which iterates over the parts and sends a JSON response of the form data.

import waitress
import falcon
import json
from jinja2 import Template
class HelloResource:
   def on_get(self, req, resp):
      resp.status = falcon.HTTP_200
      resp.content_type = 'text/html'
      fp=open("index.html","r")
      tempobj=Template(fp.read())
      resp.body=tempobj.render()

   def on_post(self, req, resp):
      result=[]
      for part in req.media:
         data={"name" :part.name,
            "content type":part.content_type,
            "value":part.text, "file":part.filename}
         result.append(data)
         resp.text = json.dumps(result)
         resp.status = falcon.HTTP_OK
         resp.content_type = falcon.MEDIA_JSON
app = falcon.App()
hello = HelloResource()
app.add_route('/hello', hello)
if __name__ == '__main__':
   waitress.serve(app, host='0.0.0.0', port=8000)

Run the above program and visit http://localhost:8000/hello link to render the form as shown below −

Jinja2 User

When the form is submitted after filling the data, the JSON response is rendered in the browser as shown below −

[
   {
      "name": "name",
      "content type": "text/plain",
      "value": "SuyashKumar Khanna",
      "file": null
   },
   {
      "name": "addr",
      "content type": "text/plain",
      "value": "New Delhi",
      "file": null
   },
   {
      "name": "file",
      "content type": "image/png",
      "value": null,
      "file": "hello.png"
   }
]
Advertisements