Mercurial > public > pelican-blog
comparison 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 |
comparison
equal
deleted
inserted
replaced
3:c3115da3ff73 | 4:7ce6393e6d30 |
---|---|
1 Fixed pages for Django | |
2 ###################### | |
3 | |
4 :date: 2012-05-13 13:30 | |
5 :tags: Django | |
6 :slug: fixed-pages-for-django | |
7 :author: Brian Neal | |
8 | |
9 I have been using Django's `flatpages app`_ for some simple, static pages that | |
10 were supposed to be temporary. I slapped a Javascript editor on the admin page | |
11 and it has worked very well. However some of the pages have long outlived their | |
12 "temporary" status, and I find myself needing to update them. It is then that I | |
13 get angry at the Javascript editor, and there is no way to keep any kind of | |
14 history on the page without having to fish through old database backups. I | |
15 started to think it would be nice to write the content in a nice markup | |
16 language, for example reStructuredText_, which I could then commit to version | |
17 control. I would just need a way to generate HTML from the source text to | |
18 produce the flatpage content. | |
19 | |
20 Of course I could use the template filters in `django.contrib.markup`_. But | |
21 turning markup into HTML at page request time can be more expensive than I like. | |
22 Yes, I could cache the page, but I'd like the process to be more explicit. | |
23 | |
24 In my first attempt at doing this, I wrote a `custom management command`_ that | |
25 used a dictionary in my ``settings.py`` file to map reStructuredText files to | |
26 flatpage URLs. My management command would open the input file, convert it to | |
27 HTML, then find the ``FlatPage`` object associated with the URL. It would then | |
28 update the object with the new HTML content and save it. | |
29 | |
30 This worked okay, but in the end I decided that the pages I wanted to update | |
31 were not temporary, quick & dirty pages, which is kind of how I view flatpages. | |
32 So I decided to stop leaning on the flatpages app for these pages. | |
33 | |
34 I then modified the management command to read a given input file, convert it | |
35 to an HTML fragment, then save it in my templates directory. Thus, a file stored | |
36 in my project directory as ``fixed/about.rst`` would get transformed to | |
37 ``templates/fixed/about.html``. Here is the source to the command which I saved | |
38 as ``make_fixed_page.py``: | |
39 | |
40 .. sourcecode:: python | |
41 | |
42 import os.path | |
43 import glob | |
44 | |
45 import docutils.core | |
46 from django.core.management.base import LabelCommand, CommandError | |
47 from django.conf import settings | |
48 | |
49 | |
50 class Command(LabelCommand): | |
51 help = "Generate HTML from restructured text files" | |
52 args = "<inputfile1> <inputfile2> ... | all" | |
53 | |
54 def handle_label(self, filename, **kwargs): | |
55 """Process input file(s)""" | |
56 | |
57 if not hasattr(settings, 'PROJECT_PATH'): | |
58 raise CommandError("Please add a PROJECT_PATH setting") | |
59 | |
60 self.src_dir = os.path.join(settings.PROJECT_PATH, 'fixed') | |
61 self.dst_dir = os.path.join(settings.PROJECT_PATH, 'templates', 'fixed') | |
62 | |
63 if filename == 'all': | |
64 files = glob.glob("%s%s*.rst" % (self.src_dir, os.path.sep)) | |
65 files = [os.path.basename(f) for f in files] | |
66 else: | |
67 files = [filename] | |
68 | |
69 for f in files: | |
70 self.process_page(f) | |
71 | |
72 def process_page(self, filename): | |
73 """Processes one fixed page""" | |
74 | |
75 # retrieve source text | |
76 src_path = os.path.join(self.src_dir, filename) | |
77 try: | |
78 with open(src_path, 'r') as f: | |
79 src_text = f.read() | |
80 except IOError, ex: | |
81 raise CommandError(str(ex)) | |
82 | |
83 # transform text | |
84 content = self.transform_input(src_text) | |
85 | |
86 # write output | |
87 basename = os.path.splitext(os.path.basename(filename))[0] | |
88 dst_path = os.path.join(self.dst_dir, '%s.html' % basename) | |
89 | |
90 try: | |
91 with open(dst_path, 'w') as f: | |
92 f.write(content.encode('utf-8')) | |
93 except IOError, ex: | |
94 raise CommandError(str(ex)) | |
95 | |
96 prefix = os.path.commonprefix([src_path, dst_path]) | |
97 self.stdout.write("%s -> %s\n" % (filename, dst_path[len(prefix):])) | |
98 | |
99 def transform_input(self, src_text): | |
100 """Transforms input restructured text to HTML""" | |
101 | |
102 return docutils.core.publish_parts(src_text, writer_name='html', | |
103 settings_overrides={ | |
104 'doctitle_xform': False, | |
105 'initial_header_level': 2, | |
106 })['html_body'] | |
107 | |
108 Next I would need a template that could render these fragments. I remembered | |
109 that the Django `include tag`_ could take a variable as an argument. Thus I | |
110 could create a single template that could render all of these "fixed" pages. | |
111 Here is the template ``templates/fixed/base.html``:: | |
112 | |
113 {% extends 'base.html' %} | |
114 {% block title %}{{ title }}{% endblock %} | |
115 {% block content %} | |
116 {% include content_template %} | |
117 {% endblock %} | |
118 | |
119 I just need to pass in ``title`` and ``content_template`` context variables. The | |
120 latter will control which HTML fragment I include. | |
121 | |
122 I then turned to the view function which would render this template. I wanted to | |
123 make this as generic and easy to do as possible. Since I was abandoning | |
124 flatpages, I would need to wire these up in my ``urls.py``. At first I didn't | |
125 think I could use Django's new `class-based generic views`_ for this, but after | |
126 some fiddling around, I came up with a very nice solution: | |
127 | |
128 .. sourcecode:: python | |
129 | |
130 from django.views.generic import TemplateView | |
131 | |
132 class FixedView(TemplateView): | |
133 """ | |
134 For displaying our "fixed" views generated with the custom command | |
135 make_fixed_page. | |
136 | |
137 """ | |
138 template_name = 'fixed/base.html' | |
139 title = '' | |
140 content_template = '' | |
141 | |
142 def get_context_data(self, **kwargs): | |
143 context = super(FixedView, self).get_context_data(**kwargs) | |
144 context['title'] = self.title | |
145 context['content_template'] = self.content_template | |
146 return context | |
147 | |
148 This allowed me to do the following in my ``urls.py`` file: | |
149 | |
150 .. sourcecode:: python | |
151 | |
152 urlpatterns = patterns('', | |
153 # ... | |
154 | |
155 url(r'^about/$', | |
156 FixedView.as_view(title='About', content_template='fixed/about.html'), | |
157 name='about'), | |
158 url(r'^colophon/$', | |
159 FixedView.as_view(title='Colophon', content_template='fixed/colophon.html'), | |
160 name='colophon'), | |
161 | |
162 # ... | |
163 | |
164 Now I have a way to efficiently serve reStructuredText files as "fixed pages" | |
165 that I can put under source code control. | |
166 | |
167 .. _flatpages app: https://docs.djangoproject.com/en/1.4/ref/contrib/flatpages/ | |
168 .. _reStructuredText: http://docutils.sourceforge.net/rst.html | |
169 .. _custom management command: https://docs.djangoproject.com/en/1.4/howto/custom-management-commands/ | |
170 .. _include tag: https://docs.djangoproject.com/en/1.4/ref/templates/builtins/#include | |
171 .. _class-based generic views: https://docs.djangoproject.com/en/1.4/topics/class-based-views/ | |
172 .. _django.contrib.markup: https://docs.djangoproject.com/en/1.4/ref/contrib/markup/ |