Roel Bruggink
Roel Bruggink
4 augustus 2009

Changing Plone theme/skin based on portal_type

Changing your Plone theme/skin based on the object's portal_type using a skin switcher.

Update

I have updated this post to fix some issues. There where problems with VHM style virtual hosting (because of the /virtual_hosting added to TraversalRequestNameStack) and I fixed my catalog query (shame on me).

As Deichi pointed out, you need Plone >= 3.1 because of IThemeSpecific. We have tested this on Plone 3.2.2 and 3.2.3, but it should work on Plone >= 3.1.

Objective

I have a folder structure like (section)[/random]/(department)[/random]/(project)[/random], and the folders project, department and district need to have a different theme. This sounds like a fairly easy task, but that couldn’t be futher away from the truth.

Problems all over

According to Maurits’ blog on Skin switching and Weblions’ “Theme switching by URL, folder, page, etc.”, IBeforeTraverseEvent should be used. I thought it would give me every object in the url, but you guessed it: it didn’t. It just gave me the Plone Site object. I then implemented bobo_traverse in the contenttypes. Either way, things like browser resources didn’t work because changeSkin just changes the theme for the request it’s in. And because everything inside the themes is IThemeSpecific and the currently active theme is a different theme, I got error 404 galore.

Solution

Using IBeforeTraverseEvent is the way to go to hijack every request. We need to do that, so portal_css and portal_js also works like expected. We need to take three steps:

In configure.zcml:
1. Configure a event handler

<subscriber
 for="plone.app.layout.navigation.interfaces.INavigationRoot
 zope.app.publication.interfaces.IBeforeTraverseEvent"
 handler=".skinswitcher.setskin" />

In skinswitcher.py:
2. Define the event handler that sets the theme.

def setskin(site, event):
"""Eventhandler to set the skin"""
    skin_name = findMeMySkin(site, event.request)
    portal = getToolByName(site, 'portal_url').getPortalObject()
    portal.changeSkin(skin_name, event.request)

3. And we need some code to find out which theme should be used.

def findMeMySkin(site, request):
    # define possible skins
    PROJECT_THEME = 'Project Plone Theme'
    AFDELING_THEME = 'Afdeling Plone Theme'
    DISTRICT_THEME = 'District Plone Theme'
    DEFAULT = 'Plone Tableless'
    skins=[PROJECT_THEME, AFDELING_THEME, DISTRICT_THEME, DEFAULT]

    # map portal_type to theme
    mapping={
        'Project': PROJECT_THEME,
        'Afdeling': AFDELING_THEME,
        'District': DISTRICT_THEME,
    }

    if not request.TraversalRequestNameStack:
        return DEFAULT

    # reverse to look from the root up
    stack=[]
    stack.extend(request.TraversalRequestNameStack)
    stack.extend(site.getPhysicalPath())
    stack=[x for x in stack if x and x!='/' and x!='virtual_hosting']
    stack.append('')
    stack=stack[::-1]

    # support portal_css and portal_js
    for item in stack:
        if item in skins:
            return item

    # check objects
    portal_catalog = getToolByName(site, 'portal_catalog')
    while stack:
        item=stack.pop()
        path = '/'.join(stack)

        query={}
        query['id']=str(item)
        query['path'] = {'query' : path}

        brains = portal_catalog.unrestrictedSearchResults(query)
        if brains:
            brain=brains[0]
            portal_type=brain.portal_type
            try:
                return mapping[portal_type]
            except:
                pass

    return DEFAULT
We love code