Back to index

plone3  3.1.7
Skinnable.py
Go to the documentation of this file.
00001 ##############################################################################
00002 #
00003 # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
00004 #
00005 # This software is subject to the provisions of the Zope Public License,
00006 # Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
00007 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
00008 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
00009 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
00010 # FOR A PARTICULAR PURPOSE.
00011 #
00012 ##############################################################################
00013 """ Base class for object managers which can be "skinned".
00014 
00015 Skinnable object managers inherit attributes from a skin specified in
00016 the browser request.  Skins are stored in a fixed-name subobject.
00017 
00018 $Id: Skinnable.py 81769 2007-11-12 15:42:35Z wichert $
00019 """
00020 
00021 import logging
00022 from thread import get_ident
00023 from warnings import warn
00024 
00025 from AccessControl import ClassSecurityInfo
00026 from Acquisition import aq_base
00027 from Globals import InitializeClass
00028 from OFS.ObjectManager import ObjectManager
00029 from ZODB.POSException import ConflictError
00030 
00031 logger = logging.getLogger('CMFCore.Skinnable')
00032 
00033 
00034 # superGetAttr is assigned to whatever ObjectManager.__getattr__
00035 # used to do.
00036 try:
00037     superGetAttr = ObjectManager.__getattr__
00038 except AttributeError:
00039     try:
00040         superGetAttr = ObjectManager.inheritedAttribute('__getattr__')
00041     except AttributeError:
00042         superGetAttr = None
00043 
00044 _MARKER = object()  # Create a new marker object.
00045 
00046 
00047 SKINDATA = {} # mapping thread-id -> (skinobj, skinname, ignore, resolve)
00048 
00049 class SkinDataCleanup:
00050     """Cleanup at the end of the request."""
00051     def __init__(self, tid):
00052         self.tid = tid
00053     def __del__(self):
00054         tid = self.tid
00055         # Be extra careful in __del__
00056         if SKINDATA is not None:
00057             if SKINDATA.has_key(tid):
00058                 del SKINDATA[tid]
00059 
00060 
00061 class SkinnableObjectManager(ObjectManager):
00062 
00063     security = ClassSecurityInfo()
00064 
00065     security.declarePrivate('getSkinsFolderName')
00066     def getSkinsFolderName(self):
00067         # Not implemented.
00068         return None
00069 
00070     def __getattr__(self, name):
00071         '''
00072         Looks for the name in an object with wrappers that only reach
00073         up to the root skins folder.
00074 
00075         This should be fast, flexible, and predictable.
00076         '''
00077         if not name.startswith('_') and not name.startswith('aq_'):
00078             sd = SKINDATA.get(get_ident())
00079             if sd is not None:
00080                 ob, skinname, ignore, resolve = sd
00081                 if not ignore.has_key(name):
00082                     if resolve.has_key(name):
00083                         return resolve[name]
00084                     subob = getattr(ob, name, _MARKER)
00085                     if subob is not _MARKER:
00086                         # Return it in context of self, forgetting
00087                         # its location and acting as if it were located
00088                         # in self.
00089                         retval = aq_base(subob)
00090                         resolve[name] = retval
00091                         return retval
00092                     else:
00093                         ignore[name] = 1
00094         if superGetAttr is None:
00095             raise AttributeError, name
00096         return superGetAttr(self, name)
00097 
00098     security.declarePrivate('getSkin')
00099     def getSkin(self, name=None):
00100         """Returns the requested skin.
00101         """
00102         skinob = None
00103         sfn = self.getSkinsFolderName()
00104 
00105         if sfn is not None:
00106             sf = getattr(self, sfn, None)
00107             if sf is not None:
00108                if name is not None:
00109                    skinob = sf.getSkinByName(name)
00110                if skinob is None:
00111                    skinob = sf.getSkinByName(sf.getDefaultSkin())
00112                    if skinob is None:
00113                        skinob = sf.getSkinByPath('')
00114         return skinob
00115 
00116     security.declarePublic('getSkinNameFromRequest')
00117     def getSkinNameFromRequest(self, REQUEST=None):
00118         '''Returns the skin name from the Request.'''
00119         if REQUEST is None:
00120             return None
00121         sfn = self.getSkinsFolderName()
00122         if sfn is not None:
00123             sf = getattr(self, sfn, None)
00124             if sf is not None:
00125                 return REQUEST.get(sf.getRequestVarname(), None)
00126 
00127     security.declarePublic('changeSkin')
00128     def changeSkin(self, skinname, REQUEST=_MARKER):
00129         '''Change the current skin.
00130 
00131         Can be called manually, allowing the user to change
00132         skins in the middle of a request.
00133         '''
00134         skinobj = self.getSkin(skinname)
00135         if skinobj is not None:
00136             tid = get_ident()
00137             SKINDATA[tid] = (skinobj, skinname, {}, {})
00138             if REQUEST is _MARKER:
00139                 REQUEST = getattr(self, 'REQUEST', None)
00140                 warn("changeSkin should be called with 'REQUEST' as second "
00141                      "argument. The BBB code will be removed in CMF 2.3.",
00142                      DeprecationWarning, stacklevel=2)
00143             if REQUEST is not None:
00144                 REQUEST._hold(SkinDataCleanup(tid))
00145 
00146     security.declarePublic('getCurrentSkinName')
00147     def getCurrentSkinName(self):
00148         '''Return the current skin name.
00149         '''
00150         sd = SKINDATA.get(get_ident())
00151         if sd is not None:
00152             ob, skinname, ignore, resolve = sd
00153             if skinname is not None:
00154                 return skinname
00155         # nothing here, so assume the default skin
00156         sfn = self.getSkinsFolderName()
00157         if sfn is not None:
00158             sf = getattr(self, sfn, None)
00159             if sf is not None:
00160                 return sf.getDefaultSkin()
00161         # and if that fails...
00162         return None
00163 
00164     security.declarePublic('clearCurrentSkin')
00165     def clearCurrentSkin(self):
00166         """Clear the current skin."""
00167         tid = get_ident()
00168         if SKINDATA.has_key(tid):
00169             del SKINDATA[tid]
00170 
00171     security.declarePublic('setupCurrentSkin')
00172     def setupCurrentSkin(self, REQUEST=_MARKER):
00173         '''
00174         Sets up skindata so that __getattr__ can find it.
00175 
00176         Can NOT be called manually to change skins in the middle of a
00177         request! Use changeSkin for that.
00178         '''
00179         if REQUEST is _MARKER:
00180             REQUEST = getattr(self, 'REQUEST', None)
00181             warn("setupCurrentSkin should be called with 'REQUEST' as "
00182                  "argument. The BBB code will be removed in CMF 2.3.",
00183                  DeprecationWarning, stacklevel=2)
00184         if REQUEST is None:
00185             # self is not fully wrapped at the moment.  Don't
00186             # change anything.
00187             return
00188         if SKINDATA.has_key(get_ident()):
00189             # Already set up for this request.
00190             return
00191         skinname = self.getSkinNameFromRequest(REQUEST)
00192         try:
00193             self.changeSkin(skinname, REQUEST)
00194         except ConflictError:
00195             raise
00196         except:
00197             # This shouldn't happen, even if the requested skin
00198             # does not exist.
00199             logger.exception("Unable to setupCurrentSkin()")
00200 
00201     def _checkId(self, id, allow_dup=0):
00202         '''
00203         Override of ObjectManager._checkId().
00204 
00205         Allows the user to create objects with IDs that match the ID of
00206         a skin object.
00207         '''
00208         superCheckId = SkinnableObjectManager.inheritedAttribute('_checkId')
00209         if not allow_dup:
00210             # Temporarily disable skindata.
00211             # Note that this depends heavily on Zope's current thread
00212             # behavior.
00213             tid = get_ident()
00214             sd = SKINDATA.get(tid)
00215             if sd is not None:
00216                 del SKINDATA[tid]
00217             try:
00218                 base = getattr(self,  'aq_base', self)
00219                 if not hasattr(base, id):
00220                     # Cause _checkId to not check for duplication.
00221                     return superCheckId(self, id, allow_dup=1)
00222             finally:
00223                 if sd is not None:
00224                     SKINDATA[tid] = sd
00225         return superCheckId(self, id, allow_dup)
00226 
00227 InitializeClass(SkinnableObjectManager)