You are not logged in Log in Join
You are here: Home » Members » Toby Dickenson » howto » FunctionTemplate

Log in
Name

Password

 

FunctionTemplate

The Problem

You are developing a Python Product. You have a DTML file which originally contained a little presentation logic, but is now stretching the limits.

One option is to convert the DTML into a python function. This would solve your complexity problems, however you would loose the context-oriented approach that makes DTML suitable for Document Templates (that is, the ability to find parameters based on when the template is used, rather than where it is implemented). You could pass the DTML namespace manually, however everywhere that uses this template would have to be changed to use the more complicated calling convention.

If you were developing a through-the-web product you might consider using an ExternalMethod. However these dont fit seamlessly into a Python Product because you want to write the python function in the body of your class.

A Solution

The class below borrows a little of DTMLs magic to allow a regular python function to make use of context-oriented parameters. It can be used to wrapper a python function, defined in the body of your class.

class MyClass(Implicit,OtherClasses):
    ....much other stuff goes here....

    def example(self,context):
        return 'A method of %s used by %s' % (self.absolute_url(), context['absolute_url'])
    example = FunctionTemplate(example)

The python function must have two parameters. self is as usual, and the second (context) is the DTML namespace of the caller (referred to in DTML by the name _ You could use that name here too if you prefer).

In use, it is exactly like a DTMLMethod. You can call that template through the web as http://an_instance_of_my_object/other/content/example or reference it in DTML as <dtml-var example>.

To call it from python you need to use the DTML calling convention: self.example(None,_,named_parameter=value). Note that you are actually calling the FunctionTemplate wrapper, so the parameter list is different to your original function.

FunctionTemplate.py

from DocumentTemplate.DT_Util import TemplateDict,InstanceDict
from ExtensionClass import Base
from Acquisition import Implicit

class FunctionTemplate(Implicit):
    """A wrapper to use a python function as a Document Template.
    This is useful if you want to promote a DTMLMethod into a python function
    (to better handle complex logic) but to not want to loose context-oriented
    nature of DTML.
    """

    isDocTemp=1

    # Prevent acquisition from our container
    index_html=None

    # Templates masquerade as functions:
    class func_code: pass
    func_code=func_code()
    func_code.co_varnames='self','REQUEST'
    func_code.co_argcount=2
    func_defaults=()

    # Only used for wrapping trusted content
    validate = None 
    
    def __init__(self,fn,**globals):
        self._fn = fn
        self._globals = globals

    def __call__(self,client=None,context={},**extras):
        md = TemplateDict()
        md.validate = self.validate
        md._push(self._globals)
        if context:
            md._push(context)
        if client is not None:
            md._push(InstanceDict(client, md)) # Circ. Ref. 8-|
        if extras:
            md._push(extras)
        try:
            real_self = self.aq_inner.aq_parent
            fn = self._fn
            return fn(real_self,md)
        finally:
            while md:
                md._pop()