Skin layers

Description

Skin layers are a legacy Plone 2 technology, still is use, for adding overridable templates and media resources to Plone packages.

Introduction

Skin layers, portal_skins and CMFCore.SkinsTool are the old-fashioned way to manage Plone templates.

  • Each Plone theme has set of folders it will pick from portal_skins. These sets are defined in portal_skins -> properties.
  • Skins layers are searched for a template by template name, higher layers first.
  • Skin layers can be reordered through-the-web in portal_skins -> properties

Defining a skin layer

Skin files are placed in the skins folder of your add-on product.

The structure looks like this:

  • yourproduct/namespace/configure.zcml
  • yourproduct/namespace/profiles/default/skins.xml
  • yourproduct/namespace/skins
  • yourproduct/namespace/skins/layer1folder
  • yourproduct/namespace/skins/layer2folder/document_view.pt
  • yourproduct/namespace/skins/layer2folder
  • ...

GenericSetup skins.xml:

<?xml version="1.0"?>
<object name="portal_skins" meta_type="Plone Skins Tool">
 <object name="headeranimation" meta_type="Filesystem Directory View"
         directory="plone.app.headeranimation:skins/headeranimation"/>
  <skin-path name="*">
    <layer name="headeranimation" insert-after="custom"/>
  </skin-path>
</object>

ZCML to register the layer:

<configure
    ...
    xmlns:cmf="http://namespaces.zope.org/cmf">

    <cmf:registerDirectory name="skins" directory="skins" recursive="True" />

</configure>

See also

Unit testing and portal_skins

If you test templates in your unit testing code you might need to call PloneTestCase._refreshSkinData():

def afterSetUp(self):
    # Must be called to load our add-on skins folders
    # for unit testing
    self._refreshSkinData()

Activating the current skin layer from a debug/ipzope shell

The skin needs to be initialised before its files can be accessed e.g. via restrictedTraverse:

portal.setupCurrentSkin()

Rendering a skin layer template

Templates must be bound to a context object before rendering. Plone acquisition magic maps templates as acquired attributes of all contentish objects.

Example:

# Any page object
doc = portal.doc

# portal_skins/plone_content/document_view.pt template bound to document
doc.document_view

# Resulting HTML is rendered when template object is called
doc.document_view()

Testing templates

Below is some example code how templates behave.

Example:

(Pdb) doc
<ATDocument at /plone/doc>
(Pdb) template = doc.document_view
(Pdb) template
<FSPageTemplate at /plone/document_view used for /plone/doc>
(Pdb) template._filepath
'/home/moo/workspace2/plone.app.headeranimation/plone/app/headeranimation/skins/headeranimation/document_view.pt'

Nested folder overrides (z3c.jbot)

z3c.jbot allows to override any portal_skins based file based on its file-system path + filename.

Example jbot ZCML slug (no layers, unconditional overrides)

<configure
    xmlns="http://namespaces.zope.org/zope"
    xmlns:five="http://namespaces.zope.org/five"
    xmlns:i18n="http://namespaces.zope.org/i18n"
    xmlns:browser="http://namespaces.zope.org/browser"
    >

    <browser:jbot directory="jbot" />

Then your add-on has folder structure (example):

yourcompany.app/yourcompany/app/jbot
yourcompany.app/yourcompany/app/jbot/Products.TinyMCE.skins.tinymce.plugins.table.js.table.js
yourcompany.app/yourcompany/app/jbot/Products.TinyMCE.skins.tinymce.plugins.table.html.pt

For layered example (theme layer, add-on layer), see

More info

Poking portal_skins

portal_skins is a persistent tool in Plone site root providing functions to manage skin layers. Its code mostly lives in Products.CMFCore.SkinsTool.

Available skin layers are directly exposed as traversable attributes:

(Pdb) for i in dir(portal_skins): print i
ATContentTypes
ATReferenceBrowserWidget
CMFEditions
COPY
COPY__roles__
ChangeSet
DELETE
...
plone_3rdParty
plone_content
plone_deprecated
plone_ecmascript
plone_form_scripts
plone_forms
plone_images
plone_login
plone_portlets
plone_prefs
plone_scripts
plone_styles
plone_templates

portal_skins.getSkinSelections() will list available skins.

You can edit a specific skin layer:

skin = portal_skins.getSkinByName("Go Mobile Default Theme")

portal_skins.selections is a PersistentDict object holding skin name -> comma separated layer list mappings.

Dumping a portal_skins folder to the filesystem

qPloneSkinDump can build a filesystem dump from portal_skins but it only works on Plone 2. If you need this functionality you can try to use this script ripped off qPloneSkinDump: https://gist.github.com/silviot/5402869. It is a WorksForMe quality script; replace the variables and run it with:

bin/instance run export_skin_folder.py