=============================
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:
.. code-block:: python
class PlainArticleView(grok.View):
# lots of other code here
def meta_tags(self):
if len(self.context.keywords) > 0:
return '' % ','.join(
self.context.keywords()
)
class FancyArticleView(grok.View):
# lots of other code here
def meta_tags(self):
if len(self.context.keywords) > 0:
return '' % ','.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:
.. code-block:: python
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 '' % (
','.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:
.. code-block:: python
class MetaTags(grok.Adapter)
"XHTML Meta tags"
grok.implements(IMetaTags)
grok.adapts(IArticle)
def render(self):
html = ''
if len(self.context.keywords) > 0:
html += '' % (
','.join(self.context.keywords()
)
if len(self.context.description) > 0:
html += '' % (
','.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.
.. code-block:: python
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.