#
# 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_pagelet.pagelet module
This module provides the core pagelet implementation, and a "pagelet_config" decorator which
can be use to register pagelets instead of ZCML directives.
"""
import logging
import venusian
from pyramid.httpexceptions import HTTPUnauthorized
from pyramid.interfaces import IRequest
from pyramid.response import Response
from pyramid_chameleon.interfaces import IChameleonTranslate
from zope.component import queryUtility
from zope.interface import Interface, implementer
from pyams_pagelet.interfaces import IPagelet, IPageletRenderer, PageletCreatedEvent
from pyams_template.interfaces import IContentTemplate, ILayoutTemplate
from pyams_utils.adapter import adapter_config
__docformat__ = 'restructuredtext'
LOGGER = logging.getLogger('PyAMS (pagelet)')
REDIRECT_STATUS_CODES = (301, 302, 303)
[docs]@implementer(IPagelet)
class Pagelet:
"""Content provider with layout support"""
template = None
layout = None
permission = None
def __init__(self, context, request):
self.context = context
self.request = request
if self.permission and not request.has_permission(self.permission):
raise HTTPUnauthorized('You are not authorized to access the page called `%s`.' %
request.view_name)
request.registry.notify(PageletCreatedEvent(self))
[docs] def update(self):
"""See `zope.contentprovider.interfaces.IContentProvider`"""
annotations = getattr(self.request, 'annotations', None)
if annotations is not None:
annotations['view'] = self
[docs] def render(self):
"""See `zope.contentprovider.interfaces.IContentProvider`"""
request = self.request
cdict = {
'context': self.context,
'request': request,
'view': self,
'translate': queryUtility(IChameleonTranslate)
}
if self.template is None:
registry = request.registry
template = registry.queryMultiAdapter((self, request, self.context),
IContentTemplate)
if template is None:
template = registry.getMultiAdapter((self, request), IContentTemplate)
return template(**cdict)
return self.template(**cdict) # pylint: disable=not-callable
def __call__(self, **kwargs):
"""Call update and return layout template"""
self.update()
if self.request.response.status_code in REDIRECT_STATUS_CODES:
return ''
request = self.request
cdict = {
'context': self.context,
'request': request,
'view': self,
'translate': queryUtility(IChameleonTranslate)
}
cdict.update(kwargs)
if self.layout is None:
registry = request.registry
layout = registry.queryMultiAdapter((self, request, self.context),
ILayoutTemplate)
if layout is None:
layout = registry.getMultiAdapter((self, request), ILayoutTemplate)
return Response(layout(**cdict))
return Response(self.layout(**cdict)) # pylint: disable=not-callable
[docs]@adapter_config(name='pagelet', context=(Interface, IRequest, IPagelet), provides=IPageletRenderer)
class PageletRenderer:
"""Pagelet renderer"""
def __init__(self, context, request, pagelet):
self.__updated = False
self.__parent__ = pagelet
self.context = context
self.request = request
[docs] def update(self):
"""See `zope.contentprovider.interfaces.IContentProvider`"""
[docs] def render(self):
"""See `zope.contentprovider.interfaces.IContentProvider`"""
return self.__parent__.render()
[docs]class pagelet_config: # pylint: disable=invalid-name
"""Function or class decorator used to declare a pagelet"""
venusian = venusian # for testing injection
def __init__(self, **settings):
if 'for_' in settings:
if settings.get('context') is None:
settings['context'] = settings.pop('for_')
if 'layer' in settings:
settings['request_type'] = settings.pop('layer')
self.__dict__.update(settings)
def __call__(self, wrapped):
settings = self.__dict__.copy()
depth = settings.pop('_depth', 0)
def callback(context, name, obj): # pylint: disable=unused-argument
"""Venusian decorator callback"""
cdict = {
'__name__': settings.get('name'),
'__module__': obj.__module__,
'permission': settings.get('permission')
}
new_class = type(obj.__name__, (obj, Pagelet), cdict)
LOGGER.debug('Registering pagelet view "{0}" for {1} ({2})'.format(
settings.get('name'), str(settings.get('context', Interface)), str(new_class)))
config = context.config.with_package(info.module) # pylint: disable=no-member
registry = settings.get('registry') or config.registry
registry.registerAdapter(new_class,
(settings.get('context', Interface),
settings.get('request_type', IRequest)),
IPagelet, settings.get('name'))
config.add_view(view=new_class, **settings)
info = self.venusian.attach(wrapped, callback, category='pyams_pagelet',
depth=depth + 1)
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