StaticGenerator for Django
Introduction
How many CPU cycles do you suppose are wasted on blogs that are generated every request? Wouldn’t it make more sense to generate them only when they’re updated? StaticGenerator is a Python class for Django that makes it easy to create static files for lightning fast performance.
Download
You can get StaticGenerator using easy_install
:
easy_install staticgenerator
Or download from the cheeseshop.
Usage
There are two ways to generate the static files. Both setups first require WEB_ROOT
to be set in settings.py
:
WEB_ROOT = '/var/www/example.com/public/'
Method 1 (preferred): Middleware
As of StaticGenerator 1.3, Middleware is available to generate the file only when the URL is requested. This solves the 404 Problem (see below).
First, add Regexes of URLs you want to cache to settings.py
like so:
STATIC_GENERATOR_URLS = (
r'^/$',
r'^/blog',
r'^/about',
)
Second, add the Middleware to MIDDLEWARE_CLASSES
:
MIDDLEWARE_CLASSES = (
...snip...
'staticgenerator.middleware.StaticGeneratorMiddleware',
'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware',
...snip...
)
Note: You must place the StaticGeneratorMiddleware before FlatpageFallbackMiddleware if you use it.
When the pages are accessed for the first time, the body of the page is saved into a static file. This is completely transparent to the end-user. When the page or an associated object has changed, simply delete the cached file (See notes on Signals).
Method 2: Generate on Save
The second method works by saving the cache file on save. This method fakes a request to get the appropriate content. In this example we want to publish our home page, all live Posts and all FlatPages:
# Passing url, a QuerySet and Model
from staticgenerator import quick_publish
quick_publish('/', Post.objects.live(), FlatPage)
Deleting files and paths is just as easy:
from staticgenerator import quick_delete
quick_delete('/path-to-delete/')
Note: Directory deletion fails silently while failing to delete a file will raise an exception.
The "404 Problem"
The second method suffers from a problem herein called the "404 problem". Say you have a blog post that is not yet to be published. When you save it, the file created is actually a 404 message since the blog post is not actually available to the public. Using the older method you'd have to re-save the object to generate the file again.
The new method solves this because it saves the file only when the URL is accessed successfully (read: only when the HTTP status is 200).
Using Signals
Integrating with existing models is easy using Django’s signal dispatcher. Simply create a function to delete your models, and connect to the dispatcher:
from django.contrib.flatpages.models import FlatPage
from blog.models import Post
from django.dispatch import dispatcher
from django.db.models import signals
from staticgenerator import quick_delete
def delete(sender, instance):
quick_delete(instance, '/')
dispatcher.connect(delete, sender=Post, signal=signals.post_save)
dispatcher.connect(delete, sender=FlatPage, signal=signals.post_save)
Every time you save a Post or FlatPage it deletes the static file (notice that I add '/' so my homepage is deleted as well). What happens when a comment is added? Just delete the corresponding page:
from django.contrib.comments.models import Comment, FreeComment
def publish_comment(sender, instance):
quick_delete(instance.get_content_object())
dispatcher.connect(publish_comment, sender=Comment, signal=signals.post_save)
dispatcher.connect(publish_comment, sender=FreeComment, signal=signals.post_save)
Configure your front-end
Sample Nginx configuration
This configuration snippet shows how Nginx can automatically show the index.html page generated by StaticGenerator, and pass all Django requests to Apache.
# This example configuration only shows parts relevant to a Django app
http {
upstream django {
# Apache/mod_python running on port 7000
server example.com:7000;
}
server {
server_name example.com;
root /var/www/;
location / {
if (-f $request_filename/index.html) {
rewrite (.*) $1/index.html break;
}
if (!-f $request_filename) {
proxy_pass http://django;
break;
}
}
}
}
It’s not for Everything
The beauty of the generator is that you choose when and what urls are made into static files. Obviously a contact form or search form won’t work this way, so we just leave them as regular Django requests. In your front-end http server (you are using a front-end web server, right?) just set the URLs you want to be served as static and they’re already being served.
Feedback
Love it? Hate it? Let me know what you think!