Mercurial > public > pelican-blog
diff content/Coding/016-django-fixed-pages.rst @ 4:7ce6393e6d30
Adding converted blog posts from old blog.
author | Brian Neal <bgneal@gmail.com> |
---|---|
date | Thu, 30 Jan 2014 21:45:03 -0600 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/content/Coding/016-django-fixed-pages.rst Thu Jan 30 21:45:03 2014 -0600 @@ -0,0 +1,172 @@ +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/