src/pyams_template/template.py
changeset 0 31ded33115d7
child 4 d9dc2f58c72b
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_template/template.py	Thu Feb 19 10:29:02 2015 +0100
@@ -0,0 +1,201 @@
+#
+# 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 library
+import inspect
+import os
+import venusian
+
+# import interfaces
+from pyams_template.interfaces import IPageTemplate, IContentTemplate, ILayoutTemplate
+from pyramid.interfaces import IRequest
+from pyramid_chameleon.interfaces import IChameleonTranslate
+
+# import packages
+from pyramid.exceptions import ConfigurationError
+from pyramid_chameleon.zpt import PyramidPageTemplateFile
+from zope.component import queryUtility
+from zope.interface import directlyProvides
+
+
+configuration_settings = {}
+
+
+class TemplateFactory(object):
+    """Template factory."""
+
+    template = None
+
+    def __init__(self, filename, contentType, macro=None):
+        self.contentType = contentType
+        self.template = PyramidPageTemplateFile(filename,
+                                                content_type=contentType,
+                                                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
+
+
+class BoundViewTemplate(object):
+    def __init__(self, pt, ob):
+        object.__setattr__(self, 'im_func', pt)
+        object.__setattr__(self, 'im_self', ob)
+
+    def __call__(self, *args, **kw):
+        if self.im_self is None:
+            im_self, args = args[0], args[1:]
+        else:
+            im_self = self.im_self
+        return self.im_func(im_self, *args, **kw)
+
+    def __setattr__(self, name, v):
+        raise AttributeError("Can't set attribute", name)
+
+    def __repr__(self):
+        return "<BoundViewTemplate of %r>" % self.im_self
+
+
+class ViewTemplate(object):
+    def __init__(self, provides=IPageTemplate, name=u''):
+        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):
+        return BoundViewTemplate(self, instance)
+
+get_view_template = ViewTemplate
+
+
+class GetPageTemplate(ViewTemplate):
+
+    def __init__(self, name=u''):
+        self.provides = IContentTemplate
+        self.name = name
+
+get_page_template = GetPageTemplate
+
+
+class GetLayoutTemplate(ViewTemplate):
+
+    def __init__(self, name=u''):
+        self.provides = ILayoutTemplate
+        self.name = name
+
+get_layout_template = GetLayoutTemplate
+
+
+class template_config(object):
+    """Class decorator used to declare a 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, ob):
+            template = os.path.join(os.path.dirname(inspect.getfile(inspect.getmodule(ob))),
+                                    settings.get('template'))
+            if not os.path.isfile(template):
+                raise ConfigurationError("No such file", template)
+
+            contentType = settings.get('contentType', 'text/html')
+            macro = settings.get('macro')
+            factory = TemplateFactory(template, contentType, macro)
+            provides = settings.get('provides', IContentTemplate)
+            directlyProvides(factory, provides)
+
+            config = context.config.with_package(info.module)
+            config.registry.registerAdapter(factory,
+                                            (ob, settings.get('layer', IRequest)),
+                                            provides, settings.get('name', ''))
+
+        info = self.venusian.attach(wrapped, callback, category='pyams_pagelet')
+
+        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
+
+
+class layout_config(object):
+    """Class decorator used to declare a 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, ob):
+            template = os.path.join(os.path.dirname(inspect.getfile(ob)), settings.get('template'))
+            if not os.path.isfile(template):
+                raise ConfigurationError("No such file", template)
+
+            contentType = settings.get('contentType', 'text/html')
+            macro = settings.get('macro')
+            factory = TemplateFactory(template, contentType, macro)
+            provides = settings.get('provides', ILayoutTemplate)
+            directlyProvides(factory, provides)
+
+            config = context.config.with_package(info.module)
+            config.registry.registerAdapter(factory,
+                                            (ob, settings.get('layer', IRequest)),
+                                            provides, settings.get('name', ''))
+
+        info = self.venusian.attach(wrapped, callback, category='pyams_pagelet')
+
+        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