aiohttp templates

In our examples so far, we’ve only been returning plain text. You can return more complex HTML if you have templates. For this, templates can be used to render HTML templates.

aiohttp is a core library without embedded templating tool, third party libraries need to be installed to provide such functionality.

Let’s use officially supported aiohttp_jinja2 for famous jinja2 template engine (http://aiohttp_jinja2.readthedocs.org/).

aiohttp-jinja2

Install the dependencies:

(.venv) python3.7 -m pip install -U jinja2 aiohttp-jinja2

Code structure:

/jinja_server.py
/templates/base.html

Sample HTML template:

<html>
    <head>
        <title>aiohttp page</title>
    </head>
    <body>
        <div>
            <h1><a href="/">My aiohttp server</a></h1>
        </div>

        <div>
            <p>Date now: {{ current_date }}</p>
            <p>Hello {{ username}}. </p>
        </div>
    </body>
</html>

Template rendering

In jinja_server.py, set up aiohttp-jinja2 and template directory:

import jinja2
import aiohttp_jinja2


app = web.Application()

aiohttp_jinja2.setup(
    app, loader=jinja2.FileSystemLoader(os.path.join(os.getcwd(), "templates"))
)

To render a page using the template:

@routes.get('/{username}')
async def greet_user(request: web.Request) -> web.Response:

    context = {
        'username': request.match_info.get("username", ""),
        'current_date': 'January 27, 2017'
    }
    response = aiohttp_jinja2.render_template("example.html", request,
                                          context=context)

    return response

Another alternative is applying @aiohttp_jinja2.template() decorator to web-handler:

@routes.get('/{username}')
@aiohttp_jinja2.template("example.html")
async def greet_user(request: web.Request) -> Dict[str, Any]:
    context = {
        'username': request.match_info.get("username", ""),
        'current_date': 'January 27, 2017'
    }
    return content

Note, the great_user signature has changed: it returns a jinja2 context now. @aiohttp_jinja2.template() decorator renders the context and returns web.Response object automatically.

Render posts list

@router.get("/")
@aiohttp_jinja2.template("index.html")
async def index(request: web.Request) -> Dict[str, Any]:
    ret = []
    db = request.config_dict["DB"]
    async with db.execute("SELECT id, owner, editor, title FROM posts") as cursor:
        async for row in cursor:
            ret.append(
                {
                    "id": row["id"],
                    "owner": row["owner"],
                    "editor": row["editor"],
                    "title": row["title"],
                }
            )
    return {"posts": ret}

index.html template:

{% extends "base.html" %}
{% block title %}
Posts index
{% endblock %}

{% block content %}
<h1>Posts</h1>
<p>
  <ul>
    {% for post in posts %}
    <li>
      <a href="/{{ post.id }}">{{ post.title }}</a> {{ post.editor }}
    </li>
    {% endfor %}
  </ul>
</p>
<hr>
<p>
  <a href="/new">New</a> 
</p>

{% endblock %}

base.html for template inheritance:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>
    {% block title %}
      Index
    {% endblock %}
    </title>
  </head>
  <body>
    <div id="content"> {% block content %} {% endblock %} </div>
  </body>
</html>

Post editing

Show edit form

@router.get("/{post}/edit")
@aiohttp_jinja2.template("edit.html")
async def edit_post(request: web.Request) -> Dict[str, Any]:
    post_id = request.match_info["post"]
    db = request.config_dict["DB"]
    return {"post": await fetch_post(db, post_id)}

edit.html template:

{% extends "base.html" %}
{% block title %}
Edit Post {{ post.title }}
{% endblock %}

{% block content %}
<h1>Edit: {{ post.title }}</h1>
<form action="/{{ post.id }}/edit" method="POST"
      enctype="multipart/form-data">
  <p>
    Title:
    <input type="text" name="title" value="{{ post.title }}">
  </p>
  <p>Text:
    <textarea name="text" rows="10">{{ post.text }}</textarea>
  </p>
  <input type="submit">
</form>
<hr>
<p><small> created by: {{ owner }}, last editor: {{ editor }} </small></p>
<p>
  <a href="/">list</a>
  <a href="/{{ post.id }}">view</a>
  <a href="/{{ post.id }}/delete">delete</a>
</p>
{% endblock %}

Multipart content

We use method="POST" enctype="multipart/form-data" to send form data.

Sent body looks like:

------WebKitFormBoundaryw6YN2HqrOi6hewhP
Content-Disposition: form-data; name="title"

title 1
------WebKitFormBoundaryw6YN2HqrOi6hewhP
Content-Disposition: form-data; name="text"

text of post
------WebKitFormBoundaryw6YN2HqrOi6hewhP--

Applying edited form data

There is POST handler for /{post}/edit along with GET to apply a new data:

@router.post("/{post}/edit")
async def edit_post_apply(request: web.Request) -> web.Response:
    post_id = request.match_info["post"]
    db = request.config_dict["DB"]
    post = await request.post()
    await db.execute(
        "UPDATE posts SET title = ?, text = ? WHERE id = ?",
        [post["title"], post["text"], post_id],
    )
    await db.commit()
    raise web.HTTPSeeOther(location=f"/{post_id}/edit")

Note

POST handler doesn’t render HTML itself but returects to GET /{post}/edit page.

HTML site endpoints

GET /

List posts.

GET /new

Show form for adding post

POST /new

Apply post adding

GET /{post}/view

Show post

GET /{post}/edit

Edit post

GET /{post}/edit

Show edit post form

POST /{post}/edit

Apply post editing

GET /{post}/delete

Delete post

Note

URLs order does matter: /new should lead /{post}, otherwise /{post} web handler is called with new post id.

Full example for templated server

Example for HTML version of blogs server: Full server example with templates