Mercurial > public > pelican-blog
view content/Coding/016-django-fixed-pages.rst @ 7:49bebfa6f9d3
Added summary lines for those posts that had back to back headings.
Those posts didn't look so great on Pelican's index.
author | Brian Neal <bgneal@gmail.com> |
---|---|
date | Fri, 31 Jan 2014 20:32:36 -0600 |
parents | 7ce6393e6d30 |
children |
line wrap: on
line source
Fixed pages for Django ###################### :date: 2012-05-13 13:30 :tags: Django :slug: fixed-pages-for-django :author: Brian Neal 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``: .. sourcecode:: python 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: .. sourcecode:: python 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: .. sourcecode:: python 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. .. _flatpages app: https://docs.djangoproject.com/en/1.4/ref/contrib/flatpages/ .. _reStructuredText: http://docutils.sourceforge.net/rst.html .. _custom management command: https://docs.djangoproject.com/en/1.4/howto/custom-management-commands/ .. _include tag: https://docs.djangoproject.com/en/1.4/ref/templates/builtins/#include .. _class-based generic views: https://docs.djangoproject.com/en/1.4/topics/class-based-views/ .. _django.contrib.markup: https://docs.djangoproject.com/en/1.4/ref/contrib/markup/