I wanted to add Textile support to my Zope 3 ‘snippets’ application. It was quite simple, thanks to Zope 3’s concept of Source Types. Source Types are marker interfaces and text rendering views which render a plain text source type into HTML. The sources that are registered with the system are kept track of in a Zope Vocabulary which could be used to give users input options – plain text? structured text? reStructuredText?
In a component architecture, this is just the type of thing one would want to be able to plug into and extend. Here’s how I added PyTextile support into my application.
First, I added textile.py
into my packages root folder, although it could have been placed anywhere on the Python path.
Second, I created the marker interface and a renderer for it:
from zope.interface import implements from zope.app.publisher.browser import BrowserView from zope.app.renderer.interfaces import ISource, IHTMLRenderer from zope.app.renderer import SourceFactory import textile ## Textile class ITextileSource(ISource): """ Marker interface for textile code. """ TextileSourceFactory = SourceFactory(ITextileSource) class TextileToHTMLRenderer(BrowserView): """ A view to convert textile source to HTML """ implements(IHTMLRenderer) __used_for__ = ITextileSource def render(self): rendered = textile.textile(str(self.context), head_offset=3) if not isinstance(rendered, unicode): rendered = unicode(rendered) return rendered
Now I had to tell Zope about the source type and renderer. In my packages configure.zcml
file, I added the following:
<factory component=".sources.TextileSourceFactory" id="textism.textile" title="Textile" description="Textile source" /> <browser:view name="" for=".sources.ITextileSource" class=".sources.TextileToHTMLRenderer" permission="zope.Public" />
The first configuration statement lets me create a Textile source object by referring to the factory id. This is how another widget or variable could be used to create a source object. I'll show how the factory is used in a moment. The second configuration statement registers a browser view, which is a special kind of "multi-adapter", adapting an object and HTTP Request to an HTML view of the object. Finally, I need to render the Textile source stored in the Snippet Post objects into HTML when being viewed through the web. In a view class I've registered for IPost objects, I have the following method:
class PostView(DublinCoreViews): implements(interfaces.IPostView) __used_for__ = IPost def renderDescription(self): """Renders the description from textile source.""" description = self.context.description source = zapi.createObject('textism.textile', description) view = zapi.getMultiAdapter((source, self.request)) html = view.render() return html
It is the zapi.createObject call which finds and calls the textism.textile
source factory and passes in the 'description' as a value. (Note: self.context
is the reference to the IPost object this view is written for). The next line finds an adapter for the source object and the HTTP Request, which is the TextileToHTMLRenderer class, and then the text is rendered and returned as HTML.
A benefit of all of this is that now any Zope 3 product / package / application that uses the sources system, such as the Zope 3 'ZWiki' implementation, now has Textile as a source option. And with only a tiny bit more work on my application (basically adding another field to the IPost Interface about source type), I could let my users have the option of other source types (simple plain text, structured text, reStructuredText) as well as Textile.