Adapters in a Web ApplicationΒΆ

While using physical adapters to connect laptops is something that we can understand, where would using adapters in a web application make sense?

While adapters can perform any action, one use for them is to add additional, reusable HTML specific functionality to your application.

Let’s say that you have a Model component called Article. You’ve developed two different Views for your Article: PlainArticleView and FancyArticleView. Your Article has a keywords property, and you’d like to render the HTML meta tag using the keywords from an Article. You might write:

class PlainArticleView(grok.View):
    # lots of other code here
    def meta_tags(self):
        if len(self.context.keywords) > 0:
            return '<meta name="keywords" content="%s" />' % ','.join(
                self.context.keywords()
            )

class FancyArticleView(grok.View):
    # lots of other code here
    def meta_tags(self):
        if len(self.context.keywords) > 0:
            return '<meta name="keywords" content="%s" />' % ','.join(
                self.context.keywords()
            )

Well, that doesn’t look so good does it? We’ve stated the logic of how to convert our keywords into a meta tag in two different places. Oh the horrors, what will all those DRY loving Web 2.0 programmers think of us? Let’s write an Adapter that can render our meta tags:

class IMetaTags(Interface):
    def render(self):
        "HTML Meta Tags"

class MetaTags(grok.Adapter):
    "HTML Meta tags"
    grok.implements(IMetaTags)
    grok.adapts(IArticle)

    def render(self):
        if len(self.context.keywords) > 0:
            return '<meta name="keywords" content="%s" />' % (
                ','.join(self.context.keywords()
            )

class PlainArticleView(grok.View):
    # lots of other code here
    def meta_tags(self):
        return IMetaTags(self.context).render()

class FancyArticleView(grok.View):
    # lots of other code here
    def meta_tags(self):
        return IMetaTags(self.context).render()

Now our presentation logic is stated in only one place, which is good. Although you might be thinking, “Is this worth all the trouble, my PHP-loving Web 1.0 programmers are saying I’m just making my code harder to understand.” This is a good point, and sometimes KISS is the enemy of DRY. But let’s say that our application is expected to have a long lifecycle, where you know you’ll be wanting to extend the application functionality frequently. Let’s see how this extra work we have done can help us down the road. You’ve added a description property to your Article, and you’d like to have a description meta tag. So you change the MetaTags adapter to look like this:

class MetaTags(grok.Adapter)
    "XHTML Meta tags"
    grok.implements(IMetaTags)
    grok.adapts(IArticle)

    def render(self):
        html = ''
        if len(self.context.keywords) > 0:
            html += '<meta name="keywords" content="%s" />' % (
                ','.join(self.context.keywords()
            )
        if len(self.context.description) > 0:
             html += '<meta name="description" content="%s" />' % (
                 ','.join(self.context.keywords()
             )
        return html

Now both of your Views have been updated, and you only need to change the code in one place. Later on you add new HowTo Model to your application, and you create some Views for it. You are careful to make sure that your HowTo model has both a keywords and a description property, and now you just need to allow your adapter to work with both the HowTo and the Article models. Interfaces can inherit from other interfaces, just like a normal Python Class, so you can make explicit the relationship between your metadata and your content types.

from zope.interface import Interface
from zope.schema import List, TextLine, SourceText

class IMetadata(Interface):
    "Metadata about a content type"
    description = TextLine(title=u'short summary of the content',)
    keywords = List(
                 title=u"Keywords",
                 unique=True,
                 value_type=TextLine(title=u"Keyword"),
                 required=False
               )

class IArticle(IMetadata):
    body = SourceText(title=u'Article Body')

class IHowTo(IMetadata):
    body = SourceText(title=u'HowTo Body')

Finally you just need to change the line of your MetaTags adapter from grok.adapts(IArticle) to grok.adapts(IMetadata). Now all of the Views that you write for your HowTo can reuse the code that you were using to support the Views for your Article.