[Flask](http://flask.pocoo.org/) is a Python web framework that's steadily increasing in popularity within the webdev world. After reading so many great things about Flask, I decided to try it out myself. I personally find testing out a new framework difficult, because you must find a project complex enough to reveal the framework's quirks, but not so daunting as to take the fun out of the project. Luckily, my PHP/Wordpress powered website filled this role quite nicely - the website simply consists of static content, a contact page, and a blog. If I could not convert such a simple site over to Flask, I would immediately know that Flask and I would not make a good team. Spoiler alert: You're reading this on a Flask powered site! Feel free to check out the source of this site [on Github](https://github.com/joequery/Vert-Flask). Getting Started --------------- The first task at hand was simply getting the home, about, services, and work pages to render correctly. While the task consisted mostly of copying and pasting, I was able to immediately apply most of the concepts from the [Flask quickstart guide][0]. Specifically, I had to learn * How do I actually start the server? * Where do my templates go? * Where do my static assets go? * How do I retrieve my assets from within the templates? * How do I route requests? Sure enough, getting the pure HTML documents to render was pretty simple. By pure HTML, I mean there were no includes for headers, footers, etc. Thus the logical next step was to take these pure HTML documents and DRY up the repeating areas. Growing pains...already?! ------------------------ Coming from PHP, I was used to just `include`-ing the content I wanted and being done with it. [Jinja2](http://jinja.pocoo.org/docs/), Flask's default templating engine, does not currently support includes. (Note: An `include` function [does exist](http://jinja.pocoo.org/docs/templates/#include). However, I could not get the include function to return non-escaped html. Perhaps this function was broken in a recent build). Jinja does, however, have [template inheritance][1]. To demonstrate, suppose we have a parent template `layout.html` {% raw %}
<!DOCTYPE html>
<html lang="en">
<head><title>My Website</title> </head>
<body>
<div id="wrapper">
<div id="content">
{% block body %}{% endblock body %}
</div><!--content-->
</div><!--wrapper-->
</body>
</html>
{% endraw %}
And a child template `index.html` that inherits the parent and fills in its
blocks
{% raw %}
{% extends "layout.html" %}
{% block body %}
Body content goes here!
{% endblock body %}
{% endraw %}
If we were to call [render_template][2] on `index.html`, this is what we
would get.
<!DOCTYPE html>
<html lang="en">
<head><title>My Website</title> </head>
<body>
<div id="wrapper">
<div id="content">
Body content goes here!
</div><!--content-->
</div><!--wrapper-->
</body>
</html>
I like the template inheritance pattern of Jinja, but the lack of an include
statement can easily lead to huge parent templates.
Thankfully, I did find a sufficient workaround to the lack of an include
statement. Jinja lets you define [Macros][4], which are essentially functions
used within templates to render something. I defined a macro called `nav` that
simply returned the HTML for the navigation. I did the same thing for `header`,
`footer`, and any other sections repeated throughout the site. Take our
footer, for example.
{% raw %}
{% macro footer() %}
<div id="footer">
<div class="hr"><hr /></div>
<p>
<a href="mailto:hi@vertstudios.com">hi@vertstudios.com</a> |
<a href="http://www.twitter.com/vertstudios">@vertstudios</a>
</p>
</div>
{% endmacro %}
{% endraw %}
Then, from `layouts.html`, I could call the footer macro. Flask requires the
use of the `|safe` filter if you want to render the returned string. Thus, a
macro `foo()` can be called from a macro `bar()` without the need to specify
`{{'{{'}}foo()|safe{{'}}'}}`. But since `layouts.html` is something being
directly rendered, we need to specify `|safe` every time we call a macro.
So the final `layouts.html` ended up looking like
{% raw %}
{% from "sections" import meta_and_css, nav, javascripts, footer %}
<!DOCTYPE html>
<html lang="en">
{{ meta_and_css(g, title)|safe }}
<body onload="prettyPrint()" id="{{g.bodyID}}">
<div id="wrapper">
{{ nav(g)|safe }}
<div id="content">
{% block body %}{% endblock body %}
</div><!--content-->
{{ footer()|safe }}
</div><!--wrapper-->
{{ javascripts(g)|safe }}
</body>
</html>
{% endraw %}
This was less than ideal, but it worked; a recurring theme throughout the
development process that surprisingly didn't bother me so much.
Being 'Brave'
-------------
Flask didn't always have an out-of-the-box solution to the problem at hand. In
fact, it *rarely* did. Flask did, however, give me all the tools necessary to
create whatever I wanted. In a way, I felt that Flask itself was telling me
"Joseph, you're smart enough to implement X functionality on your own... be
brave for once!"
Sometimes reinventing the wheel is a rewarding exercise.
The Contact Form
----------------
The recommended method of dealing with forms in Flask is the [Flask-WTF][5]
extension. Flask-WTF has many [built in validation methods][6], but it did not
include validation for a phone number. No big deal, any form library worth its
salt has means for defining custom validation functions. WTForms does in fact
have a mechanism for implementing [custom validators][7], but I did not find
their examples very pleasing to the eye:
def length(min=-1, max=-1):
message = 'Must be between %d and %d characters long.' % (min, max)
def _length(form, field):
l = field.data and len(field.data) or 0
if l < min or max != -1 and l > max:
raise ValidationError(message)
return _length
class MyForm(Form):
name = TextField('Name', [Required(), length(max=50)])
Consequently, I used Flask-WTF to gather data from the form fields, but I
rolled my own validation module, which only took about an hour.
The actual sending of the email was easily handled with Python's built-in
`smtplib` module and Gmail.
The Blog
--------
Creating a blog has become the "Hello, World" of web application frameworks.
While [Python blogging software][8] does exist, it seemed like it would take
longer to integrate an existing blogging platform into my Flask project than
just rolling my own.
For the past year or so, I've longed for a file-system based blogging platform.
Writing my articles in VIM + Markdown is **so** much more enjoyable than
writing HTML in VIM, moving the HTML over to Wordpress, previewing the HTML,
making sure the formatting is right, and other annoying steps.
There were a few specific things I needed to accomplish with the blog:
* Ability to generate an RSS feed
* The URLs of the blog posts must be the same as before.
* Pagination
Without the need to generate an RSS feed, the only thing I would need to do is
route `/blog/
{% macro img(g, file, alt="", class="") %}
<img
src="{{g.assets}}/images/{{file}}"
alt="{{alt}}"
class="{{class}}"
/>
{% endmacro %}
{% endraw %}
An example call to the macro:
{% raw %}
{{img(g, "pointer.png", "", "pointer")|safe }}
{% endraw %}
It's more verbose than what I'd really like, but it gets the job done.
Going Live with Nginx and uWSGI
-------------------------------
Now that my project was pretty much finished, it was time to put the files
on my Linode VPS and show my project to the world! I decided to use Nginx with
uWSGI, simply because the pair have been the subject of many tutorials. Flask
itself [has documentation][11] on uWSGI. However, I found parts of the
documentation inadequate.
From the documentation:
# Given a flask application in myapp.py, use the following command: $ uwsgi -s /tmp/uwsgi.sock --module myapp --callable appIf you want to put a project on a live server, there's no way you should have to do so much work! uWSGI has [ini configuration files][12] that I took full advantage of. Now, instead of executing a verbose command from the shell, I can simply execute
$ uwsgi uwsgi.iniMy `uwsgi.ini` is as follows:
[uwsgi] project = vertstudios daemonize = /var/log/nginx/access.log master = true chdir = /var/www/vertstudios.com socket = 127.0.0.1:5000 wsgi = %(project):variable holding app instance virtualenv = env/ pidfile = /var/run/uwsgi/%(project).pid touch-reload = /var/run/uwsgi/%(project).pid processes = 3 procname-prefix = %(project)Now my uWSGI configuration is under version control with the rest of my project. Here's an explanation for the parameters: * project: The module name where you call `app.run()`. * daemonize: If you want to daemonize. For some reason, you have to specify a log file path or uWSGI will just shove things down `stdin`. `true` did not work for me. * master: Boolean indicating if you want a master process. Master processes make [uwsgi management][13] *much* easier. * chdir: Change directory. Essentially lets you keep parameters such as "project" and virtualenv relative to the path provided to chdir. * socket: A Unix socket path or TCP socket info * wsgi: %(the module containing app):app * virtualenv: Path to the virtualenv for the project. * pidfile: Where the master process pid file will be written. * touch-reload: Touching this file causes the processes to reload (convenient!) * processes: How many processes you want running (not including master process) * procname-prefix: Process name prefix. Useful for `ps aux | grep myapp` Now, for the parts of my nginx config relevant to uWSGI:
# Redirect everything to the main site.
server {
server_name www.vertstudios.com;
return 301 http://vertstudios.com$request_uri;
}
server {
server_name vertstudios.com;
location / { try_files $uri @flaskapp; }
location @flaskapp {
include uwsgi_params;
uwsgi_pass 127.0.0.1:5000;
}
}
Things left TODO
----------------
I felt I had done enough to merit scrapping my PHP/Wordpress site and put the
Flask site up in its place. Despite that, there's still quite a bit of work to
be done.
* I plan on exporting my Wordpress comments to Disqus. I enjoy interacting with
people that read my articles, and I'd like to preserve the comments I've
already acquired.
* I've managed to break a few links. In particular, I've broken links to php
files I stupidly used to demo some of my jQuery plugins.
* Using [prettify][14] for syntax highlighting means I have to still use <pre>
tags in my documents, since the script requires a class of "prettyprint" on
those tags. I'll either adjust prettyprint js and styles myself, find an
alternative syntax highlighter, or do some processing of the markup to append
the "prettyprint" class whenever it finds the <pre> tag.
* Still need to set up git deployment. I'm currently just pulling from a
private bitbucket repo on my server.
* I need to automate my build workflow. A single bash script should concat all
my CSS/JS files, run YUICompressor, and upload the files to S3.
* I really need to implement meta description tags for my posts...but that
would require me to actually write them.
Overall Impressions
-------------------
As a whole, I'm quite pleased with Flask. For the most part, it gets out of
your way and lets you build things how you want to build them. Consequently,
your project structure may be sloppy (especially when first learning Flask),
but you're guaranteed to understand how and why everything works.
[0]: http://flask.pocoo.org/docs/quickstart
[1]: http://jinja.pocoo.org/docs/templates/#template-inheritance
[2]: http://flask.pocoo.org/docs/quickstart/#rendering-templates
[3]: http://flask.pocoo.org/community/poweredby/
[4]: http://jinja.pocoo.org/docs/templates/#macros
[5]: http://packages.python.org/Flask-WTF/
[6]: http://flask.pocoo.org/docs/patterns/wtforms/#the-forms
[7]: http://wtforms.simplecodes.com/docs/1.0.1/validators.html
[8]: http://wiki.python.org/moin/PythonBlogSoftware
[9]: http://packages.python.org/pyquery/api.html
[10]: http://stackoverflow.com/a/9123510/670823
[11]: http://flask.pocoo.org/docs/deploying/uwsgi/
[12]: http://projects.unbit.it/uwsgi/wiki/INIFiles
[13]: http://projects.unbit.it/uwsgi/wiki/Management
[14]: http://google-code-prettify.googlecode.com/svn/trunk/README.html