The last time I was talking code (I mean, besides the little example usage snippet when I was announcing TGOpenIDLogin, in between my reporting back about the GeekDinner, the Western Cape Linux User Group  meeting, the Cape Town Python User Group meeting, and so forth), I was showing how I converted the Wordpress Sociable plugin to a TurboGears widget (innovatively named TGSociable).

The ultimate purpose of this was to make a plugin for Gibe (my little weblog engine) which would add the sociable icons with the correct URLs to blog entry pages, without having to put anything sociable-specific in the code.

When writing the comment format "plugin system" so that Gibe could use something other than the built-in TinyMCE, I used pkg_resources to create an "entry point" so that other packages could provide functionality to Gibe, and Gibe would know about them without anything but installing the package.

So, I started with the near-simplest possible Plugin class:

class Plugin(object):
    def post_top_widgets(self, post, widgets, context):
        # widgets to put above the post
        pass


So, plugins can inherit from this Plugin class, and they can modify the list of widgets that will be displayed above posts on the post page.  I settled on passing the widgets through as a list because of the ability to order and to reorder.

I created a gibe.plugins entry point, and at startup, those packages providing entry points are checked, the plugins are loaded, and then are placed into a dictionary:

plugins = {}
for plugin_mod in pkg_resources.iter_entry_points("gibe.plugins"):
    plugin_name = plugin_mod.module_name
    if plugin_mod.attrs:
        plugin_name += ':' + '.'.join(plugin_mod.attrs)

    mod = plugin_mod.load()
    plugins[plugin_name] = mod()

A post_top_widgets function in the plugin module does all the work necessary to get the list of widgets:

def post_top_widgets(post, context):
    post_top_widgets_list = []

    for k, v in plugins.items():
        v.post_top_widgets(post, post_top_widgets_list, context)

    return MemberWidgetList(post_top_widgets_list)

MemberWidgetList is a simple list of widgets that will pass on the CSS and JavaScript checking that TurboGears does so that the widgets can be displayed fully.

Then, the blog entry page just needed to pass through the widget list to the template, which was as simple as adding a single entry into the return values that get passed to the template.

        return dict(blog=blog,
            post = post,
            previous_post = previous_post,
            next_post = next_post,
            url_for = routes.url_for,
            comment_form = commenting.comment_form,
            accept_comments = post.accept_comments,
            post_top_widgets = plugin.post_top_widgets(post, context),
            post_bottom_widgets = plugin.post_bottom_widgets(post, context))

Finally, the template needed to be updated to actually display the widgets:

      <div py:if="post_top_widgets" py:for="widget in post_top_widgets" class="post_top_widgets">
          <span py:replace="tg.ET(widget.display(post = post, blog = blog, post_url=tg.base_url, post_title=post.title, blog_name = blog.name, blog_rss = tg.url_for('rss2.0.xml')))" />
      </div>

Every widget will get the post, the blog, the post URL and title, the blog name, and the blog's RSS location.  This is hopefully enough for it to figure out what to display when it gets told to display itself.  I did the same thing for post_bottom_widgets.

Finally, I need to create a plugin for the TGSociable widget.  I just created a gibeplugin.py file in the TGSociable package (I mean, it's not like anyone else is going to use the thing...):

from gibe.plugin import Plugin
from tgsociable import SociableWidget
import cherrypy

class SociablePlugin(Plugin):
    def __init__(self):
        self.reconfigure()

    def reconfigure(self):
        extra_sites = {
            'muti': {
                'favicon': 'http://muti.co.za/images/favicon.ico',
                'url': 'http://muti.co.za/submit?url=PERMALINK&title=TITLE',
            },
        }

        active_sites = cherrypy.config.get("tgsociable.active_sites", ['muti', 'del.icio.us'])
        self.tgsw = SociableWidget(extra_sites = extra_sites, active_sites = active_sites)

    def post_top_widgets(self, post, wl, context):
        wl.extend([self.tgsw])

Then, add an entry point for the plugin in the setup.py file:

    entry_points = """
    [turbogears.widgets]
    tgsociable = tgsociable.widgets

    [gibe.plugins]
    sociable = tgsociable.gibeplugin:SociablePlugin
    """,

Adding a similar plugin for Amatomu (the South African blogosphere organiser) was even more trivial.  Of course, I'll probably have the API entirely, but my "userbase" will understand...

My next trick, ripping tags out of Gibe core, and reimplementing them as a Gibe plugin was more fun...

blog comments powered by Disqus