--- a/src/pyams_pagelet/__init__.py Mon Jun 11 17:30:43 2018 +0200
+++ b/src/pyams_pagelet/__init__.py Wed Nov 27 19:37:28 2019 +0100
@@ -10,8 +10,19 @@
# FOR A PARTICULAR PURPOSE.
#
+"""PyAMS_pagelet package
+
+This package is an update of z3c.pagelet package for use with Pyramid.
+
+Pagelets use :py:mod:`pyams_template` package, which participate in separation of Python view code
+and their template implementation, to split an HTML view template into two parts: a content
+template and a layout template. It also provides a "pagelet" ZCML directive which can be used to
+register pagelets, as well as a "pagelet_config" decorator which can be used alternatively.
+"""
from pyramid.i18n import TranslationStringFactory
+
+
_ = TranslationStringFactory('pyams_pagelet')
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_pagelet/doctests/README.rst Wed Nov 27 19:37:28 2019 +0100
@@ -0,0 +1,121 @@
+
+=====================
+pyams_pagelet package
+=====================
+
+Let's start by creating a new template:
+
+ >>> from pyramid.testing import setUp, tearDown
+ >>> from pyams_utils.request import get_annotations
+ >>> config = setUp()
+ >>> config.add_request_method(get_annotations, 'annotations', reify=True)
+
+ >>> import os, tempfile
+ >>> temp_dir = tempfile.mkdtemp()
+
+ >>> content_template = os.path.join(temp_dir, 'content-template.pt')
+ >>> with open(content_template, 'w') as file:
+ ... _ = file.write('<div>Base template content</div>')
+
+ >>> layout_template = os.path.join(temp_dir, 'layout-template.pt')
+ >>> with open(layout_template, 'w') as file:
+ ... _ = file.write('''
+ ... <html>
+ ... <body>
+ ... <div class="layout">${structure:view.render()}</div>
+ ... </body>
+ ... </html>
+ ... ''')
+
+The templates must now be registered for a view and a request. We use the TemplateFactory directly
+here from *pyams_template* package, while it may be done using a *template_config* decorator:
+
+ >>> from zope.interface import implementer, Interface
+ >>> from pyramid.interfaces import IRequest
+ >>> from pyams_template.interfaces import IContentTemplate, ILayoutTemplate
+
+ >>> from pyams_template.template import TemplateFactory
+ >>> factory = TemplateFactory(content_template, 'text/html')
+ >>> config.registry.registerAdapter(factory, (Interface, IRequest), IContentTemplate)
+
+ >>> factory = TemplateFactory(layout_template, 'text/html')
+ >>> config.registry.registerAdapter(factory, (Interface, IRequest), ILayoutTemplate)
+
+Let's now create a pagelet view:
+
+ >>> class IMyView(Interface):
+ ... """View marker interface"""
+
+ >>> from pyams_pagelet.pagelet import Pagelet
+ >>> @implementer(IMyView)
+ ... class MyView(Pagelet):
+ ... """View class"""
+
+ >>> from pyramid.testing import DummyRequest
+ >>> content = object()
+ >>> request = DummyRequest()
+ >>> view = MyView(content, request)
+ >>> print(view.render())
+ <div>Base template content</div>
+
+ >>> print(view())
+ 200 OK
+ Content-Type: text/html; charset=UTF-8
+ Content-Length: 98
+ <BLANKLINE>
+ <html>
+ <body>
+ <div class="layout"><div>Base template content</div></div>
+ </body>
+ </html>
+ <BLANKLINE>
+
+But the standard way of using a pagelet is by using the "pagelet:" TALES expression:
+
+ >>> pagelet_template = os.path.join(temp_dir, 'pagelet-template.pt')
+ >>> with open(pagelet_template, 'w') as file:
+ ... _ = file.write('''
+ ... <html>
+ ... <body>
+ ... <div class="pagelet">${structure:provider:pagelet}</div>
+ ... </body>
+ ... </html>
+ ... ''')
+
+This template will be registered using the custom view interface:
+
+ >>> from chameleon import PageTemplateFile
+ >>> from pyams_viewlet.provider import ProviderExpr
+ >>> PageTemplateFile.expression_types['provider'] = ProviderExpr
+
+ >>> factory = TemplateFactory(pagelet_template, 'text/html')
+ >>> config.registry.registerAdapter(factory, (IMyView, IRequest), ILayoutTemplate)
+
+ >>> try:
+ ... view()
+ ... except Exception as e:
+ ... print(repr(e))
+ ContentProviderLookupError('pagelet...)
+
+This exception is raised because the pagelet is not yet registered; this should be done
+automatically when *pyams_pagelet* package is included into Pyramid configuration:
+
+ >>> from zope.contentprovider.interfaces import IContentProvider
+ >>> from pyams_pagelet.interfaces import IPagelet
+ >>> from pyams_pagelet.pagelet import PageletRenderer
+ >>> config.registry.registerAdapter(PageletRenderer,
+ ... (Interface, IRequest, IPagelet),
+ ... IContentProvider, name='pagelet')
+ >>> print(view())
+ 200 OK
+ Content-Type: text/html; charset=UTF-8
+ Content-Length: 99
+ <BLANKLINE>
+ <html>
+ <body>
+ <div class="pagelet"><div>Base template content</div></div>
+ </body>
+ </html>
+ <BLANKLINE>
+
+ >>> tearDown()
--- a/src/pyams_pagelet/doctests/README.txt Mon Jun 11 17:30:43 2018 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,3 +0,0 @@
-=====================
-pyams_pagelet package
-=====================
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_pagelet/interfaces.py Wed Nov 27 19:37:28 2019 +0100
@@ -0,0 +1,52 @@
+#
+# 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.interfaces module
+
+"""
+
+from pyramid.interfaces import IView
+from zope.contentprovider.interfaces import IContentProvider
+from zope.interface import Attribute, implementer
+from zope.interface.interfaces import IObjectEvent, ObjectEvent
+
+
+__docformat__ = 'restructuredtext'
+
+
+class IPagelet(IView):
+ """Pagelet interface"""
+
+ def update(self):
+ """Update the pagelet data."""
+
+ def render(self):
+ """Render the pagelet content w/o o-wrap."""
+
+
+class IPageletRenderer(IContentProvider):
+ """Render a pagelet by calling it's 'render' method"""
+
+
+class IPageletCreatedEvent(IObjectEvent):
+ """Pagelet created event interface"""
+
+ request = Attribute('The request object')
+
+
+@implementer(IPageletCreatedEvent)
+class PageletCreatedEvent(ObjectEvent):
+ """Pagelet created event"""
+
+ def __init__(self, object): # pylint: disable=redefined-builtin
+ super(PageletCreatedEvent, self).__init__(object)
+ self.request = object.request
--- a/src/pyams_pagelet/interfaces/__init__.py Mon Jun 11 17:30:43 2018 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,52 +0,0 @@
-#
-# 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 packages
-
-# import interfaces
-from pyramid.interfaces import IView
-from zope.component.interfaces import IObjectEvent, ObjectEvent
-from zope.contentprovider.interfaces import IContentProvider
-
-# import packages
-from zope.interface import implementer, Attribute
-
-
-class IPagelet(IView):
- """Pagelet interface"""
-
- def update(self):
- """Update the pagelet data."""
-
- def render(self):
- """Render the pagelet content w/o o-wrap."""
-
-
-class IPageletRenderer(IContentProvider):
- """Render a pagelet by calling it's 'render' method"""
-
-
-class IPageletCreatedEvent(IObjectEvent):
- """Pagelet created event interface"""
-
- request = Attribute('The request object')
-
-
-@implementer(IPageletCreatedEvent)
-class PageletCreatedEvent(ObjectEvent):
- """Pagelet created event"""
-
- def __init__(self, object):
- super(PageletCreatedEvent, self).__init__(object)
- self.request = object.request
--- a/src/pyams_pagelet/metaconfigure.py Mon Jun 11 17:30:43 2018 +0200
+++ b/src/pyams_pagelet/metaconfigure.py Wed Nov 27 19:37:28 2019 +0100
@@ -10,32 +10,36 @@
# FOR A PARTICULAR PURPOSE.
#
-
-# import standard packages
+"""PyAMS_pagelet.metaconfigure module
-# import interfaces
-from pyams_pagelet.interfaces import IPagelet
-from pyramid.interfaces import IRequest
+This module provides handlers for ZCML directives.
+"""
-# import packages
-from pyams_pagelet.pagelet import Pagelet
from pyramid.exceptions import ConfigurationError
+from pyramid.interfaces import IRequest
from pyramid_zcml import with_context
from zope.component import zcml
from zope.component.interface import provideInterface
from zope.interface import Interface, classImplements
+from pyams_pagelet.interfaces import IPagelet
+from pyams_pagelet.pagelet import Pagelet
+
def PageletDirective(_context, name, view,
context=Interface,
permission=None,
layer=IRequest,
**kwargs):
+ # pylint: disable=invalid-name
+ """Pagelet ZCML directive"""
+
if not view:
- raise ConfigurationError("You must specify a view class.")
- cdict = {}
- cdict['__name__'] = name
- cdict['permission'] = permission
+ raise ConfigurationError("You must specify a view class or interface")
+ cdict = {
+ '__name__': name,
+ 'permission': permission
+ }
cdict.update(kwargs)
new_class = type(view.__name__, (view, Pagelet), cdict)
--- a/src/pyams_pagelet/metadirectives.py Mon Jun 11 17:30:43 2018 +0200
+++ b/src/pyams_pagelet/metadirectives.py Wed Nov 27 19:37:28 2019 +0100
@@ -10,19 +10,19 @@
# FOR A PARTICULAR PURPOSE.
#
-__docformat__ = 'restructuredtext'
-
+"""PyAMS_paget.metadirectives module
-# import standard packages
+This module provides interface of ZCML directives.
+"""
-# import interfaces
-
-# import packages
from zope.configuration.fields import GlobalObject
from zope.interface import Interface
from zope.schema import TextLine
+__docformat__ = 'restructuredtext'
+
+
class IPageletDirective(Interface):
"""Pagelet ZCML directive interface"""
@@ -34,7 +34,7 @@
required=False)
layer = GlobalObject(title="The request interface or class this pagelet is for",
- description="Defaults to zope.publisher.interfaces.browser.IDefaultBrowserLayer.",
+ description="Defaults to pyramid.interfaces.IRequest",
required=False)
view = GlobalObject(title='View class',
@@ -46,4 +46,5 @@
required=False)
+# pylint: disable=vo-value-for-parameter
IPageletDirective.setTaggedValue('keyword_arguments', True)
--- a/src/pyams_pagelet/pagelet.py Mon Jun 11 17:30:43 2018 +0200
+++ b/src/pyams_pagelet/pagelet.py Wed Nov 27 19:37:28 2019 +0100
@@ -10,34 +10,36 @@
# FOR A PARTICULAR PURPOSE.
#
-__docformat__ = 'restructuredtext'
-
+"""PyAMS_pagelet.pagelet module
-# import standard packages
+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
-logger = logging.getLogger('PyAMS (pagelet)')
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
-# import interfaces
from pyams_pagelet.interfaces import IPagelet, IPageletRenderer, PageletCreatedEvent
from pyams_template.interfaces import IContentTemplate, ILayoutTemplate
-from pyramid.interfaces import IRequest
-from pyramid_chameleon.interfaces import IChameleonTranslate
+from pyams_utils.adapter import adapter_config
+
-# import packages
-from pyams_utils.adapter import adapter_config
-from pyramid.httpexceptions import HTTPUnauthorized
-from pyramid.response import Response
-from zope.component import queryUtility
-from zope.interface import implementer, Interface
+__docformat__ = 'restructuredtext'
+LOGGER = logging.getLogger('PyAMS (pagelet)')
REDIRECT_STATUS_CODES = (301, 302, 303)
@implementer(IPagelet)
-class Pagelet(object):
+class Pagelet:
"""Content provider with layout support"""
template = None
@@ -53,14 +55,20 @@
request.registry.notify(PageletCreatedEvent(self))
def update(self):
- self.request.annotations['view'] = self
+ """See `zope.contentprovider.interfaces.IContentProvider`"""
+ annotations = getattr(self.request, 'annotations', None)
+ if annotations is not None:
+ annotations['view'] = self
def render(self):
+ """See `zope.contentprovider.interfaces.IContentProvider`"""
request = self.request
- cdict = {'context': self.context,
- 'request': request,
- 'view': self,
- 'translate': queryUtility(IChameleonTranslate)}
+ 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),
@@ -68,7 +76,7 @@
if template is None:
template = registry.getMultiAdapter((self, request), IContentTemplate)
return template(**cdict)
- return self.template(**cdict)
+ return self.template(**cdict) # pylint: disable=not-callable
def __call__(self, **kwargs):
"""Call update and return layout template"""
@@ -77,10 +85,12 @@
return ''
request = self.request
- cdict = {'context': self.context,
- 'request': request,
- 'view': self,
- 'translate': queryUtility(IChameleonTranslate)}
+ cdict = {
+ 'context': self.context,
+ 'request': request,
+ 'view': self,
+ 'translate': queryUtility(IChameleonTranslate)
+ }
cdict.update(kwargs)
if self.layout is None:
registry = request.registry
@@ -89,11 +99,11 @@
if layout is None:
layout = registry.getMultiAdapter((self, request), ILayoutTemplate)
return Response(layout(**cdict))
- return Response(self.layout(**cdict))
+ return Response(self.layout(**cdict)) # pylint: disable=not-callable
@adapter_config(name='pagelet', context=(Interface, IRequest, IPagelet), provides=IPageletRenderer)
-class PageletRenderer(object):
+class PageletRenderer:
"""Pagelet renderer"""
def __init__(self, context, request, pagelet):
@@ -103,13 +113,14 @@
self.request = request
def update(self):
- pass
+ """See `zope.contentprovider.interfaces.IContentProvider`"""
def render(self):
+ """See `zope.contentprovider.interfaces.IContentProvider`"""
return self.__parent__.render()
-class pagelet_config(object):
+class pagelet_config: # pylint: disable=invalid-name
"""Function or class decorator used to declare a pagelet"""
venusian = venusian # for testing injection
@@ -126,33 +137,34 @@
settings = self.__dict__.copy()
depth = settings.pop('_depth', 0)
- def callback(context, name, ob):
+ def callback(context, name, obj): # pylint: disable=unused-argument
+ """Venusian decorator callback"""
cdict = {
'__name__': settings.get('name'),
- '__module__': ob.__module__,
+ '__module__': obj.__module__,
'permission': settings.get('permission')
}
- new_class = type(ob.__name__, (ob, Pagelet), cdict)
+ new_class = type(obj.__name__, (obj, Pagelet), cdict)
- config = context.config.with_package(info.module)
- logger.debug('Registering pagelet view "{0}" for {1} ({2})'.format(settings.get('name'),
- str(settings.get('context', Interface)),
- str(new_class)))
- config.registry.registerAdapter(new_class,
- (settings.get('context', Interface),
- settings.get('request_type', IRequest)),
- IPagelet, settings.get('name'))
+ 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':
+ 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 # fbo "action_method"
+ settings['_info'] = info.codeinfo # pylint: disable=no-member
return wrapped
--- a/src/pyams_pagelet/tests/__init__.py Mon Jun 11 17:30:43 2018 +0200
+++ b/src/pyams_pagelet/tests/__init__.py Wed Nov 27 19:37:28 2019 +0100
@@ -1,1 +1,29 @@
+#
+# 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.
+#
+"""
+Generic Test case for pyams_pagelet doctest
+"""
+
+import os
+import sys
+
+__docformat__ = 'restructuredtext'
+
+
+def get_package_dir(value):
+ """Get package directory"""
+
+ package_dir = os.path.split(value)[0]
+ if package_dir not in sys.path:
+ sys.path.append(package_dir)
+ return package_dir
--- a/src/pyams_pagelet/tests/test_utilsdocs.py Mon Jun 11 17:30:43 2018 +0200
+++ b/src/pyams_pagelet/tests/test_utilsdocs.py Wed Nov 27 19:37:28 2019 +0100
@@ -14,18 +14,20 @@
Generic Test case for pyams_pagelet doctest
"""
+import doctest
+import os
+import unittest
+
+from pyams_pagelet.tests import get_package_dir
+
+
__docformat__ = 'restructuredtext'
-import unittest
-import doctest
-import sys
-import os
+CURRENT_DIR = os.path.abspath(os.path.dirname(__file__))
-current_dir = os.path.dirname(__file__)
-
-def doc_suite(test_dir, setUp=None, tearDown=None, globs=None):
- """Returns a test suite, based on doctests found in /doctest."""
+def doc_suite(test_dir, setUp=None, tearDown=None, globs=None): # pylint: disable=invalid-name
+ """Returns a test suite, based on doctests found in /doctests"""
suite = []
if globs is None:
globs = globals()
@@ -33,15 +35,12 @@
flags = (doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE |
doctest.REPORT_ONLY_FIRST_FAILURE)
- package_dir = os.path.split(test_dir)[0]
- if package_dir not in sys.path:
- sys.path.append(package_dir)
-
+ package_dir = get_package_dir(test_dir)
doctest_dir = os.path.join(package_dir, 'doctests')
# filtering files on extension
docs = [os.path.join(doctest_dir, doc) for doc in
- os.listdir(doctest_dir) if doc.endswith('.txt')]
+ os.listdir(doctest_dir) if doc.endswith('.txt') or doc.endswith('.rst')]
for test in docs:
suite.append(doctest.DocFileSuite(test, optionflags=flags,
@@ -51,10 +50,11 @@
return unittest.TestSuite(suite)
+
def test_suite():
"""returns the test suite"""
- return doc_suite(current_dir)
+ return doc_suite(CURRENT_DIR)
+
if __name__ == '__main__':
unittest.main(defaultTest='test_suite')
-
--- a/src/pyams_pagelet/tests/test_utilsdocstrings.py Mon Jun 11 17:30:43 2018 +0200
+++ b/src/pyams_pagelet/tests/test_utilsdocstrings.py Wed Nov 27 19:37:28 2019 +0100
@@ -14,16 +14,17 @@
Generic Test case for pyams_pagelet doc strings
"""
+import doctest
+import os
+import sys
+import unittest
+
+
__docformat__ = 'restructuredtext'
-import unittest
-import doctest
-import sys
-import os
+CURRENT_DIR = os.path.abspath(os.path.dirname(__file__))
-current_dir = os.path.abspath(os.path.dirname(__file__))
-
def doc_suite(test_dir, globs=None):
"""Returns a test suite, based on doc tests strings found in /*.py"""
suite = []
@@ -43,7 +44,7 @@
docs = [doc for doc in docs if not doc.startswith('__')]
for test in docs:
- fd = open(os.path.join(package_dir, test))
+ fd = open(os.path.join(package_dir, test)) # pylint: disable=invalid-name
content = fd.read()
fd.close()
if '>>> ' not in content:
@@ -55,9 +56,11 @@
return unittest.TestSuite(suite)
+
def test_suite():
"""returns the test suite"""
- return doc_suite(current_dir)
+ return doc_suite(CURRENT_DIR)
+
if __name__ == '__main__':
unittest.main(defaultTest='test_suite')