Tag Archives: Plone

Setting the modification date of an Archetype object in Plone.

Modification dates of Archetypes objects are set automatically to the current
date and time when an object is reindexed. Usually this is useful behaviour but if
,for example, you’re importing content from elsewhere into a Plone site and you’d
like to set the modification date to be some time in the past then it’s a problem.

Heres a method that sets the standard dates of an Archetypes content type ‘obj’
to DateTime ‘dt’.

def setObjDate(obj, dt):
    """Prevent update of modification date 
       during reindexing"""
    obj.setCreationDate(dt)
    obj.setEffectiveDate(dt)
    obj.setModificationDate(dt)
    od = obj.__dict__
    od['notifyModified'] = lambda *args: None
    obj.reindexObject()
    del od['notifyModified']

The trick, based on migration code found in the plone.app.blob package, is
to mask the ‘notifyModified’ class method with a no-op lambda on our
instance, call the reindex, then drop the mask so that the class method is visible
once again.

I’ve used this successfully for importing data into a Plone 4 instance but it should work at least as far back as Plone 2.5.

On Ubuntu Python, Exceptions and unnecessary imports

A few days ago Alexander Limi (one of Plone’s founders) tweeted the following:

Ubuntu Python: Raise an exception, import 190 modules: http://bit.ly/bCxlhC – this is why you don’t want to use the system Python.

Now this gets my goat on a few points. First up, why the hell would I not want to use the system python? If I’m using any sane distribution I’ll have package management and security updates, and any flaw in Python will be patched, packaged and tested by people that are far smarter than me. Upgrading the Python that ships with the Plone Unified Installer just isn’t going to be as easy, however you play it. And that’s without the risk of the Plone community moving on to more exciting things, leaving their version of Python unsupported.

Secondly, there’s a fatal flaw in the original blog post to which limi refers. Yes, on the desktop, Ubuntu imports 190 packages when an exception is raised. As the author explains this is to enable Apport to provide as much information to the Ubuntu devs about application failures. What the author does not mention is that this doesn’t happen on the Server edition of Ubuntu. Why would it? Apport is designed to handle desktop application failures and to improve the end user experience. It isn’t installed by default on the server edition, because it isn’t needed.

On my Karmic desktop:

Python 2.6.4 (r264:75706, Dec  7 2009, 18:43:55) 
[GCC 4.4.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> len(sys.modules)
35
>>> raise KeyError
Traceback (most recent call last):
  File "", line 1, in 
KeyError
>>> len(sys.modules)
225

On my Karmic server:

Python 2.6.4 (r264:75706, Dec  7 2009, 18:43:55) 
[GCC 4.4.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> len(sys.modules)
32
>>> raise KeyError
Traceback (most recent call last):
  File "", line 1, in 
KeyError
>>> len(sys.modules)
32

Being quick to condemn Ubuntu, and their packaging of Python, doesn’t do anyone any good. Think before you tweet. And don’t go live on a desktop distro.

Javascript localization within Plone

For a while now, I’ve been trying to think of the best way to get localized strings into Javascript under Plone. Many existing packages and parts of Plone use one Javascript file per language, containing all translations. Whilst this is effective, it doesn’t “feel right” having all your translations distributed to the user on each page load, much less having them drawn from two different sources. Worse still (in my mind) is having translations dumped out into hidden HTML elements and then recalled by Javascript using
getElementById

or some such.

The solution I’ve eventually settled on is one that combines AJAX and client-side caching in cookies to minimize page load time. The implementation consists of a single view, a Javascript file and a bunch of XML & ZCML to tie it all together.

The powers that be here at Isotoma, have kindly agreed to let me open source this under the Apache 2.0 license.

Full documentation can be found for the code can be found in the
README.txt

, however I’ve included a quickstart guide below.

Continue reading

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: %s\n\tAuth'd as: %s\n\tFile: %s\n\tLine: %s\n\tColumn: %s\n\tExpression: %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.

Get a content type class by name in Zope

I recently had the problem of finding out if a content type in Plone implemented a certain interface, but only given the name of the content type rather than an instantiated object or its class. Whilst it’s easy to create an instance given a content type name, using the Portal Type Tool, creating an instance to test and then deleting it felt wrong. Unfortunately the Types Tool seems to provide no easy way of determining the class of a given type.

The solution comes from a combination of the Portal Types Tool’s type information and the Archetypes Tool’s information about the implementation:

from Products.CMFCore.utils import getToolByName

def has_interface(portal, typeName, interface):
    """ Portal is the root portal object, typeName is a
        string such as 'MyDocumentType' and interface
        is the interface class to be tested against. """

    pt = getToolByName(portal, 'portal_types')
    at = getToolByName(portal, 'archetype_tool')
    typeinfo = pt.get(typeName)
    if typeinfo:
        package = typeinfo.product
        type = at.lookupType(package, typeName)
        if type:
            klass = type['klass']
            return interface.isImplementedByInstancesOf(klass)
    return False

LinguaPlone and redirection

One of our current projects working with Plone requires multilingual content using the LinguaPlone product. One of the features that has been lost since LinguaPlone was first released was the redirection to content in the user’s preferred language when given a hard link to content in another language. As mentioned in this rejected bug report from 2006, the desired functionality is that there should be no redirection however this approach doesn’t leave any room for flexibility in the name of usability.

One suggested solution in the bug report is to re-add old code from previous versions of LinguaPlone but what if one doesn’t want redirection on all translatable views?

The solution we came up with was to override the
__call__

method of the view using our own base class, which could be inherited by views which we wished to redirect:

from Products.Five.browser import BrowserView
from Products.CMFCore.utils import getToolByName

class MyBaseView (BrowserView):

    def __call__(self, *args, **kwargs):
        
        if self.context == self.context.request.other['PARENTS'][0]:
            # Don't try to redirect if we're not in the context of the object originally called.
            # Otherwise, if child objects' views are called the whole view may be redirected
            # to that child in exceptional circumstances.
            
            try:
                # Import may fail if LinguaPlone not enabled.
                from Products.LinguaPlone.interfaces import ITranslatable
                
                redirect = self.context.request.get('i18nredirect')
                if (not redirect or redirect.lower() not in ['no','false']) \
                        and ITranslatable.providedBy(self.context):
                    
                    language_tool = getToolByName(self.context, 'portal_languages', None)
                    
                    # Check current selected language, language of object sought & available translations
                    if language_tool is not None:
                        preferred_lang = language_tool.getPreferredLanguage()
                        if self.context.getLanguage() != preferred_lang and self.context.hasTranslation(preferred_lang):
                            translation = self.context.getTranslation(preferred_lang)
                            return self.context.request.RESPONSE.redirect(translation.absolute_url_path())
            except ImportError:
                if 'LinguaPlone' not in e.message:
                    raise e
        
        # Resume normal behaviour
        return super(MyBaseView,self).__call__(args, kwargs)

This allows us add the redirect functionality arbitrarily if we wish, and also retains a method of hard-linking using the
i18nredirect

GET variable. This functionality could of course be reversed, leaving the redirection as the special case rather than the default.