src/pyams_viewlet/manager.py
changeset 29 6ab01534cc92
child 37 38ec13042956
equal deleted inserted replaced
-1:000000000000 29:6ab01534cc92
       
     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 
       
    16 # import standard packages
       
    17 import logging
       
    18 logger = logging.getLogger('PyAMS (viewlet)')
       
    19 
       
    20 import venusian
       
    21 
       
    22 # import interfaces
       
    23 from pyams_viewlet.interfaces import IViewletManager, IViewlet
       
    24 from pyramid.interfaces import IRequest, IView
       
    25 from zope.component.interfaces import ComponentLookupError
       
    26 from zope.contentprovider.interfaces import BeforeUpdateEvent
       
    27 from zope.location.interfaces import ILocation
       
    28 
       
    29 # import packages
       
    30 from pyams_template.template import get_view_template
       
    31 from pyams_utils.request import check_request
       
    32 from pyramid.exceptions import ConfigurationError
       
    33 from pyramid.httpexceptions import HTTPUnauthorized
       
    34 from pyramid.threadlocal import get_current_registry
       
    35 from zope.interface import implementer, classImplements, Interface
       
    36 
       
    37 
       
    38 @implementer(IViewletManager)
       
    39 class ViewletManager(object):
       
    40     """The Viewlet Manager base
       
    41 
       
    42     A generic manager class which can be instantiated
       
    43     """
       
    44 
       
    45     permission = None
       
    46     template = None
       
    47 
       
    48     def __init__(self, context, request, view):
       
    49         self.__updated = False
       
    50         self.__parent__ = view
       
    51         self.context = context
       
    52         self.request = request
       
    53 
       
    54     def __getitem__(self, name):
       
    55         """See zope.interface.common.mapping.IReadMapping"""
       
    56         # Find the viewlet
       
    57         registry = get_current_registry()
       
    58         viewlet = registry.queryMultiAdapter((self.context, self.request, self.__parent__, self),
       
    59                                              IViewlet, name=name)
       
    60 
       
    61         # If the viewlet was not found, then raise a lookup error
       
    62         if viewlet is None:
       
    63             raise ComponentLookupError('No provider with name `%s` found.' % name)
       
    64 
       
    65         # If the viewlet cannot be accessed, then raise an
       
    66         # unauthorized error
       
    67         if viewlet.permission and not self.request.has_permission(viewlet.permission, context=self.context):
       
    68             raise HTTPUnauthorized('You are not authorized to access the provider called `%s`.' % name)
       
    69 
       
    70         # Return the viewlet.
       
    71         return viewlet
       
    72 
       
    73     def get(self, name, default=None):
       
    74         """See zope.interface.common.mapping.IReadMapping"""
       
    75         try:
       
    76             return self[name]
       
    77         except (ComponentLookupError, HTTPUnauthorized):
       
    78             return default
       
    79 
       
    80     def __contains__(self, name):
       
    81         """See zope.interface.common.mapping.IReadMapping"""
       
    82         return bool(self.get(name, False))
       
    83 
       
    84     def filter(self, viewlets):
       
    85         """Sort out all content providers
       
    86 
       
    87         ``viewlets`` is a list of tuples of the form (name, viewlet).
       
    88         """
       
    89         # Only return viewlets accessible to the principal
       
    90         request = self.request
       
    91         return [(name, viewlet) for name, viewlet in viewlets
       
    92                 if (not viewlet.permission) or request.has_permission(viewlet.permission, context=self.context)]
       
    93 
       
    94     def sort(self, viewlets):
       
    95         """Sort the viewlets.
       
    96 
       
    97         ``viewlets`` is a list of tuples of the form (name, viewlet).
       
    98         """
       
    99         # By default, we are not sorting by viewlet name.
       
   100         return sorted(viewlets, key=lambda x: x[0])
       
   101 
       
   102     def update(self):
       
   103         """See zope.contentprovider.interfaces.IContentProvider"""
       
   104         self.__updated = True
       
   105 
       
   106         # check permission
       
   107         if self.permission and not self.request.has_permission(self.permission, context=self.context):
       
   108             return
       
   109         # Find all content providers for the region
       
   110         viewlets = self._get_viewlets()
       
   111         # Just use the viewlets from now on
       
   112         self.viewlets = []
       
   113         for name, viewlet in viewlets:
       
   114             if ILocation.providedBy(viewlet):
       
   115                 viewlet.__name__ = name
       
   116             self.viewlets.append(viewlet)
       
   117         self._update_viewlets()
       
   118 
       
   119     def _get_viewlets(self):
       
   120         """Find all content providers for the region"""
       
   121         registry = self.request.registry
       
   122         viewlets = registry.getAdapters((self.context, self.request, self.__parent__, self),
       
   123                                         IViewlet)
       
   124         viewlets = self.filter(viewlets)
       
   125         viewlets = self.sort(viewlets)
       
   126         return viewlets
       
   127 
       
   128     def _update_viewlets(self):
       
   129         """Calls update on all viewlets and fires events"""
       
   130         registry = self.request.registry
       
   131         for viewlet in self.viewlets:
       
   132             registry.notify(BeforeUpdateEvent(viewlet, self.request))
       
   133             viewlet.update()
       
   134 
       
   135     def render(self):
       
   136         """See zope.contentprovider.interfaces.IContentProvider"""
       
   137         # Now render the view
       
   138         if self.permission and not self.request.has_permission(self.permission, context=self.context):
       
   139             return ''
       
   140         if not self.viewlets:
       
   141             return ''
       
   142         if self.template:
       
   143             return self.template(viewlets=self.viewlets)
       
   144         else:
       
   145             return '\n'.join([viewlet.render() for viewlet in self.viewlets])
       
   146 
       
   147 
       
   148 def ViewletManagerFactory(name, interface, bases=(), cdict=None):
       
   149     """Viewlet manager factory"""
       
   150 
       
   151     attr_dict = {'__name__': name}
       
   152     attr_dict.update(cdict or {})
       
   153 
       
   154     if ViewletManager not in bases:
       
   155         # Make sure that we do not get a default viewlet manager mixin, if the
       
   156         # provided base is already a full viewlet manager implementation.
       
   157         if not (len(bases) == 1 and IViewletManager.implementedBy(bases[0])):
       
   158             bases = bases + (ViewletManager,)
       
   159 
       
   160     viewlet_manager_class = type('<ViewletManager providing %s>' % interface.getName(), bases, attr_dict)
       
   161     classImplements(viewlet_manager_class, interface)
       
   162     return viewlet_manager_class
       
   163 
       
   164 
       
   165 def get_weight(item):
       
   166     """Get sort weight of a given viewlet"""
       
   167     name, viewlet = item
       
   168     try:
       
   169         return int(viewlet.weight)
       
   170     except (TypeError, AttributeError):
       
   171         return 0
       
   172 
       
   173 
       
   174 def get_label(item, request=None):
       
   175     """Get sort label of a given viewlet"""
       
   176     name, viewlet = item
       
   177     try:
       
   178         if request is None:
       
   179             request = check_request()
       
   180         return request.localizer.translate(viewlet.label)
       
   181     except AttributeError:
       
   182         return '--'
       
   183 
       
   184 
       
   185 def get_weight_and_label(item, request=None):
       
   186     """Get sort weight and label of a given viewlet"""
       
   187     return get_weight(item), get_label(item, request)
       
   188 
       
   189 
       
   190 class WeightOrderedViewletManager(ViewletManager):
       
   191     """Weight ordered viewlet managers.
       
   192 
       
   193     Viewlets with the same weight are sorted by label
       
   194     """
       
   195 
       
   196     def sort(self, viewlets):
       
   197         return sorted(viewlets, key=lambda x: get_weight_and_label(x, request=self.request))
       
   198 
       
   199 
       
   200 def is_available(viewlet):
       
   201     try:
       
   202         return ((not viewlet.permission) or
       
   203                 viewlet.request.has_permission(viewlet.permission, context=viewlet.context)) and \
       
   204                viewlet.available
       
   205     except AttributeError:
       
   206         return True
       
   207 
       
   208 
       
   209 class ConditionalViewletManager(WeightOrderedViewletManager):
       
   210     """Conditional weight ordered viewlet managers."""
       
   211 
       
   212     def filter(self, viewlets):
       
   213         """Sort out all viewlets which are explicit not available
       
   214 
       
   215         ``viewlets`` is a list of tuples of the form (name, viewlet).
       
   216         """
       
   217         return [(name, viewlet) for name, viewlet in viewlets if is_available(viewlet)]
       
   218 
       
   219 
       
   220 class TemplateBasedViewletManager(object):
       
   221     """Template based viewlet manager mixin class"""
       
   222 
       
   223     template = get_view_template()
       
   224 
       
   225 
       
   226 class viewletmanager_config(object):
       
   227     """Class or interface decorator used to declare a viewlet manager
       
   228 
       
   229     You can provide same arguments as in 'viewletManager' ZCML directive:
       
   230     @name = name of the viewlet; may be unique for a given viewlet manager
       
   231     @view = the view class or interface for which viewlet is displayed
       
   232     @for_ = the context class or interface for which viewlet is displayed
       
   233     @permission = name of a permission required to display the viewlet
       
   234     @layer = request interface required to display the viewlet
       
   235     @class_ = the class handling the viewlet manager; if the decorator is applied
       
   236       on an interface and if this argument is not provided, the viewlet manager
       
   237       will be handled by a default ViewletManager class
       
   238     @provides = an interface the viewlet manager provides; if the decorator is
       
   239       applied on an Interface, this will be the decorated interface; if the
       
   240       decorated is applied on a class and if this argument is not specified,
       
   241       the manager will provide IViewletManager interface.
       
   242     """
       
   243 
       
   244     venusian = venusian  # for testing injection
       
   245 
       
   246     def __init__(self, **settings):
       
   247         if not settings.get('name'):
       
   248             raise ConfigurationError("You must provide a name for a ViewletManager")
       
   249         if 'for_' in settings:
       
   250             if settings.get('context') is None:
       
   251                 settings['context'] = settings['for_']
       
   252         self.__dict__.update(settings)
       
   253 
       
   254     def __call__(self, wrapped):
       
   255         settings = self.__dict__.copy()
       
   256 
       
   257         def callback(context, name, ob):
       
   258             cdict = {'__name__': settings.get('name')}
       
   259             if 'permission' in settings:
       
   260                 cdict['permission'] = settings.get('permission')
       
   261 
       
   262             if issubclass(ob, Interface):
       
   263                 class_ = settings.get('class_', ViewletManager)
       
   264                 provides = ob
       
   265             else:
       
   266                 class_ = ob
       
   267                 provides = settings.get('provides', IViewletManager)
       
   268             new_class = ViewletManagerFactory(settings.get('name'), provides, (class_,), cdict)
       
   269 
       
   270             logger.debug("Registering viewlet manager {0} ({1})".format(settings.get('name'),
       
   271                                                                         str(new_class)))
       
   272             config = context.config.with_package(info.module)
       
   273             config.registry.registerAdapter(new_class,
       
   274                                             (settings.get('context', Interface),
       
   275                                              settings.get('layer', IRequest),
       
   276                                              settings.get('view', IView)),
       
   277                                             provides, settings.get('name'))
       
   278 
       
   279         info = self.venusian.attach(wrapped, callback, category='pyams_viewlet')
       
   280 
       
   281         if info.scope == 'class':
       
   282             # if the decorator was attached to a method in a class, or
       
   283             # otherwise executed at class scope, we need to set an
       
   284             # 'attr' into the settings if one isn't already in there
       
   285             if settings.get('attr') is None:
       
   286                 settings['attr'] = wrapped.__name__
       
   287 
       
   288         settings['_info'] = info.codeinfo  # fbo "action_method"
       
   289         return wrapped