The new look (adding Gibe theme support)
10 Apr 2007
Return visitors may notice a slight difference today. For the first time ever, I'm running a web site theme that someone else created. While it's obviously better than my attempts, that's not why it's here (and who knows how long it'll stay). I'm testing out the theme support in Gibe, my web log engine, which ended up not being particularly hard to do at all, thanks to the way TurboGears' expose decorator works, and a simple decorator of my own.
There were ultimately only three main types of work necessary to do the theming. First was catching use of particular templates and overriding them with another (called "template replacements"), second was providing additional template data to all template renders ("template data"), and finally was fixing the search path for the templating engine so that it will find the right template for includes ("template_search_path", and a little hack).
Back to the infamously naive Plugin class, and add those names as functions to it. When implemented by a theme, it looks like:
class BloggingProPlugin(Plugin):
def enabled(self):
return bool(cherrypy.config.get('bloggingprotheme.on', None))
def template_search_path(self, blog, search_path):
import gibebloggingpro.templates
search_path.insert(0, gibebloggingpro.templates.__path__[0])
def template_data(self, blog, template_data):
td = {
'gibebloggingpro_theme': BloggingProTheme,
}
template_data.update(td)
def template_replacements(self, blog, template_replacements):
template_replacements.update({
'gibe.templates.frontpage': 'gibebloggingpro.templates.archives',
'gibe.templates.post': 'gibebloggingpro.templates.post',
'gibe.templates.archives': 'gibebloggingpro.templates.archives',
})
On the plugin side, an add_plugin_data decorator is added, which gets all the plugin information (ie, the widgets lists for the top of the post and so forth and also plugin-specific data from the template_data from each plugin.
def get_plugin_data(blog):
from gibe import plugin
blogid = blog.blog_id
if blogid not in blog_plugin_data:
blog_plugin_data[blogid] = {
'post_top_widgets': plugin.post_top_widgets(blog, {}),
'post_bottom_widgets': plugin.post_bottom_widgets(blog, {}),
'post_list_top_widgets': plugin.post_list_top_widgets(blog, {}),
'post_list_bottom_widgets': plugin.post_list_bottom_widgets(blog, {}),
'post_edit_widgets': plugin.post_edit_widgets(blog, {}),
'template_search_path': plugin.template_search_path(blog),
'template_search_path': plugin.template_search_path(blog),
'template_replacements': plugin.template_replacements(blog),
}
blog_plugin_data[blogid].update(plugin.template_data(blog))
return blog_plugin_data[blogid]
def add_plugin_data(template=None):
def wrap(func):
def entangle(self, *args, **kw):
ret = func(self, *args, **kw)
blog = kw.get('blog', None)
if not blog:
return ret
if not isinstance(ret, dict):
return ret
ret.update(get_plugin_data(blog))
if template:
ret['tg_template'] = find_template(blog, template)
return ret
return entangle
return wrap
So, put simply, intercept all calls to templates, and set up a search path for files that the templates might include (for example, "master.html"). Provide additional template data if necessary so that the templates have access to additional variables or functions they might want to have available to them.
Adding a TurboGears Widget to the template_data dictionary has the standard effect of adding any required JavaScript and CSS files to the resulting page. This means that adding the necessary resources for a theme is as simple as adding a widget like this:
class BloggingProTheme(Widget):
version = release.version
css = [
CSSLink(resource_name, 'css/style.css'),
]
javascript = [
JSLink(resource_name, 'javascript/imghover.js'),
]
In all, around 100 lines of code to add theme support. The first theme took about an hour and a half to do (a Hemingway-based theme, screenshot below), the second took about an hour (the BloggingPro-by-Design-Disease-based one used at the time of this post, in the image above), and I imagine it can end up taking only a half an hour a theme from now on.

2 old-style comments
kmf — April 10, 2007 at 11:49 AM.
Stii — April 10, 2007 at 05:19 PM.