src/pyams_viewlet/viewlet.py
changeset 29 6ab01534cc92
child 37 38ec13042956
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_viewlet/viewlet.py	Wed Dec 05 13:23:08 2018 +0100
@@ -0,0 +1,216 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# 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 packages
+import logging
+logger = logging.getLogger('PyAMS (viewlet)')
+
+import venusian
+
+# import interfaces
+from pyams_viewlet.interfaces import IViewlet, IViewletManager
+from pyramid.interfaces import IRequest, IView
+from zope.contentprovider.interfaces import IContentProvider
+
+# import packages
+from pyams_template.template import get_view_template
+from pyramid.exceptions import ConfigurationError
+from zope.interface import implementer, Interface
+
+
+@implementer(IContentProvider)
+class EmptyContentProvider(object):
+    """Empty content provider base class"""
+
+    permission = None
+
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+
+    def __call__(self):
+        if self.permission and not self.request.has_permission(self.permission, context=self.context):
+            return ''
+        self.update()
+        return self.render()
+
+    def update(self):
+        pass
+
+    def render(self):
+        return ''
+
+
+class BaseContentProvider(EmptyContentProvider):
+    """Base template based content provider"""
+
+    render = get_view_template()
+
+
+@implementer(IContentProvider)
+class ViewContentProvider(BaseContentProvider):
+    """Template based content provider"""
+
+    def __init__(self, context, request, view):
+        super(ViewContentProvider, self).__init__(context, request)
+        self.view = self.__parent__ = view
+
+
+class contentprovider_config(object):
+    """Class decorator used to declare a content provider
+
+    You can provide same arguments as in 'viewlet' ZCML directive:
+    @name = name of the viewlet; may be unique for a given viewlet manager
+    @view = the view class or interface for which viewlet is displayed
+    @for = the context class or interface for which viewlet is displayed
+    @permission = name of a permission required to display the viewlet
+    @layer = request interface required to display the viewlet
+    """
+
+    venusian = venusian  # for testing injection
+
+    def __init__(self, **settings):
+        if not settings.get('name'):
+            raise ConfigurationError("You must provide a name for a Viewlet")
+        if 'for_' in settings:
+            if settings.get('context') is None:
+                settings['context'] = settings['for_']
+        self.__dict__.update(settings)
+
+    def __call__(self, wrapped):
+        settings = self.__dict__.copy()
+
+        def callback(context, name, ob):
+            cdict = {
+                '__name__': settings.get('name'),
+                '__module__': ob.__module__
+            }
+            if 'permission' in settings:
+                settings['permission'] = settings.get('permission')
+
+            bases = (ob,)
+            if not IContentProvider.implementedBy(ob):
+                bases = bases + (ViewContentProvider,)
+            new_class = type('<ViewContentProvider %s>' % settings.get('name'), bases, cdict)
+
+            logger.debug("Registering content provider {0} ({1})".format(settings.get('name'),
+                                                                         str(new_class)))
+            config = context.config.with_package(info.module)
+            config.registry.registerAdapter(new_class,
+                                            (settings.get('context', Interface),
+                                             settings.get('layer', IRequest),
+                                             settings.get('view', IView)),
+                                            IContentProvider, settings.get('name'))
+
+        info = self.venusian.attach(wrapped, callback, category='pyams_viewlet')
+
+        if info.scope == 'class':
+            # if the decorator was attached to a method in a class, or
+            # otherwise executed at class scope, we need to set an
+            # 'attr' into the settings if one isn't already in there
+            if settings.get('attr') is None:
+                settings['attr'] = wrapped.__name__
+
+        settings['_info'] = info.codeinfo  # fbo "action_method"
+        return wrapped
+
+
+@implementer(IViewlet)
+class EmptyViewlet(object):
+    """Empty viewlet base class"""
+
+    permission = None
+
+    def __init__(self, context, request, view, manager):
+        self.context = context
+        self.request = request
+        self.__parent__ = view
+        self.manager = manager
+
+    def update(self):
+        pass
+
+    def render(self):
+        return ''
+
+
+class Viewlet(EmptyViewlet):
+    """Viewlet adapter class used in meta directive as a mixin class."""
+
+    render = get_view_template()
+
+
+class viewlet_config(object):
+    """Class decorator used to declare a viewlet
+
+    You can provide same arguments as in 'viewlet' ZCML directive:
+    @name = name of the viewlet; may be unique for a given viewlet manager
+    @manager = manager class or interface holding the viewlet
+    @view = the view class or interface for which viewlet is displayed
+    @for = the context class or interface for which viewlet is displayed
+    @permission = name of a permission required to display the viewlet
+    @layer = request interface required to display the viewlet
+    @weight = weight of the viewlet when using a weight ordered viewlet manager
+    """
+
+    venusian = venusian  # for testing injection
+
+    def __init__(self, **settings):
+        if not settings.get('name'):
+            raise ConfigurationError("You must provide a name for a Viewlet")
+        if 'for_' in settings:
+            if settings.get('context') is None:
+                settings['context'] = settings['for_']
+        self.__dict__.update(settings)
+
+    def __call__(self, wrapped):
+        settings = self.__dict__.copy()
+
+        def callback(context, name, ob):
+            cdict = {
+                '__name__': settings.get('name'),
+                '__module__': ob.__module__
+            }
+            if 'permission' in settings:
+                cdict['permission'] = settings.get('permission')
+            if 'weight' in settings:
+                cdict['weight'] = settings.get('weight')
+
+            bases = (ob,)
+            if not IViewlet.implementedBy(ob):
+                bases = bases + (Viewlet,)
+            new_class = type('<Viewlet %s>' % settings.get('name'), bases, cdict)
+
+            logger.debug("Registering viewlet {0} ({1})".format(settings.get('name'),
+                                                                str(new_class)))
+            config = context.config.with_package(info.module)
+            config.registry.registerAdapter(new_class,
+                                            (settings.get('context', Interface),
+                                             settings.get('layer', IRequest),
+                                             settings.get('view', IView),
+                                             settings.get('manager', IViewletManager)),
+                                            IViewlet, settings.get('name'))
+
+        info = self.venusian.attach(wrapped, callback, category='pyams_viewlet')
+
+        if info.scope == 'class':
+            # if the decorator was attached to a method in a class, or
+            # otherwise executed at class scope, we need to set an
+            # 'attr' into the settings if one isn't already in there
+            if settings.get('attr') is None:
+                settings['attr'] = wrapped.__name__
+
+        settings['_info'] = info.codeinfo  # fbo "action_method"
+        return wrapped