src/pyams_skin/skin.py
changeset 474 7bb070e90138
equal deleted inserted replaced
-1:000000000000 474:7bb070e90138
       
     1 #
       
     2 # Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
       
     3 # All Rights Reserved.
       
     4 #
       
     5 # This software is subject to the provisions of the Zope Public License,
       
     6 # Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
       
     7 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
       
     8 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
       
     9 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
       
    10 # FOR A PARTICULAR PURPOSE.
       
    11 #
       
    12 
       
    13 __docformat__ = 'restructuredtext'
       
    14 
       
    15 # import standard library
       
    16 import logging
       
    17 logger = logging.getLogger('PyAMS (skin)')
       
    18 
       
    19 # import interfaces
       
    20 from pyams_skin.interfaces import ISkin, ISkinnable, IUserSkinnable, SkinChangedEvent
       
    21 from pyams_skin.layer import IBaseLayer, IPyAMSLayer
       
    22 from pyams_utils.interfaces.site import ISiteRoot
       
    23 from zope.traversing.interfaces import IBeforeTraverseEvent
       
    24 
       
    25 # import packages
       
    26 from pyams_utils.registry import utility_config
       
    27 from pyams_utils.traversing import get_parent
       
    28 from pyams_utils.zodb import volatile_property
       
    29 from pyramid.events import subscriber
       
    30 from pyramid.threadlocal import get_current_request
       
    31 from pyramid_zope_request import PyramidPublisherRequest
       
    32 from zope.interface import implementer, directlyProvidedBy, directlyProvides
       
    33 from zope.schema.fieldproperty import FieldProperty
       
    34 
       
    35 from pyams_skin import _
       
    36 
       
    37 
       
    38 @implementer(ISkinnable)
       
    39 class SkinnableContent(object):
       
    40     """Skinnable content base class"""
       
    41 
       
    42     _inherit_skin = FieldProperty(ISkinnable['inherit_skin'])
       
    43     _skin = FieldProperty(IUserSkinnable['skin'])
       
    44 
       
    45     @property
       
    46     def can_inherit_skin(self):
       
    47         return get_parent(self, ISkinnable, allow_context=False) is not None
       
    48 
       
    49     @property
       
    50     def inherit_skin(self):
       
    51         return self._inherit_skin if self.can_inherit_skin else False
       
    52 
       
    53     @inherit_skin.setter
       
    54     def inherit_skin(self, value):
       
    55         if self.can_inherit_skin:
       
    56             self._inherit_skin = value
       
    57         del self.skin_parent
       
    58 
       
    59     @property
       
    60     def no_inherit_skin(self):
       
    61         return not bool(self.inherit_skin)
       
    62 
       
    63     @no_inherit_skin.setter
       
    64     def no_inherit_skin(self, value):
       
    65         self.inherit_skin = not bool(value)
       
    66 
       
    67     @property
       
    68     def skin(self):
       
    69         if not self.inherit_skin:
       
    70             return self._skin
       
    71         else:
       
    72             return self.skin_parent.skin
       
    73 
       
    74     @skin.setter
       
    75     def skin(self, value):
       
    76         if not self.inherit_skin:
       
    77             self._skin = value
       
    78         del self.skin_parent
       
    79 
       
    80     @volatile_property
       
    81     def skin_parent(self):
       
    82         if (not self._inherit_skin) and self.skin:
       
    83             return self
       
    84         parent = get_parent(self, ISkinnable, allow_context=False)
       
    85         if parent is not None:
       
    86             return parent.skin_parent
       
    87 
       
    88     def get_skin(self, request=None):
       
    89         parent = self.skin_parent
       
    90         if parent is self:
       
    91             return self.skin
       
    92         elif parent is not None:
       
    93             skin = parent.skin
       
    94             if skin is not None:
       
    95                 if request is None:
       
    96                     request = get_current_request()
       
    97                 return request.registry.queryUtility(ISkin, skin)
       
    98 
       
    99 
       
   100 @implementer(IUserSkinnable)
       
   101 class UserSkinnableContent(SkinnableContent):
       
   102     """User skinnable content base class"""
       
   103 
       
   104 
       
   105 def apply_skin(request, skin):
       
   106     """Apply given skin to request"""
       
   107 
       
   108     def _apply(request, skin):
       
   109         ifaces = [iface for iface in directlyProvidedBy(request)
       
   110                   if not issubclass(iface, IBaseLayer)]
       
   111         # Add the new skin.
       
   112         if isinstance(skin, str):
       
   113             skin = request.registry.queryUtility(ISkin, skin)
       
   114         if skin is not None:
       
   115             ifaces.append(skin.layer)
       
   116             directlyProvides(request, *ifaces)
       
   117             logger.debug("Applied skin {0!r} to request {1!r}".format(skin.label, request))
       
   118 
       
   119     _apply(request, skin)
       
   120     if isinstance(request, PyramidPublisherRequest):
       
   121         request = request._request
       
   122         _apply(request, skin)
       
   123     else:
       
   124         request.registry.notify(SkinChangedEvent(request))
       
   125         request.annotations['__skin__'] = skin
       
   126 
       
   127 
       
   128 @subscriber(IBeforeTraverseEvent, context_selector=ISkinnable)
       
   129 def handle_content_skin(event):
       
   130     """Apply skin when traversing skinnable object"""
       
   131     request = event.request
       
   132     skinnable = event.object
       
   133     if not skinnable.inherit_skin:
       
   134         skin = skinnable.get_skin(request)
       
   135         if skin is not None:
       
   136             apply_skin(request, skin)
       
   137 
       
   138 
       
   139 @subscriber(IBeforeTraverseEvent, context_selector=ISiteRoot)
       
   140 def handle_root_skin(event):
       
   141     """Apply skin when traversing site root"""
       
   142     context = event.object
       
   143     if not ISkinnable.providedBy(context):
       
   144         apply_skin(event.request, PyAMSSkin)
       
   145     elif context.skin is None:
       
   146         apply_skin(event.request, PyAMSSkin)
       
   147 
       
   148 
       
   149 #
       
   150 # Base and default skins
       
   151 #
       
   152 
       
   153 @utility_config(name='PyAMS base skin', provides=ISkin)
       
   154 class PyAMSSkin(object):
       
   155     """PyAMS base skin"""
       
   156 
       
   157     label = _("PyAMS base skin")
       
   158     layer = IPyAMSLayer