diff -r 000000000000 -r 7bb070e90138 src/pyams_skin/skin.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_skin/skin.py Wed Dec 05 13:13:47 2018 +0100 @@ -0,0 +1,158 @@ +# +# Copyright (c) 2008-2015 Thierry Florac +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# + +__docformat__ = 'restructuredtext' + +# import standard library +import logging +logger = logging.getLogger('PyAMS (skin)') + +# import interfaces +from pyams_skin.interfaces import ISkin, ISkinnable, IUserSkinnable, SkinChangedEvent +from pyams_skin.layer import IBaseLayer, IPyAMSLayer +from pyams_utils.interfaces.site import ISiteRoot +from zope.traversing.interfaces import IBeforeTraverseEvent + +# import packages +from pyams_utils.registry import utility_config +from pyams_utils.traversing import get_parent +from pyams_utils.zodb import volatile_property +from pyramid.events import subscriber +from pyramid.threadlocal import get_current_request +from pyramid_zope_request import PyramidPublisherRequest +from zope.interface import implementer, directlyProvidedBy, directlyProvides +from zope.schema.fieldproperty import FieldProperty + +from pyams_skin import _ + + +@implementer(ISkinnable) +class SkinnableContent(object): + """Skinnable content base class""" + + _inherit_skin = FieldProperty(ISkinnable['inherit_skin']) + _skin = FieldProperty(IUserSkinnable['skin']) + + @property + def can_inherit_skin(self): + return get_parent(self, ISkinnable, allow_context=False) is not None + + @property + def inherit_skin(self): + return self._inherit_skin if self.can_inherit_skin else False + + @inherit_skin.setter + def inherit_skin(self, value): + if self.can_inherit_skin: + self._inherit_skin = value + del self.skin_parent + + @property + def no_inherit_skin(self): + return not bool(self.inherit_skin) + + @no_inherit_skin.setter + def no_inherit_skin(self, value): + self.inherit_skin = not bool(value) + + @property + def skin(self): + if not self.inherit_skin: + return self._skin + else: + return self.skin_parent.skin + + @skin.setter + def skin(self, value): + if not self.inherit_skin: + self._skin = value + del self.skin_parent + + @volatile_property + def skin_parent(self): + if (not self._inherit_skin) and self.skin: + return self + parent = get_parent(self, ISkinnable, allow_context=False) + if parent is not None: + return parent.skin_parent + + def get_skin(self, request=None): + parent = self.skin_parent + if parent is self: + return self.skin + elif parent is not None: + skin = parent.skin + if skin is not None: + if request is None: + request = get_current_request() + return request.registry.queryUtility(ISkin, skin) + + +@implementer(IUserSkinnable) +class UserSkinnableContent(SkinnableContent): + """User skinnable content base class""" + + +def apply_skin(request, skin): + """Apply given skin to request""" + + def _apply(request, skin): + ifaces = [iface for iface in directlyProvidedBy(request) + if not issubclass(iface, IBaseLayer)] + # Add the new skin. + if isinstance(skin, str): + skin = request.registry.queryUtility(ISkin, skin) + if skin is not None: + ifaces.append(skin.layer) + directlyProvides(request, *ifaces) + logger.debug("Applied skin {0!r} to request {1!r}".format(skin.label, request)) + + _apply(request, skin) + if isinstance(request, PyramidPublisherRequest): + request = request._request + _apply(request, skin) + else: + request.registry.notify(SkinChangedEvent(request)) + request.annotations['__skin__'] = skin + + +@subscriber(IBeforeTraverseEvent, context_selector=ISkinnable) +def handle_content_skin(event): + """Apply skin when traversing skinnable object""" + request = event.request + skinnable = event.object + if not skinnable.inherit_skin: + skin = skinnable.get_skin(request) + if skin is not None: + apply_skin(request, skin) + + +@subscriber(IBeforeTraverseEvent, context_selector=ISiteRoot) +def handle_root_skin(event): + """Apply skin when traversing site root""" + context = event.object + if not ISkinnable.providedBy(context): + apply_skin(event.request, PyAMSSkin) + elif context.skin is None: + apply_skin(event.request, PyAMSSkin) + + +# +# Base and default skins +# + +@utility_config(name='PyAMS base skin', provides=ISkin) +class PyAMSSkin(object): + """PyAMS base skin""" + + label = _("PyAMS base skin") + layer = IPyAMSLayer