Creating an Apfell - Part 3
Published:
At the end of the last post we ended up with a very basic RESTful interface that can communicate with a postgres database. To do this we leveraged peewee (for our ORM) and peewee-async (for our connections). If you go back to our initial diagram of what all will be included in this, we started with the web server in the middle. We used Python 3.5+ and Sanic in general to get a RESTful interface. Then, we used postgresql, peewee, peewee-async, asyncio, and uvloop to create an asyncrhonous loop for doing database interactions and hooking those up to our prior RESTful API.
This post will start to incorporate a user interface on this. We’ll get into Jinja2 and the twitter bootstrap.
Jinja2
Jinja2 is a templeting engine for python to help with building HTML templates. This sounds way crazier than it actually is. It does a few main things:
- Allows you to pass variables into HTML code to generate unique pages without things like PHP
- Allows you to isolate pieces of HTML as ‘templates’ and merge them together at run time
Let’s take an example. For Apfell, I want the site to have the same general feel on every page (such as the navigation bar at the top), but the main content will change. In our case, we’ll make a base.html
file to contain the navigation piece on every page, and dedicate spaces for page-specific code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
{% if title %}
<title>{{ title }} - Apfell</title>
{% else %}
<title>Welcome - Apfell</title>
{% endif %}
</head>
<body>
<div id="main" class="container-fluid">
{% block body %}{% endblock %}
</div>
{% block scripts %}{% endblock %}
</body>
</html>
So let’s examine this a little bit more. There are a few interesting constructs that allow us to do that first bullet point. Jinja2 components are within {% %}
or within {{ }}
. We can see a check for a variable called title
with {% if title %}
. If we want to access the value of that variable, it’s as easy as {{ title }}
. Be sure to end this with an {% endif %}
. In our case, we use this to display either a standard “Welcome” message, or a title specific to the page we’re on. The next piece involves the {% block body %}{% endblock %}
. This expects a block of code called body
. This is where we’ll start including the second bullet point I mentioned. The above code wouldn’t be used in isolation. Instead, we’d create something like the following:
{% extends "base.html" %}
{% block body %}
<div class="block1">
<h1>Hello {{name}}!</h1>
</div>
{% endblock %}
{% block scripts %}
{% endblock %}
The {% extends "base.html" %}
directive tells Jinja2 that we will use “base.html” as an initial template and will be augmenting it with this file (which we can call something like “main_page.html”). “base.html” expects a block of code called body
, so we provide it in this section. We simply put in a welcome message for the body of the page. Since we don’t have any scripts for this page, we define the block for scripts
, but don’t include anything in it.
Put both “base.html” and “main_page.html” into our Server/app/templates
folder. This is interesting, but how do we actually leverage something like this in our Sanic code? Let’s go back to our routes.py
file and leverage our new Jinja2 templates.
from app import apfell
from sanic import response
from jinja2 import Environment, PackageLoader
env = Environment(loader=PackageLoader('app', 'templates'))
@apfell.route("/")
async def index(request):
template = env.get_template('main_page.html')
content = template.render(name="Bob", title="Main Page")
return response.html(content)
The Environment and PackageLoader allow us to leverage anything in our templates
folder for our application app
as we render Jinja2 templates. We want to display the main_page.html
page as our main page. Remember, this extends the base.html
page, so we don’t actually need to reference that template here as long as they’re both in the templates folder. When we call template.render
, we pass in any variables that we’re expecting in our template (and any our templates inheret). So, main_page.html
has a {{name}}
parameter and base.html
has a {{title}}
parameter. Finally, we display the result as html. It’s as simple as that. When you run your program and go to http://localhost/
, you should be greated with a simple page that says “Hello Bob!” with a title of “Main Page - Apfell”.
Jinja2 can do a lot more than simply displaying a variable or if-statements. I recommend looking into it more and exploring the looping capabilities. You can also pass in a dictionary and reference the fields by name. For example, if you have links = {"welcome":"message"}
and you pass links
as a variable to a Jinja2 template, you can access the welcome message as {{ links.welcome }}
. That gets us dynamic page creation, but still for static HTML pages. We won’t be getting into using JavaScript and Vue.js for making a responsive web page in this post (that’ll be a later one), but we will do something to make our HTML a little prettier.
Twitter Bootstrap
Twitter bootstrap allows us to leverage a few more stylistic and functional components in our UI design. I haven’t done a lot of website design before, especially not since 2008, so a lot has changed. Twitter Bootstrap made it pretty easy to incorporate dropdowns, buttons, navigation bar design, wells, panels, and more. It’s useful to go through their website and start playing around with all the different components they offer. Their website for components and JavaScript does a much better job than I can for displaying all the different things you can do inline with how they actually look. There are a few specific pieces I leveraged heavily for this:
To include bootstrap in your HTML files is as simple as adding in the style sheet and script capability (the .min (minified) versions are more obfuscated and lack some debugging so that they’re better for production environments):
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
Here’s my “base.html” so far:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
{% if title %}
<title>{{ title }} - Apfell</title>
{% else %}
<title>Welcome - Apfell</title>
{% endif %}
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<script src="http://ajax.aspnetcdn.com/ajax/jquery/jquery-1.9.1.js"></script>
<!-- Latest compiled and minified JavaScript -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</head>
<body>
<nav class="navbar navbar-default navbar-inverse">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="{{ links.index }}">Apfell<span class="glyphicon glyphicon-apple" aria-hidden="true"></span></a>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<!-- These are the links across the top, above this is the main icon/name for home -->
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
Operations <span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li><a href="{{ links.callbacks }}">Current Callbacks</a></li>
<li><a href="#">Action</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">Separated Link</a></li>
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
Payloads <span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li><a href="#">Action</a></li>
<li><a href="#">Another Action</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">Separated Link</a></li>
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
Attacks <span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li><a href="#">Action</a></li>
<li><a href="#">Another Action</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">Separated Link</a></li>
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
API <span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li><a href="#">Action</a></li>
<li><a href="#">Another Action</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">Separated Link</a></li>
</ul>
</li>
{% if logged_in %}
<li><a href="{{ links.logout }}">Logout</a></li>
{% else %}
<li><a href="{{ links.login }}">Login</a></li>
{% endif %}
</ul>
</div>
</div>
</nav>
<div id="main" class="container-fluid">
{% block body %}{% endblock %}
</div>
{% block scripts %}{% endblock %}
</body>
</html>
This looks like:
except it doesn’t include the callback information behind it :) that’s for later. Take some time now to experiment with Jinja2 and Twitter’s Bootstrap to figure out how you want to structure your website. We’re closing in on some of the final pieces needed to make this flow all the way through in real time from database to interactive UI.