A Peek Inside 2buntu


Nathan Osman's Gravatar

Nathan Osman
published Sept. 21, 2013, 2:20 p.m.


Most Ubuntu blogs these days are running WordPress. So it may surprise a number of our readers to learn that we actually run our own custom blogging engine. Very little has been said here regarding our platform and how we run things under the hood, so I thought it might be worthwhile to go into a bit of detail.

History

Originally, we ran 2buntu on WordPress. In fact, we were originally hosted on wordpress.com before making a couple of moves to our current server (hosted by Ondina). We started to outgrow the capabilities of the free hosting that wordpress.com provided almost immediately - and we switched to a custom WordPress install on a shared hosting account. We also acquired the domain name 2buntu.com around this time.

We stuck with shared hosting for quite some time, experimenting with different WordPress themes and adding many plugins for caching, widgets, etc. All of these led to longer and longer page load times. At the same time, it was becoming more and more difficult to customize the WordPress installation. I wrote a couple of plugins for the website, but it was a lot of work and PHP is not a fun language to work with (based on my personal experience).

What really bothered me was that our articles were written in a bizarre combination of WordPress markup, plugin syntax, and raw HTML. This led to all kinds of problems when we switched themes or modified plugins. It also led to a steep learning curve for new authors, many of whom were not really familiar with HTML. Granted, WordPress did provide a toolbar, but not everything had a toolbar button.

So with Roland's permission, I began working on the current blogging engine. The entire blog was rewritten in Python using the Django web framework (which I used on my own website as well). It took a fair amount of time to reproduce all of the essential functionality that WordPress provided, but eventually the task was completed.

Under the Hood

One of Django's strengths is loose-coupling - keeping each component as separate as possible from the others. This allows us to easily throw together additional functionality and take it down just as easily.

We chose Bootstrap for its fluid layout capabilities. (This is why our website looks so nice on a mobile phone.) We do have a small number of custom style overrides that fix a couple of minor issues we would otherwise have.

We made the switch to Markdown for writing articles since many of our authors are already familiar with it and because the syntax is easy to learn. We recently added a toolbar for authors to use in case they are not familiar with Markdown syntax. (Not only that but we also provide a "cheat sheet".)

Software

Here's the list of software we're currently using (as of September 2013):

  • Apache 2.2 with mod_wsgi
  • Python 2.7.2
  • Django 1.4
  • MySQL 5.1

We also cache the rendered Markdown for the home page in a MySQL table. We're working on rewriting the caching code to speed up rendering of other pages as well.

Editor

Our editor is basically a textarea - nothing fancy. As mentioned above, we added a toolbar (written in JavaScript) that inserts Markdown for some common items.

Of particular note is our image uploader. We use Fine Uploader on the client side in combination with a Bootstrap modal dialog box to enable authors to upload their images. We store them on the server and serve them from media.2buntu.com. I've written a custom Markdown extension that allows images to be referenced by their unique ID in the database instead of by filename.

Comments

Back in the days of WordPress, we mostly used Disqus for comments. So in order to avoid a difficult migration, we decided to stick with Disqus. At the time, Django provided a comment app that could have easily been used - but we're glad we avoided it since the app is now deprecated.

API

Yes, we have an API.

We currently provide access to article content, including the rendered markdown, author, etc. But rather than going into detail on what the API provides, we'll take a look at how it works.

I am a huge fan of Python decorators. They make code reuse easy and beautiful. For example, suppose we have a function like so:

def calculate(v):
    '''
    Performs a really expensive calculation.
    '''
    return v*2+3/5

Okay, it doesn't really do anything useful - but pretend it does. Because calling this function with the same parameter will result in the same return value, it doesn't make sense to repeat calculations when the same parameter is supplied. If we call calculate(2) once, we shouldn't have to call it again. We need to cache the return value.

Here's a decorator we can use for this:

def cache(fn):
    '''
    Caches the value of the function call.
    '''
    c = {}
    def wrap(v):
        if not v in c:
            c[v] = fn(v)
        return c[v]
    return wrap

The inner function (wrap()) checks to see if the value v is stored in the dictionary c. If not, it performs the calculation and stores the return value. Otherwise, it merely returns the return value calculated last time. The outer function (cache) takes a function as a parameter and returns the function wrap(). How is this used? Well, let's modify the definition of calculate():

@cache
def calculate(v):
    ...

The @ syntax specifies that the decorator should be applied to the function. Now when we call calculate(), the return value is cached and reused when the same parameter is supplied. The beauty of this is that we can apply @cache to any function that takes a single parameter. (Well, almost - the parameter must be an immutable type.) You can also "chain" decorators by applying more than one to a function.

Now how does our API use this? Well, it turns out that the API endpoints share a number of common parameters. Since we always strive to follow the DRY principle (Don't Repeat Yourself), it made sense to separate the common functionality from each endpoint. We could have used regular functions for this - but even that would have resulted in a bit of duplication (parameters, etc.). Decorators made much more sense, allowing us to do things like this (copied from our source code):

@endpoint
@articles
@paginate
@minmax('modification_date')
def modified(request):
    ...

These decorators do the following (in reverse order):

  • @minmax applies the min and max parameters (if supplied) to the query made against the database
  • @paginate uses the page and size parameters (if supplied) to apply a slice to the results of the database query
  • @articles formats the data as a JSON string
  • @endpoint checks for the callback GET parameter and returns the data with the appropriate MIME type set

Each of these can be reused for other endpoints. Also, it is really easy to see at a glance what each endpoint supports.

Summary

I hope that this article helped give you a picture of how we run things here at 2buntu. We're always changing things to improve performance so this information may be out of date by the time you read this. As always, stay tuned for updates!