For fun, I thought I'd write a post describing how to build a blog using Flask, a Python web-framework. Building a blog seems like, along with writing a Twitter-clone, a quintessential experience when learning a new web framework. I remember when I was attending a five-day Django tutorial presented by Jacob Kaplan-Moss, one of my favorite projects we did was creating a blog. After setting up the core of the site, I spent a ton of time adding features and little tweaks here-and-there. My hope is that this post will give you the tools to build a blog, and that you have fun customizing the site and adding cool new features.
In this post we'll cover the basics to get a functional site, but leave lots of room for personalization and improvements so you can make it your own. The actual Python source code for the blog will be a very manageable 200 lines.
Who is this post for?
This post is intended for beginner to intermediate-level Python developers, or experienced developers looking to learn a bit more about Python and Flask. For the mother of all Flask tutorials, check out Migeul Grinberg's 18 part Flask mega-tutorial.
The spec
Here are the features:
Entries are formatted using markdown.
Entries support syntax highlighting, optionally using Github-style triple-backticks.
Automatic video / rich media embedding using OEmbed.
Very nice full-text search thanks to SQLite's FTS extension.
Pagination.
Draft posts.
Here is a quick preview of what the blog will look like when we're finished!
Index page
Entry detail page
Getting started
If you'd like to skip the post and go directly to the code, you can find the python app and templates in this gist and download the static assets.
To get started, let's create a virtualenv and install the packages we'll be using. If you're not familiar with virtualenv, it is practically a standard library which creates isolated, self-contained Python environments into which you can install packages. Check out the install docs for instructions on installing virtualenv.
For our app we'll need to install the following packages:
Flask, a lightweight web framework.
Peewee, for storing entries in the database and executing queries.
pygments, syntax highlighting with support for a ton of different languages.
markdown, formatting for our entries.
micawber, for converting URLs into rich content objects. For example if you wanted to embed a YouTube video, just place the URL to the video in your post and a video player will automagically appear in its place.
BeautifulSoup, required by micawber for parsing HTML.
Our blog app will be a single Python module named app.py. We'll also create some folders for static assets (stylesheets, javascript files) and a folder for HTML templates. I'll cover the templates at the end of the post, and the statics will just be bootstrap.
Configuring the Flask app
Let's start editing app.py and configuring our Flask app. We will configure various objects used by our app like our SQLite database, the OEmbed client, and the Flask app itself. Configuration values can be placed in a separate module, but for simplicity we'll just put them in the same namespace as our app.
You'll notice that the admin password is stored as a configuration value in plaintext. This is OK for prototyping, but if you end up deploying this app you might consider at least using a one-way hash to store the password.
Defining our database models
I always enjoy defining the database schema for a new project. It's fun to think about modeling the relationships between the different entities, thinking of ways the schema might evolve, how to efficiently support the queries you'll be executing... For our blog project, we'll focus on simplicity, knowing that it'll be easy to extend in the future. Entries will be stored in a single table, and we'll have a separate table for the search index.
The Entry model will have the following columns:
title
slug: URL-friendly representation of the title.
content: markdown-formatted entry.
published: boolean flag indicating whether the entry is published (visible on site).
timestamp: time the entry is created.
id: peewee automatically will create an auto-incrementing primary key for us, so we don't need to define this explicitly.
The search index will be stored using the FTSEntry model class:
entry_id: The primary key of the indexed entry.
content: Search content for the given entry.
Add the following code after the app configuration and initialization code:
The above code defines two model classes and their respective fields. The Entry model has two additional methods which are used to ensure that when an entry is saved, we also generate a slug from the title, and update the search index. You may wonder why we didn't put a foreign key on the FTSEntry model -- the reason we did not is because SQLite virtual tables do not support indexes or constraints, so a foreign key would be moot.
Also note that we set up some fields with index=True. This tells peewee to create a secondary index on those columns, which will speed up some queries we'll be running in our app.
Initialization code
Now that we have our models defined, let's add app initialization code, a hook for handling 404s, and a template filter we'll use later on. When we start the app in debug mode by running it from the command-line, we'll automatically create the database tables if they don't exist, and start the development server. Add the following code to the bottom of the file:
If you'd like, you can try running your app now. We won't be able to make any requests since there are no views yet, but your database will be created and you should see the following output:
Adding login and logout functionality
In order to create and edit posts, as well as to manage the list of drafts, we will add some very simple authentication to the blog site. Flask has a cookie-based session object which we'll use to store whether a user has authenticated with the site. A common pattern with Flask apps is to use decorators to protect views that require authentication. We'll add a login_required() decorator and views for logging in and out.
To keep things simple, we'll just hard-code the admin password into the application. You may have noticed that we defined the ADMIN_PASSWORD at the top of the module along with the other app configuration data.
Add the following code after the model definitions:
Note that the login and logout views do different things depending on whether the request was a GET or a POST (form submission). When logging in, if you simply navigate to /login/ in your browser, you will see a rendered template with a password field. When you submit the form, though, the view will check the submitted password against the configured ADMIN_PASSWORD, and conditionally redirect or display an error message.
Implementing views
Now that we've laid the foundation of our site, we can start working on the views that will actually be used to display and manage blog entries. Thanks to some of the helpers in the flask_utils playhouse module, the view code will be very minimal.
Index, Search and Drafts
Let's start with the homepage index view. This view will list entries ordered newest-to-oldest, paginated to display 20 posts at a time. We'll use the object_list helper from flask_utils, which accepts a query and returns the requested page of objects. Additionally, the index page will allow users to perform searches.
Add the following code below the login and logout views:
If a search query is present, as indicated by the GET argument q, we will call the Entry.search() classmethod. This method will use the SQLite full-text search index to query for matching entries. SQLite's full-text search supports boolean queries, quoted phrases, and more.
You may notice that we're also calling Entry.public() if no search is present. This method will return only published entries.
To implement these, add the following classmethods to the Entry class:
Let's dig into the search() method briefly. What we're doing is querying the FTSEntry virtual table, which stores the search index of our blog entries. SQLite's full-text search implements a custom MATCH operator, which we'll use to match indexed content against the search query. We also are joining the Entry table so we only return search results for published entries. For more details, check out my SQLite full-text search with Python post.
Because we're only displaying published entries on the index and search results, we'll need a way for logged-in users to manage the list of draft posts. Let's add a protected view for displaying draft posts. We'll add another classmethod to Entry, and add new view below the existing index() view.
Add the following method to Entry:
Add the following view below the index view. This view will use the login_required decorator to ensure only logged-in users can access it:
Entry detail page
In order to have nice URLs, we'll use a URL-friendly representation of an entry's title to identify each Entry. You might recall that we overrode the Entry.save() method to populate the slug field with a URL-friendly title. Example title and slug might be:
"Extending SQLite with Python" -> extending-sqlite-with-python
"How to make a blog using Flask" -> how-to-make-a-blog-using-flask
Our detail view will accept a single parameter, the slug, and then attempt to match that to an Entry in the database. The catch is that if the user is logged-in we will allow them to view drafts, but if the user is not, we will only show public entries.
Add the following view code after the drafts() function:
The get_object_or_404 helper is defined in the playhouse flask_utils module and, if an object matching the query is not found, will return a 404 response.
Rendering entry content
In order to convert the Entry's markdown-formatted text into HTML in the detail page, we'll add an additional property to the Entry class. This property will turn the markdown content into HTML and convert media links into embedded objects (i.e. a YouTube URL becomes a video player).
Add the following property to the Entry model:
The Markup object tells Flask that we trust the HTML content, so it will not be escaped when rendered in the template.
Creating and editing entries
Now that we've covered the views for displaying entries, drafts and detail pages, we need two new views for creating and editing entries. These views will have a lot in common, but for clarity we'll implement them as two separate view functions.
The code is hopefully fairly self-explanatory, but to give it some color, basically we're going to do different things depending on the request method. If the request method is GET, then we will display a form allowing the user to create or edit the given entry. If the method is POST we will assume they submitted the form on the page (which we'll get to when we cover templates), and after doing some simple validation, we'll either create a new entry or update the existing one. After saving the entry, we will either redirect to the detail page, or redirect to edit page depending on whether the entry is published or not.
Here is the code for create, which should be placed immediately before the detail view. This is important because if we didn't put it before detail, then Flask would interpret requests to /create/ as attempting to request the detail page for an entry with the slug create, which we don't want.
The edit view is similar and can be placed after the detail view. The only difference is that we will call get_object_or_404 to verify that the entry exists:
Templates
Believe it or not, that is all that's required in terms of Python code! You can compare your finished code with this file. We're now ready to write some templates to go along with all these views. The template layer is a great first place to start with customizations, so the templates presented will be very basic and use bootstrap for the styles.
Rather than present all the templates, which you can find in this gist, I'll hit some of the interesting or tricky parts.
Jinja2 is a very popular Python templating engine that's used with Flask apps. One of the key reasons to use templates is code re-use. Typically this means a combination of template inheritance and template includes. Inheritance means you'll define a base template that defines the shell of your site and blocks out places for titles, navigation links, body content, etc. You can have multiple base templates -- this is common in bigger apps -- but for our blog we'll just have one. The other way to re-use template code is through template includes. Template includes allow us to define a fragment of HTML code we want to re-use multiple places and then include it wherever we need it. The pagination links we'll be adding to the entry index will be an include so it'll be easy to add pagination elsewhere in the future.
The base template is the largest of the templates so I'm not going to include it here, but what it does is define the HTML structure of the site and defines several blocks which we'll override in sub-templates:
title: controls the title of the page (i.e. what's displayed in the tab or title-bar).
content_title: controls the title you see in the actual page, which is wrapped in an <h1> header tag.
content: controls the main content area of the page, where we'll be displaying entries and such.
index.html
Let's take a look at the index template. It begins by indicating that it extends base.html, then defines overrides for our three main blocks. Because this template is used for both the default index list as well as the search results, we'll add conditional statements to check if a search is present. If you refer back to the views, you might note that any user search query is passed into the template context when the index is rendered.
A couple things worth noting:
The object_list context variable is added by the object_list helper function we called in the index view. It contains a list of either blog entries or FTSEntry search results.
If a search is present, we display a different content title indicating what the user searched for.
We are calling the strftime() function on the entry's timestamp to display a friendly version of the timestamp.
We are including the pagination partial at the bottom of the page.
detail.html
The detail template will display the entry's title and the HTML content that results from converting the markdown to HTML and processing any embeddable objects. In the base template there is an extra block for adding links to the navbar, which we'll use to add an "Edit entry" link if the user is logged-in. Here is the code:
edit.html
The final template I'll cover here is the template for editing an entry. This template uses Bootstrap conventions for defining the form fields, so there's a bit of boilerplate that's safe to ignore. Note that we are prepopulating the form fields with data taken either from the previous POST request (if the form was submitted with errors), or the entry you are editing.
{% extends "base.html" %}
{% block title %}Edit entry{% endblock %}
{% block content_title %}Edit entry{% endblock %}
{% block content %}
<form action="{{</