Source code for pyams_template.template

#
# 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_template.template module

This module provides Pyramid decorators which can be used to register a Chameleon template for
a given view.

These templates are registered as multi-adapters, for a view and a request, so they can be
replaced or overriden easilly.
"""

import inspect
import os

import venusian
from pyramid.exceptions import ConfigurationError
from pyramid.interfaces import IRequest
from pyramid_chameleon.interfaces import IChameleonTranslate
from pyramid_chameleon.zpt import PyramidPageTemplateFile
from zope.component import queryUtility
from zope.component.globalregistry import getGlobalSiteManager
from zope.interface import directlyProvides

from pyams_template.interfaces import IContentTemplate, ILayoutTemplate, IPageTemplate


__docformat__ = 'restructuredtext'


CONFIGURATION_SETTINGS = {}


[docs]class TemplateFactory: """Base template factory""" template = None def __init__(self, filename, content_type, macro=None): self.content_type = content_type self.template = PyramidPageTemplateFile(filename, content_type=content_type, macro=macro, auto_reload=CONFIGURATION_SETTINGS.get( 'reload_templates', False), debug=CONFIGURATION_SETTINGS.get( 'debug_templates', False), translate=queryUtility(IChameleonTranslate)) self.macro = self.template.macro = macro def __call__(self, view, request, context=None): return self.template
[docs]class BoundViewTemplate: """Bound view template""" __self__ = None __func__ = None def __init__(self, tmpl, obj): object.__setattr__(self, '__func__', tmpl) object.__setattr__(self, '__self__', obj) @property def im_self(self): """im_self property""" return self.__self__ @property def im_func(self): """im_func property""" return self.__func__ def __call__(self, *args, **kwargs): if self.__self__ is None: im_self, args = args[0], args[1:] else: im_self = self.__self__ return self.__func__(im_self, *args, **kwargs) # pylint: disable=not-callable def __setattr__(self, name, v): raise AttributeError("Can't set attribute", name) def __repr__(self): return "<BoundViewTemplate of %r>" % self.__self__
[docs]class ViewTemplate: """View template class""" def __init__(self, provides=IPageTemplate, name=''): self.provides = provides self.name = name def __call__(self, instance, *args, **keywords): registry = instance.request.registry template = registry.queryMultiAdapter((instance, instance.request, instance.context), self.provides, name=self.name) if template is None: template = registry.getMultiAdapter((instance, instance.request), self.provides, name=self.name) keywords.update({ 'context': instance.context, 'request': instance.request, 'view': instance, 'translate': queryUtility(IChameleonTranslate) }) return template(*args, **keywords) def __get__(self, instance, type): # pylint: disable=redefined-builtin return BoundViewTemplate(self, instance)
get_view_template = ViewTemplate # pylint: disable=invalid-name
[docs]class GetContentTemplate(ViewTemplate): """Page template getter class""" def __init__(self, name=''): # pylint: disable=super-init-not-called self.provides = IContentTemplate self.name = name
get_content_template = GetContentTemplate # pylint: disable=invalid-name
[docs]class GetLayoutTemplate(ViewTemplate): """Layout template getter class""" def __init__(self, name=''): # pylint: disable=super-init-not-called self.provides = ILayoutTemplate self.name = name
get_layout_template = GetLayoutTemplate # pylint: disable=invalid-name
[docs]class template_config: # pylint: disable=invalid-name """Class decorator used to declare a view template""" venusian = venusian # for testing injection def __init__(self, **settings): 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): template = os.path.join(os.path.dirname(inspect.getfile(inspect.getmodule(obj))), settings.get('template')) if not os.path.isfile(template): raise ConfigurationError("No such file", template) content_type = settings.get('content_type', 'text/html') macro = settings.get('macro') factory = TemplateFactory(template, content_type, macro) provides = settings.get('provides', IContentTemplate) directlyProvides(factory, provides) # check context context = settings.get('context') if context is None: required = (obj, settings.get('layer', IRequest)) else: required = (obj, settings.get('layer', IRequest), context) # check registry registry = settings.get('registry') if registry is None: config = context.config.with_package(info.module) # pylint: disable=no-member registry = config.registry registry.registerAdapter(factory, required, provides, settings.get('name', '')) info = self.venusian.attach(wrapped, callback, category='pyams_template') 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
[docs]def override_template(view, **settings): """Override template for a given context This function can be used to override a class template without using ZCML. Settings are the same as for @template_config decorator. """ stack = inspect.stack()[1] template = settings.get('template', '') if not template: raise ConfigurationError("No template specified") if not template.startswith('/'): template = os.path.join(os.path.dirname(inspect.getfile(inspect.getmodule(stack[0]))), template) if not os.path.isfile(template): raise ConfigurationError("No such file", template) content_type = settings.get('content_type', 'text/html') macro = settings.get('macro') # create factory factory = TemplateFactory(template, content_type, macro) provides = settings.get('provides', IContentTemplate) directlyProvides(factory, provides) # check context context = settings.get('context') if context is None: required = (view, settings.get('layer', IRequest)) else: required = (view, settings.get('layer', IRequest), context) # check registry registry = settings.get('registry') if registry is None: registry = getGlobalSiteManager() registry.registerAdapter(factory, required, provides, settings.get('name', ''))
[docs]class layout_config: # pylint: disable=invalid-name """Class decorator used to declare a layout template""" venusian = venusian # for testing injection def __init__(self, **settings): 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): template = os.path.join(os.path.dirname(inspect.getfile(inspect.getmodule(obj))), settings.get('template')) if not os.path.isfile(template): raise ConfigurationError("No such file", template) content_type = settings.get('content_type', 'text/html') macro = settings.get('macro') factory = TemplateFactory(template, content_type, macro) provides = settings.get('provides', ILayoutTemplate) directlyProvides(factory, provides) # check context context = settings.get('context') if context is None: required = (obj, settings.get('layer', IRequest)) else: required = (obj, settings.get('layer', IRequest), context) # check registry registry = settings.get('registry') if registry is None: config = context.config.with_package(info.module) # pylint: disable=no-member registry = config.registry registry.registerAdapter(factory, required, provides, settings.get('name', '')) info = self.venusian.attach(wrapped, callback, category='pyams_template') 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
[docs]def override_layout(view, **settings): """Override layout template for a given class This function can be used to override a class layout template without using ZCML. Settings are the same as for @layout_config decorator. """ stack = inspect.stack()[1] template = settings.get('template', '') if not template: raise ConfigurationError("No template specified") if not template.startswith('/'): template = os.path.join(os.path.dirname(inspect.getfile(inspect.getmodule(stack[0]))), template) if not os.path.isfile(template): raise ConfigurationError("No such file", template) content_type = settings.get('content_type', 'text/html') macro = settings.get('macro') # create factory factory = TemplateFactory(template, content_type, macro) provides = settings.get('provides', ILayoutTemplate) directlyProvides(factory, provides) # check context context = settings.get('context') if context is None: required = (view, settings.get('layer', IRequest)) else: required = (view, settings.get('layer', IRequest), context) # check registry registry = settings.get('registry') if registry is None: registry = getGlobalSiteManager() registry.registerAdapter(factory, required, provides, settings.get('name', ''))