Category Archives: Archetypes

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.

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.

Using TextIndexNG3 with Archetypes and automated testing

I’m currently working on the latest version of our Business Directory product for Business Link Kent (it should be going live on 8th Feb, in fact).

Because of the complex searching requirements plain old ZCTextIndex or TextIndex just aren’t enough, and we’ve incorporated the very powerful TextIndexNG3 instead.

Unfortunately the installation documentation and so on doesn’t really deal with implementing new indexes using the product. Nor does it show how to programatically convert the pre-existing indexes upon installation of your product; instead it is described as manual steps.

Manual steps do not play nice with either our automated testing strategy or the fact that we blow away and rebuild our development environments 3 or 4 times a day.

In your Install.py (yes, this is a Plone 2.1 site and no, I’m not using Generic Setup for this one) put the following function (I’m using stemming here, so don’t copy this blindly if you don’t want the effects of stemming):

Then call updateIndexes(out) from your install method and lo! your indexes are converted by your install script, rather than having to do it manually each time.

To implement new TextIndexNG3 indexes from within your Archetypes schema definition is very simple. I’m still debating whether I should be making use of the broader ability of TextIndexNG3 to search across a subset of the fields that it indexes (meaning that I might not need to create multiple TextIndexNG3 indexes), but for right now I’m going the simple route and defining them on the schema as I would for any other type of index.

Once you’ve installed TextIndexNG3 have a look at Products/TextIndexNG3/src/config.py. In there are all the parameters that you can pass at the point of index creation. To do this in Archetypes it’s exactly the same as it would be for any other index; any non-default creation parameters should be comma separated after the index type is specified. For example:

So far the product looks very powerful, and it’s definitely sorted all my complex searching requirements (ranges, booleans and so forth) that until now had me completely stumped.

Archetypes validation on the basis of the value of more than one field

In the general category of ‘you learn something new every day’…. Today I’m back using Plone (it’s been a while!). I’m trying to validate a particular field on the basis of the value of another. Specifically I’m trying to validate a postcode depending on the selected country (so use different validators depending on the country the user has chosen).
If you use Archetypes validators it initially appears as if you are passed the entire object as well as the new value (kwargs['instance']), but unfortunately the validator is called 3 times, and the first two calls have the changed value that you’re trying to validate, but the unchanged object. What that means is if the user changed the postcode and the country in the same edit the validator would be using the wrong country on the first call, and raise an error.
Some digging around led me to this reference on the plone site which describes how to correctly validate across multiple fields. Instead of registering a validator as usual, define a pre_validation or post_validation method on your object, pull the values that you need from the REQUEST object, perform your validation as required and write any errors to the errors object. It’s not quite as elegant as the general validation machinery, but it certainly does the job.

Caching Archetypes ImageFields with Squid

This is an issue that has cropped up on a couple of our sites. We generally only use Squid to cache images, javascript and css. Cached Javascript and CSS are not a problem in Plone, because the resource tools change the url of the javascript and css every time a new version is released. That way we can have extremely aggressive caching strategies for them.

Images are another matter however. We certainly don’t want to serve them from Zope if we can avoid it – but caching is a problem when the images are changed.

Enter CMFSquidTool. This patches the catalog reindexing hook to generate a sequence of PURGE commands to Squid, for the item being reindexed. This works great if you are using a single Archetypes object to store your content, but if you have anything like an ImageField it doesn’t work so well. You get a PURGE generated for your object, but not for it’s dependent images — which might be what changed.

So, I’ve pached CMFSquidTool to look for ImageFields in the object it’s purging, and generate additional PURGE commands for all images, and all of their different sized thumbnails. This works well, and solves what is otherwise a difficult issue.

I’d like to produce an adaptor-based solution for this – With an interface for objects and fields that says what purgable urls they have, if any, and adapters for the base Archetype object and it’s various fields. Does anyone know if anything like this exists already?