[z3-five] Adapter and View insanity
Martin Aspeli
optilude at gmx.net
Thu Nov 16 12:56:03 CET 2006
Chris Withers <chris <at> simplistix.co.uk> writes:
> I need to render representations of objects and sometimes override their
> titles, so I came up with an interface:
>
> class IRender(Interface):
>
> def render(context,title=None)
> ""
When you are passing context in like this, you may want to think about having
multi-adapters that adapt the object being rendered and the context it's
rendered in. It may not always be appropriate, of course, but it allows you to
vary the adapter registration by either or both (so override based on object
type and/or context type). If you find that you have multiple functions all
taking that 'context' parameter, it may be a sign that you're really after a
multi-adapter.
> The context parameter is because the rendering can change depending on
> where its being used.
>
> So, this works really well from code and I end up with nice little
> snippets like:
>
> self._html = IRender(obj).render(context,title)
>
> Okay, so now I want to re-use this to render objects inside a ZPT.
>
> My first attempt at this was horrific:
>
> <p tal:content="structure
> python:modules['Products.MyProduct.interfaces'].IRender(item).render
(context,title='Foo')"/>
If the template was really a view, registered with <browser:page>, you'd have
a class in which you cuold put this logic:
class MyView(BrowserView):
def render(self, item, title):
return IRender(item).render(self.context, title)
And then in the template:
tal:replace="python:view.render(item, title)"
Sometimes it may be more appropriate to step back another step and construct
lists of dicts or whatever that have done all the necessary processing and are
used with simple (non-python:) expressions in TAL. Sometimes that may be
overkill.
> Having cleaned up the resulting vomit, I decided to try and use a view,
> as suggested by Martin A. The only way I could seem to do that was to
> have a whole seperate class:
>
> class Render:
>
> def __call__(self):
> return IRender(self.context).render(self.context)
>
> ...which I then have to write zcml to register:
>
> <browser:page
> for="*"
> name="render"
> class=".views.Render"
> permission="zope2.View"/>
>
> Which means I can finally do this:
>
> <p tal:content="structure item/ <at> <at> render"/>
>
> But there's still a few problems:
>
> 1. I'm instantiating yet another class every time I just want to adapt
> something and call a method
So you could instantiate once in a tal:define and put the logic in a method
rather than in __call__().
> 2. I can't pass in a title or explicit context. I have to work the
> context out using acquisition
Again solved by a method.
> 3. I'm deeply disturbed as to how instances of my Render view class come
> to magically have a context attribute. wtf?
It's a browser view. The ZCML directive injects a base class. Yes, it's nasty,
which is why it's normally saner to explicitly inherit from
Products.Five.browser.BrowserView.
> So, anyway I'm left wondering:
>
> - what's the _minimum_ I need to do to be able to use something like:
>
> <p tal:content="structure item/ <at> <at> render"/>
>
> ...to mean "adapt item to IRender and call its render method"?
See above.
> - how do I do the equivalent of item/ <at> <at> render in python? (I'm
hoping
> this will enlighten me as to how I'd pass an explicit context and title in)
You can use restrictedTraverse() if you wish, but really you are doing:
from zope.component import getMultiAdapter
render = getMultiAdapter((context, view), name='render')
print render()
Martin
More information about the z3-five
mailing list