Source code for pyams_viewlet.manager

#
# 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.
#

"""PyAMS_viewlet.manager module

This module defines the viewlet manager, as weel as a "viewletmanager_config" decorator
which can be used instead of ZCML to declare a viewlets manager.
"""

import logging

import venusian
from pyramid.exceptions import ConfigurationError
from pyramid.httpexceptions import HTTPUnauthorized
from pyramid.interfaces import IRequest, IView
from pyramid.threadlocal import get_current_registry
from zope.contentprovider.interfaces import BeforeUpdateEvent
from zope.interface import Interface, classImplements, implementer
from zope.interface.interfaces import ComponentLookupError
from zope.location.interfaces import ILocation

from pyams_template.template import get_view_template
from pyams_utils.request import check_request
from pyams_viewlet.interfaces import IViewlet, IViewletManager


__docformat__ = 'restructuredtext'


LOGGER = logging.getLogger('PyAMS (viewlet)')


[docs]@implementer(IViewletManager) class ViewletManager: """The Viewlet Manager base A generic manager class which can be instantiated. A viewlet manager can be used as mapping and can get to a given viewlet by it's name. """ permission = None template = None viewlets = None def __init__(self, context, request, view): self.__updated = False self.__parent__ = view self.context = context self.request = request def __getitem__(self, name): """See zope.interface.common.mapping.IReadMapping""" # Find the viewlet registry = get_current_registry() viewlet = registry.queryMultiAdapter((self.context, self.request, self.__parent__, self), IViewlet, name=name) # If the viewlet was not found, then raise a lookup error if viewlet is None: raise ComponentLookupError('No provider with name `%s` found.' % name) # If the viewlet cannot be accessed, then raise an # unauthorized error if viewlet.permission and not self.request.has_permission(viewlet.permission, context=self.context): raise HTTPUnauthorized('You are not authorized to access the ' 'provider called `%s`.' % name) # Return the viewlet. return viewlet
[docs] def get(self, name, default=None): """See zope.interface.common.mapping.IReadMapping""" try: return self[name] except (ComponentLookupError, HTTPUnauthorized): return default
def __contains__(self, name): """See zope.interface.common.mapping.IReadMapping""" return bool(self.get(name, False))
[docs] def filter(self, viewlets): """Filter out all content providers :param viewlets: list of viewlets, each element being a tuple of (name, viewlet) form Default implementation is filtering out viewlets for which a permission which is not granted to the current principal is defined. """ request = self.request def _filter(viewlet): """Filter viewlet based on permission""" _, viewlet = viewlet return (not viewlet.permission) or request.has_permission(viewlet.permission, context=self.context) return filter(_filter, viewlets)
[docs] def sort(self, viewlets): # pylint: disable=no-self-use """Sort the viewlets. :param viewlets: list of viewlets, each element being a tuple of (name, viewlet) form Default implementation is sorting viewlets by name """ return sorted(viewlets, key=lambda x: x[0])
def _get_viewlets(self): """Find all content providers for the region""" registry = self.request.registry viewlets = registry.getAdapters((self.context, self.request, self.__parent__, self), IViewlet) viewlets = self.filter(viewlets) viewlets = self.sort(viewlets) return viewlets def _update_viewlets(self): """Calls update on all viewlets and fires events""" registry = self.request.registry for viewlet in self.viewlets: registry.notify(BeforeUpdateEvent(viewlet, self.request)) viewlet.update()
[docs] def update(self): """See :py:class:`zope.contentprovider.interfaces.IContentProvider`""" # check permission if self.permission and not self.request.has_permission(self.permission, context=self.context): return # get the viewlets from now on self.viewlets = [] append = self.viewlets.append for name, viewlet in self._get_viewlets(): if ILocation.providedBy(viewlet): viewlet.__name__ = name append(viewlet) # and update them... self._update_viewlets() self.__updated = True
[docs] def render(self): """See :py:class:`zope.contentprovider.interfaces.IContentProvider`""" # Check for previous update if not (self.__updated and self.viewlets): return '' # Now render the view if self.template: return self.template(viewlets=self.viewlets) # pylint: disable=not-callable return '\n'.join([viewlet.render() for viewlet in self.viewlets])
[docs] def reset(self): """Reset viewlet manager status""" self.viewlets = None self.__updated = False
[docs]def ViewletManagerFactory(name, interface, bases=(), cdict=None): # pylint: disable=invalid-name """Viewlet manager factory""" attr_dict = {'__name__': name} attr_dict.update(cdict or {}) if ViewletManager not in bases: # Make sure that we do not get a default viewlet manager mixin, if the # provided base is already a full viewlet manager implementation. # pylint: disable=no-value-for-parameter if not (len(bases) == 1 and IViewletManager.implementedBy(bases[0])): bases = bases + (ViewletManager,) viewlet_manager_class = type('<ViewletManager providing %s>' % interface.getName(), bases, attr_dict) classImplements(viewlet_manager_class, interface) return viewlet_manager_class
[docs]def get_weight(item): """Get sort weight of a given viewlet""" _, viewlet = item try: return int(viewlet.weight) except (TypeError, AttributeError): return 0
[docs]def get_label(item, request=None): """Get sort label of a given viewlet""" _, viewlet = item try: if request is None: request = check_request() return request.localizer.translate(viewlet.label) except AttributeError: return '--'
[docs]def get_weight_and_label(item, request=None): """Get sort weight and label of a given viewlet""" return get_weight(item), get_label(item, request)
[docs]class WeightOrderedViewletManager(ViewletManager): """Weight ordered viewlet managers. Viewlets with the same weight are sorted by label """
[docs] def sort(self, viewlets): return sorted(viewlets, key=lambda x: get_weight_and_label(x, request=self.request))
[docs]class ConditionalViewletManager(WeightOrderedViewletManager): """Conditional weight ordered viewlet managers"""
[docs] def filter(self, viewlets): """Sort out all viewlets which are explicitly not available Viewlets shoud have a boolean "available" attribute to specify if they are available or not. """ def is_available(viewlet): _, viewlet = viewlet try: return ((not viewlet.permission) or viewlet.request.has_permission(viewlet.permission, context=viewlet.context)) and \ viewlet.available except AttributeError: return True return filter(is_available, viewlets)
[docs]class TemplateBasedViewletManager: """Template based viewlet manager mixin class""" template = get_view_template()
[docs]class viewletmanager_config: # pylint: disable=invalid-name """Class or interface decorator used to declare a viewlet manager You can provide same arguments as in 'viewletManager' ZCML directive: :param name: name of the viewlet; may be unique for a given viewlet manager :param view: the view class or interface for which viewlet is displayed :param for_: the context class or interface for which viewlet is displayed :param permission: name of a permission required to display the viewlet :param layer: request interface required to display the viewlet :param class_: the class handling the viewlet manager; if the decorator is applied on an interface and if this argument is not provided, the viewlet manager will be handled by a default ViewletManager class :param provides: an interface the viewlet manager provides; if the decorator is applied on an Interface, this will be the decorated interface; if the decorated is applied on a class and if this argument is not specified, the manager will provide IViewletManager interface. """ venusian = venusian # for testing injection def __init__(self, **settings): if not settings.get('name'): raise ConfigurationError("You must provide a name for a ViewletManager") 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, obj): # pylint: disable=unused-argument cdict = {'__name__': settings.get('name')} if 'permission' in settings: cdict['permission'] = settings.get('permission') if issubclass(obj, Interface): class_ = settings.get('class_', ViewletManager) provides = obj else: class_ = obj provides = settings.get('provides', IViewletManager) new_class = ViewletManagerFactory(settings.get('name'), provides, (class_,), cdict) LOGGER.debug("Registering viewlet manager {0} ({1})".format(settings.get('name'), str(new_class))) registry = settings.get('registry') if registry is None: config = context.config.with_package(info.module) # pylint: disable=no-member registry = config.registry registry.registerAdapter(new_class, (settings.get('context', Interface), settings.get('layer', IRequest), settings.get('view', IView)), provides, settings.get('name')) info = self.venusian.attach(wrapped, callback, category='pyams_viewlet') if info.scope == 'class': # pylint: disable=no-member # 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 # pylint: disable=no-member return wrapped