Skip to content

Useful Plone template debugging functions

As Plone developers, a lot of the problems we have when writing code and templates are only revealed with cryptic, sometimes misleading error messages from somewhere way down the stack from their underlying cause, if at all. When an error is raised, by some template rendering, Zope does provide some useful traceback information specifying the template with line numbers and expressions and whatnot. But why shouldn’t we be able to access this information without raising an error? For example, to diagnose security or redirection problems that aren’t necessarily obvious even with extra logging & verbose security enabled.

The functions provided below allow the developer to gather this kind of feedback and output wherever he or she wishes, without having to provide any arguments that might not always be easy to get from the current part of source code. They work under Zope 2.7 but are untested under other versions. If you do try them out on other versions, please report back in the comments if they do work!

import sys
from Products.CMFCore.utils import getToolByName
from zope.tales.tales import TALESTracebackSupplement

def get_current_template_position():  
    """ If called from a stack frame which has been called from template evaluation, 
        returns a tuple of template filename, line number, column number and 
        TALES expression closest in the stack to the caller. Otherwise, returns None. 
    """  
    i = 0  
    curframe = sys._getframe(i)  
    while True:  
        locals = curframe.f_locals  
        globals = curframe.f_globals  
        if '__traceback_supplement__' in locals:  
            # Use the supplement defined in the function.  
            tbs = locals.get('__traceback_supplement__')  
        elif '__traceback_supplement__' in globals:  
            # Use the supplement defined in the module.  
            # This is used by Scripts (Python).  
            tbs = globals.get('__traceback_supplement__')  
        else:  
            tbs = None  
        if tbs is not None:  
            factory = tbs[0]  
            args = tbs[1:]  
            try:  
                supp = factory(*args)  
            except:  
                continue  
            if type(supp) is TALESTracebackSupplement:  
                return (supp.context, supp.source_url, supp.line, supp.column, supp.expression)  
  
        i=i+1  
        try:  
            curframe = sys._getframe(i)  
            if curframe is None:  
                return None  
        except:  
            return None  
  
def dump_current_template_position(context=None, return_string=False):  
    """ When called, attempts to print to the console the URL of the current request, the 
        authenticated user, the currently executing template file, the line and column 
        currently being evaluated in the file and the expression being evaluated. 
 
        Will not print if called from a stack frame which has been called from template 
        evaluation. May not print if called from a .cpy or .vpy file, depending on 
        permissions to 'print'.
        
        Wherever possible, this function should be called with the 'context' arguement
        specified.
 
        If the optional argument 'return_string' is set to True, the function returns the 
        message that would be output, rather than printing. 
    """  
    tpos = get_current_template_position()  
    if tpos is not None:  
        (ctx, template, line, col, expr) = tpos  
        url = 'Unknown'
        if context is not None:
            try:  
                request = hasattr(context, 'request') and context.request or context.REQUEST  
                url = request.get('ACTUAL_URL')  
            except AttributeError:
                pass
        if url == 'Unknown':
            try:  
                request = hasattr(ctx, 'request') and ctx.request or ctx.REQUEST  
                url = request.get('ACTUAL_URL')  
            except AttributeError:
                pass
                
        member = 'Unknown'
        if context is not None:
            try:
                mtool = getToolByName(context, 'portal_membership')
                member = mtool.getAuthenticatedMember()  
            except AttributeError:
                pass
        if member == 'Unknown':
            try:
                mtool = getToolByName(ctx, 'portal_membership')
                member = mtool.getAuthenticatedMember()  
            except AttributeError:
                pass
            
        output = "tURL: %sntAuth'd as: %sntFile: %sntLine: %sntColumn: %sntExpression: %s" % (url, member, template, line, col, expr)  
        if return_string:  
            return output  
        print output

This may also be called from templates, provided the template has sufficient permissions to call the module it lies in:

<tal:block tal:define="dummy python:modules['myproject.app.utils'].dump_current_template_position(context)" />

Or, as it is mostly used, from code called by templates, simply by importing the function(s) as necessary and calling them with options of your choice. If calling from .cpy or .vpy files, the print command may not work properly, so the dump_current_template_position function may be called with the optional argument return_string set to True and then the result may be logged or printed using alternate methods.