Date Tags Django

I have been using Django's flatpages app for some simple, static pages that were supposed to be temporary. I slapped a Javascript editor on the admin page and it has worked very well. However some of the pages have long outlived their "temporary" status, and I find myself needing to update them. It is then that I get angry at the Javascript editor, and there is no way to keep any kind of history on the page without having to fish through old database backups. I started to think it would be nice to write the content in a nice markup language, for example reStructuredText, which I could then commit to version control. I would just need a way to generate HTML from the source text to produce the flatpage content.

Of course I could use the template filters in django.contrib.markup. But turning markup into HTML at page request time can be more expensive than I like. Yes, I could cache the page, but I'd like the process to be more explicit.

In my first attempt at doing this, I wrote a custom management command that used a dictionary in my settings.py file to map reStructuredText files to flatpage URLs. My management command would open the input file, convert it to HTML, then find the FlatPage object associated with the URL. It would then update the object with the new HTML content and save it.

This worked okay, but in the end I decided that the pages I wanted to update were not temporary, quick & dirty pages, which is kind of how I view flatpages. So I decided to stop leaning on the flatpages app for these pages.

I then modified the management command to read a given input file, convert it to an HTML fragment, then save it in my templates directory. Thus, a file stored in my project directory as fixed/about.rst would get transformed to templates/fixed/about.html. Here is the source to the command which I saved as make_fixed_page.py:

import os.path
import glob

import docutils.core
from django.core.management.base import LabelCommand, CommandError
from django.conf import settings


class Command(LabelCommand):
    help = "Generate HTML from restructured text files"
    args = "<inputfile1> <inputfile2> ... | all"

    def handle_label(self, filename, **kwargs):
        """Process input file(s)"""

        if not hasattr(settings, 'PROJECT_PATH'):
            raise CommandError("Please add a PROJECT_PATH setting")

        self.src_dir = os.path.join(settings.PROJECT_PATH, 'fixed')
        self.dst_dir = os.path.join(settings.PROJECT_PATH, 'templates', 'fixed')

        if filename == 'all':
            files = glob.glob("%s%s*.rst" % (self.src_dir, os.path.sep))
            files = [os.path.basename(f) for f in files]
        else:
            files = [filename]

        for f in files:
            self.process_page(f)

    def process_page(self, filename):
        """Processes one fixed page"""

        # retrieve source text
        src_path = os.path.join(self.src_dir, filename)
        try:
            with open(src_path, 'r') as f:
                src_text = f.read()
        except IOError, ex:
            raise CommandError(str(ex))

        # transform text
        content = self.transform_input(src_text)

        # write output
        basename = os.path.splitext(os.path.basename(filename))[0]
        dst_path = os.path.join(self.dst_dir, '%s.html' % basename)

        try:
            with open(dst_path, 'w') as f:
                f.write(content.encode('utf-8'))
        except IOError, ex:
            raise CommandError(str(ex))

        prefix = os.path.commonprefix([src_path, dst_path])
        self.stdout.write("%s -> %s\n" % (filename, dst_path[len(prefix):]))

    def transform_input(self, src_text):
        """Transforms input restructured text to HTML"""

        return docutils.core.publish_parts(src_text, writer_name='html',
                settings_overrides={
                    'doctitle_xform': False,
                    'initial_header_level': 2,
                    })['html_body']

Next I would need a template that could render these fragments. I remembered that the Django include tag could take a variable as an argument. Thus I could create a single template that could render all of these "fixed" pages. Here is the template templates/fixed/base.html:

{% extends 'base.html' %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
{% include content_template %}
{% endblock %}

I just need to pass in title and content_template context variables. The latter will control which HTML fragment I include.

I then turned to the view function which would render this template. I wanted to make this as generic and easy to do as possible. Since I was abandoning flatpages, I would need to wire these up in my urls.py. At first I didn't think I could use Django's new class-based generic views for this, but after some fiddling around, I came up with a very nice solution:

from django.views.generic import TemplateView

class FixedView(TemplateView):
    """
    For displaying our "fixed" views generated with the custom command
    make_fixed_page.

    """
    template_name = 'fixed/base.html'
    title = ''
    content_template = ''

    def get_context_data(self, **kwargs):
        context = super(FixedView, self).get_context_data(**kwargs)
        context['title'] = self.title
        context['content_template'] = self.content_template
        return context

This allowed me to do the following in my urls.py file:

urlpatterns = patterns('',
   # ...

   url(r'^about/$',
       FixedView.as_view(title='About', content_template='fixed/about.html'),
       name='about'),
   url(r'^colophon/$',
       FixedView.as_view(title='Colophon', content_template='fixed/colophon.html'),
       name='colophon'),

   # ...

Now I have a way to efficiently serve reStructuredText files as "fixed pages" that I can put under source code control.


Comments

comments powered by Disqus