# HG changeset patch # User Thierry Florac # Date 1544012588 -3600 # Node ID 6ab01534cc9278f3a871c666b0a133991d889b30 Rebuild repository after corruption diff -r 000000000000 -r 6ab01534cc92 .hgignore --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.hgignore Wed Dec 05 13:23:08 2018 +0100 @@ -0,0 +1,19 @@ + +syntax: regexp +^develop-eggs$ +syntax: regexp +^parts$ +syntax: regexp +^bin$ +syntax: regexp +^\.installed\.cfg$ +syntax: regexp +^\.settings$ +syntax: regexp +^build$ +syntax: regexp +^dist$ +syntax: regexp +^\.idea$ +syntax: regexp +.*\.pyc$ diff -r 000000000000 -r 6ab01534cc92 .hgtags --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.hgtags Wed Dec 05 13:23:08 2018 +0100 @@ -0,0 +1,6 @@ +6acf661dc29963d3131b0bb88b0e2eaeb3b5299d 0.1.0 +64ae0f110fbdae5b74b87d11117736fb104d172b 0.1.1 +60cbd36af4f643ff1f7e6c2b28817db29c9d7967 0.1.2 +25288b4939b57a81c06a889900761b03e9c49e72 0.1.3 +857b6364ca4533d9f9dda7fab9df1093676b3f68 0.1.4 +c14b2a9dde6377c8c68ff2b6f1d5eaa705c85cb8 0.1.5 diff -r 000000000000 -r 6ab01534cc92 LICENSE --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/LICENSE Wed Dec 05 13:23:08 2018 +0100 @@ -0,0 +1,42 @@ +Zope Public License (ZPL) Version 2.1 +===================================== + +A copyright notice accompanies this license document that identifies +the copyright holders. + +This license has been certified as open source. It has also been designated +as GPL compatible by the Free Software Foundation (FSF). + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions in source code must retain the accompanying copyright + notice, this list of conditions, and the following disclaimer. + 2. Redistributions in binary form must reproduce the accompanying copyright + notice, this list of conditions, and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Names of the copyright holders must not be used to endorse or promote + products derived from this software without prior written permission + from the copyright holders. + 4. The right to distribute this software or to use it for any purpose does + not give you the right to use Servicemarks (sm) or Trademarks (tm) of the + copyright holders. Use of them is covered by separate agreement with the + copyright holders. + 5. If any files are modified, you must cause the modified files to carry + prominent notices stating that you changed the files and the date of any + change. + + +Disclaimer +========== + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff -r 000000000000 -r 6ab01534cc92 MANIFEST.in --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MANIFEST.in Wed Dec 05 13:23:08 2018 +0100 @@ -0,0 +1,5 @@ +include *.txt +recursive-include docs * +recursive-include src * +global-exclude *.pyc +global-exclude *.*~ diff -r 000000000000 -r 6ab01534cc92 bootstrap.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bootstrap.py Wed Dec 05 13:23:08 2018 +0100 @@ -0,0 +1,210 @@ +############################################################################## +# +# Copyright (c) 2006 Zope Foundation and Contributors. +# 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. +# +############################################################################## +"""Bootstrap a buildout-based project + +Simply run this script in a directory containing a buildout.cfg. +The script accepts buildout command-line options, so you can +use the -c option to specify an alternate configuration file. +""" + +import os +import shutil +import sys +import tempfile + +from optparse import OptionParser + +__version__ = '2015-07-01' +# See zc.buildout's changelog if this version is up to date. + +tmpeggs = tempfile.mkdtemp(prefix='bootstrap-') + +usage = '''\ +[DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options] + +Bootstraps a buildout-based project. + +Simply run this script in a directory containing a buildout.cfg, using the +Python that you want bin/buildout to use. + +Note that by using --find-links to point to local resources, you can keep +this script from going over the network. +''' + +parser = OptionParser(usage=usage) +parser.add_option("--version", + action="store_true", default=False, + help=("Return bootstrap.py version.")) +parser.add_option("-t", "--accept-buildout-test-releases", + dest='accept_buildout_test_releases', + action="store_true", default=False, + help=("Normally, if you do not specify a --buildout-version, " + "the bootstrap script and buildout gets the newest " + "*final* versions of zc.buildout and its recipes and " + "extensions for you. If you use this flag, " + "bootstrap and buildout will get the newest releases " + "even if they are alphas or betas.")) +parser.add_option("-c", "--config-file", + help=("Specify the path to the buildout configuration " + "file to be used.")) +parser.add_option("-f", "--find-links", + help=("Specify a URL to search for buildout releases")) +parser.add_option("--allow-site-packages", + action="store_true", default=False, + help=("Let bootstrap.py use existing site packages")) +parser.add_option("--buildout-version", + help="Use a specific zc.buildout version") +parser.add_option("--setuptools-version", + help="Use a specific setuptools version") +parser.add_option("--setuptools-to-dir", + help=("Allow for re-use of existing directory of " + "setuptools versions")) + +options, args = parser.parse_args() +if options.version: + print("bootstrap.py version %s" % __version__) + sys.exit(0) + + +###################################################################### +# load/install setuptools + +try: + from urllib.request import urlopen +except ImportError: + from urllib2 import urlopen + +ez = {} +if os.path.exists('ez_setup.py'): + exec(open('ez_setup.py').read(), ez) +else: + exec(urlopen('https://bootstrap.pypa.io/ez_setup.py').read(), ez) + +if not options.allow_site_packages: + # ez_setup imports site, which adds site packages + # this will remove them from the path to ensure that incompatible versions + # of setuptools are not in the path + import site + # inside a virtualenv, there is no 'getsitepackages'. + # We can't remove these reliably + if hasattr(site, 'getsitepackages'): + for sitepackage_path in site.getsitepackages(): + # Strip all site-packages directories from sys.path that + # are not sys.prefix; this is because on Windows + # sys.prefix is a site-package directory. + if sitepackage_path != sys.prefix: + sys.path[:] = [x for x in sys.path + if sitepackage_path not in x] + +setup_args = dict(to_dir=tmpeggs, download_delay=0) + +if options.setuptools_version is not None: + setup_args['version'] = options.setuptools_version +if options.setuptools_to_dir is not None: + setup_args['to_dir'] = options.setuptools_to_dir + +ez['use_setuptools'](**setup_args) +import setuptools +import pkg_resources + +# This does not (always?) update the default working set. We will +# do it. +for path in sys.path: + if path not in pkg_resources.working_set.entries: + pkg_resources.working_set.add_entry(path) + +###################################################################### +# Install buildout + +ws = pkg_resources.working_set + +setuptools_path = ws.find( + pkg_resources.Requirement.parse('setuptools')).location + +# Fix sys.path here as easy_install.pth added before PYTHONPATH +cmd = [sys.executable, '-c', + 'import sys; sys.path[0:0] = [%r]; ' % setuptools_path + + 'from setuptools.command.easy_install import main; main()', + '-mZqNxd', tmpeggs] + +find_links = os.environ.get( + 'bootstrap-testing-find-links', + options.find_links or + ('http://downloads.buildout.org/' + if options.accept_buildout_test_releases else None) + ) +if find_links: + cmd.extend(['-f', find_links]) + +requirement = 'zc.buildout' +version = options.buildout_version +if version is None and not options.accept_buildout_test_releases: + # Figure out the most recent final version of zc.buildout. + import setuptools.package_index + _final_parts = '*final-', '*final' + + def _final_version(parsed_version): + try: + return not parsed_version.is_prerelease + except AttributeError: + # Older setuptools + for part in parsed_version: + if (part[:1] == '*') and (part not in _final_parts): + return False + return True + + index = setuptools.package_index.PackageIndex( + search_path=[setuptools_path]) + if find_links: + index.add_find_links((find_links,)) + req = pkg_resources.Requirement.parse(requirement) + if index.obtain(req) is not None: + best = [] + bestv = None + for dist in index[req.project_name]: + distv = dist.parsed_version + if _final_version(distv): + if bestv is None or distv > bestv: + best = [dist] + bestv = distv + elif distv == bestv: + best.append(dist) + if best: + best.sort() + version = best[-1].version +if version: + requirement = '=='.join((requirement, version)) +cmd.append(requirement) + +import subprocess +if subprocess.call(cmd) != 0: + raise Exception( + "Failed to execute command:\n%s" % repr(cmd)[1:-1]) + +###################################################################### +# Import and run buildout + +ws.add_entry(tmpeggs) +ws.require(requirement) +import zc.buildout.buildout + +if not [a for a in args if '=' not in a]: + args.append('bootstrap') + +# if -c was provided, we push it back into args for buildout' main function +if options.config_file is not None: + args[0:0] = ['-c', options.config_file] + +zc.buildout.buildout.main(args) +shutil.rmtree(tmpeggs) diff -r 000000000000 -r 6ab01534cc92 buildout.cfg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/buildout.cfg Wed Dec 05 13:23:08 2018 +0100 @@ -0,0 +1,78 @@ +[buildout] +eggs-directory = /var/local/env/pyams/eggs +extends = http://download.ztfy.org/pyams/pyams-dev.cfg +find-links = http://download.ztfy.org/eggs + +socket-timeout = 3 + +#allow-picked-versions = false +show-picked-versions = true +newest = false + +allow-hosts = + bitbucket.org + *.python.org + *.sourceforge.net + github.com + +versions = versions + +src = src +develop = + . + ../ext/lingua + ../pyams_catalog + ../pyams_file + ../pyams_form + ../pyams_i18n + ../pyams_pagelet + ../pyams_skin + ../pyams_template + ../pyams_utils + ../pyams_viewlet + ../pyams_zmi + +parts = + package + i18n + pyflakes + test + +[package] +recipe = zc.recipe.egg +eggs = + chameleon + pyams_utils + pyams_viewlet + pyramid + zope.component + zope.configuration + zope.contentprovider + zope.interface + zope.location + zope.schema + +[i18n] +recipe = zc.recipe.egg +eggs = + babel + lingua + +[pyflakes] +recipe = zc.recipe.egg +eggs = pyflakes +scripts = pyflakes +entry-points = pyflakes=pyflakes.scripts.pyflakes:main +initialization = if not sys.argv[1:]: sys.argv[1:] = ["${buildout:src}"] + +[pyflakesrun] +recipe = collective.recipe.cmd +on_install = true +cmds = ${buildout:develop}/bin/${pyflakes:scripts} + +[test] +recipe = zc.recipe.testrunner +eggs = pyams_viewlet [test] + +[versions] +pyams_viewlet = 0.1.5 diff -r 000000000000 -r 6ab01534cc92 docs/HISTORY.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/docs/HISTORY.txt Wed Dec 05 13:23:08 2018 +0100 @@ -0,0 +1,27 @@ +Changelog +========= + +0.1.5 +----- + - added regular expression to get content provider name + +0.1.4 +----- + - updated viewlet and content provider decorators to set module name + +0.1.3 +----- + - in weight ordered viewlets managers, sort viewlets with same weight by labels + - added "empty" viewlet and content provider (rendering an empty string) as base classes + +0.1.2 +----- + - splitted content provider class between BaseContentProvider and ViewContentProvider + +0.1.1 +----- + - added context for request permission check + +0.1.0 +----- + - first release diff -r 000000000000 -r 6ab01534cc92 docs/README.txt diff -r 000000000000 -r 6ab01534cc92 setup.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/setup.py Wed Dec 05 13:23:08 2018 +0100 @@ -0,0 +1,75 @@ +### -*- coding: utf-8 -*- #################################################### +############################################################################## +# +# Copyright (c) 2008-2015 Thierry Florac +# 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. +# +############################################################################## + +""" +This module contains pyams_viewlet package +""" +import os +from setuptools import setup, find_packages + +DOCS = os.path.join(os.path.dirname(__file__), + 'docs') + +README = os.path.join(DOCS, 'README.txt') +HISTORY = os.path.join(DOCS, 'HISTORY.txt') + +version = '0.1.5' +long_description = open(README).read() + '\n\n' + open(HISTORY).read() + +tests_require = [] + +setup(name='pyams_viewlet', + version=version, + description="PyAMS base viewlet interfaces and classes; z3c.viewlet package adapted to Pyramid", + long_description=long_description, + classifiers=[ + "License :: OSI Approved :: Zope Public License", + "Development Status :: 4 - Beta", + "Programming Language :: Python", + "Framework :: Pyramid", + "Topic :: Software Development :: Libraries :: Python Modules", + ], + keywords='Pyramid PyAMS viewlet', + author='Thierry Florac', + author_email='tflorac@ulthar.net', + url='http://hg.ztfy.org/pyams/pyams_viewlet', + license='ZPL', + packages=find_packages('src'), + package_dir={'': 'src'}, + namespace_packages=[], + include_package_data=True, + package_data={'': ['*.zcml', '*.txt', '*.pt', '*.pot', '*.po', '*.mo', '*.png', '*.gif', '*.jpeg', '*.jpg', '*.css', '*.js']}, + zip_safe=False, + # uncomment this to be able to run tests with setup.py + test_suite="pyams_viewlet.tests.test_utilsdocs.test_suite", + tests_require=tests_require, + extras_require=dict(test=tests_require), + install_requires=[ + 'setuptools', + # -*- Extra requirements: -*- + 'chameleon', + 'pyams_utils', + 'pyramid', + 'zope.component', + 'zope.configuration', + 'zope.contentprovider', + 'zope.interface', + 'zope.location', + 'zope.schema' + ], + entry_points=""" + # -*- Entry points: -*- + """, + ) diff -r 000000000000 -r 6ab01534cc92 src/pyams_viewlet.egg-info/PKG-INFO --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_viewlet.egg-info/PKG-INFO Wed Dec 05 13:23:08 2018 +0100 @@ -0,0 +1,46 @@ +Metadata-Version: 2.1 +Name: pyams-viewlet +Version: 0.1.5 +Summary: PyAMS base viewlet interfaces and classes; z3c.viewlet package adapted to Pyramid +Home-page: http://hg.ztfy.org/pyams/pyams_viewlet +Author: Thierry Florac +Author-email: tflorac@ulthar.net +License: ZPL +Description: + + Changelog + ========= + + 0.1.5 + ----- + - added regular expression to get content provider name + + 0.1.4 + ----- + - updated viewlet and content provider decorators to set module name + + 0.1.3 + ----- + - in weight ordered viewlets managers, sort viewlets with same weight by labels + - added "empty" viewlet and content provider (rendering an empty string) as base classes + + 0.1.2 + ----- + - splitted content provider class between BaseContentProvider and ViewContentProvider + + 0.1.1 + ----- + - added context for request permission check + + 0.1.0 + ----- + - first release + +Keywords: Pyramid PyAMS viewlet +Platform: UNKNOWN +Classifier: License :: OSI Approved :: Zope Public License +Classifier: Development Status :: 4 - Beta +Classifier: Programming Language :: Python +Classifier: Framework :: Pyramid +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Provides-Extra: test diff -r 000000000000 -r 6ab01534cc92 src/pyams_viewlet.egg-info/SOURCES.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_viewlet.egg-info/SOURCES.txt Wed Dec 05 13:23:08 2018 +0100 @@ -0,0 +1,25 @@ +MANIFEST.in +setup.py +docs/HISTORY.txt +docs/README.txt +src/pyams_viewlet/__init__.py +src/pyams_viewlet/configure.zcml +src/pyams_viewlet/manager.py +src/pyams_viewlet/meta.zcml +src/pyams_viewlet/metaconfigure.py +src/pyams_viewlet/metadirectives.py +src/pyams_viewlet/provider.py +src/pyams_viewlet/viewlet.py +src/pyams_viewlet.egg-info/PKG-INFO +src/pyams_viewlet.egg-info/SOURCES.txt +src/pyams_viewlet.egg-info/dependency_links.txt +src/pyams_viewlet.egg-info/entry_points.txt +src/pyams_viewlet.egg-info/namespace_packages.txt +src/pyams_viewlet.egg-info/not-zip-safe +src/pyams_viewlet.egg-info/requires.txt +src/pyams_viewlet.egg-info/top_level.txt +src/pyams_viewlet/doctests/README.txt +src/pyams_viewlet/interfaces/__init__.py +src/pyams_viewlet/tests/__init__.py +src/pyams_viewlet/tests/test_utilsdocs.py +src/pyams_viewlet/tests/test_utilsdocstrings.py \ No newline at end of file diff -r 000000000000 -r 6ab01534cc92 src/pyams_viewlet.egg-info/dependency_links.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_viewlet.egg-info/dependency_links.txt Wed Dec 05 13:23:08 2018 +0100 @@ -0,0 +1,1 @@ + diff -r 000000000000 -r 6ab01534cc92 src/pyams_viewlet.egg-info/entry_points.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_viewlet.egg-info/entry_points.txt Wed Dec 05 13:23:08 2018 +0100 @@ -0,0 +1,3 @@ + + # -*- Entry points: -*- + \ No newline at end of file diff -r 000000000000 -r 6ab01534cc92 src/pyams_viewlet.egg-info/namespace_packages.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_viewlet.egg-info/namespace_packages.txt Wed Dec 05 13:23:08 2018 +0100 @@ -0,0 +1,1 @@ + diff -r 000000000000 -r 6ab01534cc92 src/pyams_viewlet.egg-info/not-zip-safe --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_viewlet.egg-info/not-zip-safe Wed Dec 05 13:23:08 2018 +0100 @@ -0,0 +1,1 @@ + diff -r 000000000000 -r 6ab01534cc92 src/pyams_viewlet.egg-info/requires.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_viewlet.egg-info/requires.txt Wed Dec 05 13:23:08 2018 +0100 @@ -0,0 +1,12 @@ +setuptools +chameleon +pyams_utils +pyramid +zope.component +zope.configuration +zope.contentprovider +zope.interface +zope.location +zope.schema + +[test] diff -r 000000000000 -r 6ab01534cc92 src/pyams_viewlet.egg-info/top_level.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_viewlet.egg-info/top_level.txt Wed Dec 05 13:23:08 2018 +0100 @@ -0,0 +1,1 @@ +pyams_viewlet diff -r 000000000000 -r 6ab01534cc92 src/pyams_viewlet/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_viewlet/__init__.py Wed Dec 05 13:23:08 2018 +0100 @@ -0,0 +1,37 @@ +# +# Copyright (c) 2008-2015 Thierry Florac +# 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 interfaces + +# import packages +from chameleon import PageTemplateFile +from pyams_viewlet.provider import ProviderExpr + +from pyramid.i18n import TranslationStringFactory +_ = TranslationStringFactory('pyams_viewlet') + + +def includeme(config): + """Pyramid include""" + + # load registry components + config.scan() + + if hasattr(config, 'load_zcml'): + config.load_zcml('configure.zcml') + + PageTemplateFile.expression_types['provider'] = ProviderExpr diff -r 000000000000 -r 6ab01534cc92 src/pyams_viewlet/configure.zcml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_viewlet/configure.zcml Wed Dec 05 13:23:08 2018 +0100 @@ -0,0 +1,11 @@ + + + + + + + + + + diff -r 000000000000 -r 6ab01534cc92 src/pyams_viewlet/doctests/README.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_viewlet/doctests/README.txt Wed Dec 05 13:23:08 2018 +0100 @@ -0,0 +1,133 @@ +================== +ZTFY.utils package +================== + +Introduction +------------ + +This package is composed of a set of utility functions, which in complement with zope.app.zapi +package can make Zope management easier. + + +Unicode functions +----------------- + +While working with extended characters sets containing accentuated characters, it's necessary to +conversing strings to UTF8 so that they can be used without any conversion problem. + + >>> from ztfy.utils import unicode + +'translateString' is a utility function which can be used, for example, to generate an object's id +without space and with accentuated characters converted to their unaccentuated version: + + >>> sample = 'Mon titre accentué' + >>> unicode.translateString(sample) + u'mon titre accentue' + +Results are lower-cased by default ; this can be avoided be setting the 'forceLower' parameter +to False: + + >>> unicode.translateString(sample, forceLower=False) + u'Mon titre accentue' + +If input string can contain 'slashes' (/) or 'backslashes' (\), they are normally removed ; +by using the 'escapeSlashes' parameter, the input string is splitted and only the last element is +returned ; this is handy to handle filenames on Windows platform: + + >>> sample = 'Autre / chaîne / accentuée' + >>> unicode.translateString(sample) + u'autre chaine accentuee' + >>> unicode.translateString(sample, escapeSlashes=True) + u'accentuee' + >>> sample = 'C:\\Program Files\\My Application\\test.txt' + >>> unicode.translateString(sample) + u'cprogram filesmy applicationtest.txt' + >>> unicode.translateString(sample, escapeSlashes=True) + u'test.txt' + +To remove remaining spaces or convert them to another character, you can use the "spaces" parameter +which can contain any string to be used instead of initial spaces: + + >>> sample = 'C:\\Program Files\\My Application\\test.txt' + >>> unicode.translateString(sample, spaces=' ') + u'cprogram filesmy applicationtest.txt' + >>> unicode.translateString(sample, spaces='-') + u'cprogram-filesmy-applicationtest.txt' + +Spaces replacement is made in the last step, so using it with "escapeSlashes" parameter only affects +the final result: + + >>> unicode.translateString(sample, escapeSlashes=True, spaces='-') + u'test.txt' + +Unicode module also provides encoding and decoding functions: + + >>> var = 'Chaîne accentuée' + >>> unicode.decode(var) + u'Cha\xeene accentu\xe9e' + >>> unicode.encode(unicode.decode(var)) == var + True + + >>> utf = u'Cha\xeene accentu\xe9e' + >>> unicode.encode(utf) + 'Cha\xc3\xaene accentu\xc3\xa9e' + >>> unicode.decode(unicode.encode(utf)) == utf + True + + +Dates functions +--------------- + +Dates functions are used to convert dates from/to string representation: + + >>> from datetime import datetime + >>> from ztfy.utils import date + >>> now = datetime.fromtimestamp(1205000000) + >>> now + datetime.datetime(2008, 3, 8, 19, 13, 20) + +You can get an unicode representation of a date in ASCII format using 'unidate' fonction ; date is +converted to GMT: + + >>> udate = date.unidate(now) + >>> udate + u'2008-03-08T19:13:20+00:00' + +'parsedate' can be used to convert ASCII format into datetime: + + >>> ddate = date.parsedate(udate) + >>> ddate + datetime.datetime(2008, 3, 8, 19, 13, 20, tzinfo=) + +'datetodatetime' can be used to convert a 'date' type to a 'datetime' value ; if a 'datetime' value +is used as argument, it is returned 'as is': + + >>> ddate.date() + datetime.date(2008, 3, 8) + >>> date.datetodatetime(ddate) + datetime.datetime(2008, 3, 8, 19, 13, 20, tzinfo=) + >>> date.datetodatetime(ddate.date()) + datetime.datetime(2008, 3, 8, 0, 0) + + +Timezones handling +------------------ + +Timezones handling game me headaches at first. I finally concluded that the best way (for me !) to handle +TZ data was to store every datetime value in GMT timezone. +As far as I know, there is no easy way to know the user's timezone from his request settings. So you can: +- store this timezone in user's profile, +- define a static server's timezone +- create and register a ServerTimezoneUtility to handle server default timezone. + +My current default user's timezone is set to 'Europe/Paris' ; you should probably update this setting in +'timezone.py' if you are located elsewhere. + + >>> from ztfy.utils import timezone + >>> timezone.tztime(ddate) + datetime.datetime(2008, 3, 8, 19, 13, 20, tzinfo=) + +'gmtime' function can be used to convert a datetime to GMT: + + >>> timezone.gmtime(now) + datetime.datetime(2008, 3, 8, 19, 13, 20, tzinfo=) diff -r 000000000000 -r 6ab01534cc92 src/pyams_viewlet/interfaces/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_viewlet/interfaces/__init__.py Wed Dec 05 13:23:08 2018 +0100 @@ -0,0 +1,57 @@ +# +# Copyright (c) 2008-2015 Thierry Florac +# 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 interfaces +from zope.contentprovider.interfaces import IContentProvider +from zope.interface.common.mapping import IReadMapping + +# import packages +from zope.interface import Attribute + + +class IViewlet(IContentProvider): + """A content provider that is managed by another content provider, known + as viewlet manager. + + Note that you *cannot* call viewlets directly as a provider, i.e. through + the TALES ``provider`` expression, since it always has to know its manager. + """ + + manager = Attribute("""The Viewlet Manager + + The viewlet manager for which the viewlet is registered. The viewlet + manager will contain any additional data that was provided by the + view, for example the TAL namespace attributes. + """) + + +class IViewletManager(IContentProvider, IReadMapping): + """A component that provides access to the content providers. + + The viewlet manager's responsibilities are: + + (1) Aggregation of all viewlets registered for the manager. + + (2) Apply a set of filters to determine the availability of the + viewlets. + + (3) Sort the viewlets based on some implemented policy. + + (4) Provide an environment in which the viewlets are rendered. + + (5) Render itself containing the HTML content of the viewlets. + """ diff -r 000000000000 -r 6ab01534cc92 src/pyams_viewlet/manager.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_viewlet/manager.py Wed Dec 05 13:23:08 2018 +0100 @@ -0,0 +1,289 @@ +# +# Copyright (c) 2008-2015 Thierry Florac +# 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 logging +logger = logging.getLogger('PyAMS (viewlet)') + +import venusian + +# import interfaces +from pyams_viewlet.interfaces import IViewletManager, IViewlet +from pyramid.interfaces import IRequest, IView +from zope.component.interfaces import ComponentLookupError +from zope.contentprovider.interfaces import BeforeUpdateEvent +from zope.location.interfaces import ILocation + +# import packages +from pyams_template.template import get_view_template +from pyams_utils.request import check_request +from pyramid.exceptions import ConfigurationError +from pyramid.httpexceptions import HTTPUnauthorized +from pyramid.threadlocal import get_current_registry +from zope.interface import implementer, classImplements, Interface + + +@implementer(IViewletManager) +class ViewletManager(object): + """The Viewlet Manager base + + A generic manager class which can be instantiated + """ + + permission = None + template = 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 + + 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)) + + def filter(self, viewlets): + """Sort out all content providers + + ``viewlets`` is a list of tuples of the form (name, viewlet). + """ + # Only return viewlets accessible to the principal + request = self.request + return [(name, viewlet) for name, viewlet in viewlets + if (not viewlet.permission) or request.has_permission(viewlet.permission, context=self.context)] + + def sort(self, viewlets): + """Sort the viewlets. + + ``viewlets`` is a list of tuples of the form (name, viewlet). + """ + # By default, we are not sorting by viewlet name. + return sorted(viewlets, key=lambda x: x[0]) + + def update(self): + """See zope.contentprovider.interfaces.IContentProvider""" + self.__updated = True + + # check permission + if self.permission and not self.request.has_permission(self.permission, context=self.context): + return + # Find all content providers for the region + viewlets = self._get_viewlets() + # Just use the viewlets from now on + self.viewlets = [] + for name, viewlet in viewlets: + if ILocation.providedBy(viewlet): + viewlet.__name__ = name + self.viewlets.append(viewlet) + self._update_viewlets() + + 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() + + def render(self): + """See zope.contentprovider.interfaces.IContentProvider""" + # Now render the view + if self.permission and not self.request.has_permission(self.permission, context=self.context): + return '' + if not self.viewlets: + return '' + if self.template: + return self.template(viewlets=self.viewlets) + else: + return '\n'.join([viewlet.render() for viewlet in self.viewlets]) + + +def ViewletManagerFactory(name, interface, bases=(), cdict=None): + """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. + if not (len(bases) == 1 and IViewletManager.implementedBy(bases[0])): + bases = bases + (ViewletManager,) + + viewlet_manager_class = type('' % interface.getName(), bases, attr_dict) + classImplements(viewlet_manager_class, interface) + return viewlet_manager_class + + +def get_weight(item): + """Get sort weight of a given viewlet""" + name, viewlet = item + try: + return int(viewlet.weight) + except (TypeError, AttributeError): + return 0 + + +def get_label(item, request=None): + """Get sort label of a given viewlet""" + name, viewlet = item + try: + if request is None: + request = check_request() + return request.localizer.translate(viewlet.label) + except AttributeError: + return '--' + + +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) + + +class WeightOrderedViewletManager(ViewletManager): + """Weight ordered viewlet managers. + + Viewlets with the same weight are sorted by label + """ + + def sort(self, viewlets): + return sorted(viewlets, key=lambda x: get_weight_and_label(x, request=self.request)) + + +def is_available(viewlet): + try: + return ((not viewlet.permission) or + viewlet.request.has_permission(viewlet.permission, context=viewlet.context)) and \ + viewlet.available + except AttributeError: + return True + + +class ConditionalViewletManager(WeightOrderedViewletManager): + """Conditional weight ordered viewlet managers.""" + + def filter(self, viewlets): + """Sort out all viewlets which are explicit not available + + ``viewlets`` is a list of tuples of the form (name, viewlet). + """ + return [(name, viewlet) for name, viewlet in viewlets if is_available(viewlet)] + + +class TemplateBasedViewletManager(object): + """Template based viewlet manager mixin class""" + + template = get_view_template() + + +class viewletmanager_config(object): + """Class or interface decorator used to declare a viewlet manager + + You can provide same arguments as in 'viewletManager' ZCML directive: + @name = name of the viewlet; may be unique for a given viewlet manager + @view = the view class or interface for which viewlet is displayed + @for_ = the context class or interface for which viewlet is displayed + @permission = name of a permission required to display the viewlet + @layer = request interface required to display the viewlet + @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 + @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, ob): + cdict = {'__name__': settings.get('name')} + if 'permission' in settings: + cdict['permission'] = settings.get('permission') + + if issubclass(ob, Interface): + class_ = settings.get('class_', ViewletManager) + provides = ob + else: + class_ = ob + 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))) + config = context.config.with_package(info.module) + config.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': + # 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 diff -r 000000000000 -r 6ab01534cc92 src/pyams_viewlet/meta.zcml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_viewlet/meta.zcml Wed Dec 05 13:23:08 2018 +0100 @@ -0,0 +1,19 @@ + + + + + + + + + + + diff -r 000000000000 -r 6ab01534cc92 src/pyams_viewlet/metaconfigure.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_viewlet/metaconfigure.py Wed Dec 05 13:23:08 2018 +0100 @@ -0,0 +1,100 @@ +# +# Copyright (c) 2008-2015 Thierry Florac +# 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 interfaces +from pyams_viewlet.interfaces import IViewletManager, IViewlet +from pyramid.interfaces import IRequest, IView + +# import packages +from pyams_viewlet.manager import ViewletManager, ViewletManagerFactory +from pyramid.exceptions import ConfigurationError +from zope.component import zcml +from zope.component.interface import provideInterface +from zope.interface import Interface, classImplements + + +def ViewletManagerDirective(_context, name, + context=Interface, + layer=IRequest, + view=IView, + provides=IViewletManager, + class_=None, + permission=None): + + # If class is not given we use the basic viewlet manager. + if class_ is None: + class_ = ViewletManager + + # Create a new class based on the class. + cdict = {'permission': permission} + new_class = ViewletManagerFactory(name, provides, bases=(class_,), cdict=cdict) + + # Register interfaces + _handle_for(_context, context) + zcml.interface(_context, view) + + # Create a checker for the viewlet manager + # checker.defineChecker(new_class, checker.Checker(required)) + + # register a viewlet manager + _context.action(discriminator=('viewletManager', context, layer, view, name), + callable=zcml.handler, + args=('registerAdapter', new_class, + (context, layer, view), + provides, name, _context.info)) + + +def ViewletDirective(_context, name, class_, + context=Interface, + layer=IRequest, + view=IView, + manager=IViewletManager, + attribute='render', + permission=None, + **kwargs): + + # Make sure the has the right form, if specified. + if attribute != 'render': + if not hasattr(class_, attribute): + raise ConfigurationError("The provided class doesn't have the specified attribute") + + cdict = {} + cdict['__name__'] = name + cdict['__page_attribute__'] = attribute + cdict['permission'] = permission + cdict.update(kwargs) + new_class = type(class_.__name__, (class_,), cdict) + + classImplements(new_class, IViewlet) + + # Register the interfaces. + _handle_for(_context, context) + zcml.interface(_context, view) + + # register viewlet + _context.action(discriminator=('viewlet', context, layer, view, manager, name), + callable=zcml.handler, + args=('registerAdapter', new_class, + (context, layer, view, manager), + IViewlet, name, _context.info)) + + +def _handle_for(_context, for_): + if for_ is not None: + _context.action(discriminator=None, + callable=provideInterface, + args=('', for_)) diff -r 000000000000 -r 6ab01534cc92 src/pyams_viewlet/metadirectives.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_viewlet/metadirectives.py Wed Dec 05 13:23:08 2018 +0100 @@ -0,0 +1,104 @@ +# +# Copyright (c) 2008-2015 Thierry Florac +# 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 interfaces +from pyams_viewlet.interfaces import IViewletManager + +# import packages +from zope.configuration.fields import GlobalInterface, GlobalObject +from zope.interface import Interface +from zope.schema import TextLine + + +class IContentProvider(Interface): + """A directive to register a simple content provider. + + Content providers are registered by their context (`for` attribute), the + request (`layer` attribute) and the view (`view` attribute). They also + must provide a name, so that they can be found using the TALES + ``provider`` namespace. Other than that, content providers are just like + any other views. + """ + + view = GlobalObject(title="The view the content provider is registered for", + description="The view can either be an interface or a class. By " + "default the provider is registered for all views, " + "the most common case.", + required=False, + default=Interface) + + name = TextLine(title="The name of the content provider", + description="The name of the content provider is used in the TALES " + "``provider`` namespace to look up the content " + "provider.", + required=True) + + for_ = GlobalObject(title="The interface or class this view is for", + required=False) + + permission = TextLine(title="Permission", + description="The permission needed to use the view", + required=False) + + class_ = GlobalObject(title="Class", + description="A class that provides attributes used by the view", + required=False) + + layer = GlobalInterface(title="The layer the view is in", + description="""A skin is composed of layers. It is common to put skin + specific views in a layer named after the skin. If the 'layer' + attribute is not supplied, it defaults to 'default'.""", + required=False) + + +class IViewletManagerDirective(IContentProvider): + """A directive to register a new viewlet manager. + + Viewlet manager registrations are very similar to content provider + registrations, since they are just a simple extension of content + providers. However, viewlet managers commonly have a specific provided + interface, which is used to discriminate the viewlets they are providing. + """ + + provides = GlobalInterface(title="The interface this viewlet manager provides", + description="A viewlet manager can provide an interface, which " + "is used to lookup its contained viewlets.", + required=False, + default=IViewletManager) + + +class IViewletDirective(IContentProvider): + """A directive to register a new viewlet. + + Viewlets are content providers that can only be displayed inside a viewlet + manager. Thus they are additionally discriminated by the manager. Viewlets + can rely on the specified viewlet manager interface to provide their + content. + + The viewlet directive also supports an undefined set of keyword arguments + that are set as attributes on the viewlet after creation. Those attributes + can then be used to implement sorting and filtering, for example. + """ + + manager = GlobalObject(title="Manager", + description="The interface or class of the viewlet manager", + required=False, + default=IViewletManager) + + +# Arbitrary keys and values are allowed to be passed to the viewlet. +IViewletDirective.setTaggedValue('keyword_arguments', True) diff -r 000000000000 -r 6ab01534cc92 src/pyams_viewlet/provider.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_viewlet/provider.py Wed Dec 05 13:23:08 2018 +0100 @@ -0,0 +1,68 @@ +# +# Copyright (c) 2008-2015 Thierry Florac +# 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 re + +from chameleon.astutil import Symbol +from chameleon.tales import StringExpr +from pyramid_zope_request import PyramidPublisherRequest +from zope.contentprovider.interfaces import BeforeUpdateEvent, ContentProviderLookupError, IContentProvider +from zope.contentprovider.tales import addTALNamespaceData +from zope.location.interfaces import ILocation + +from pyams_utils.tales import ContextExprMixin + + +CONTENT_PROVIDER_NAME = re.compile('([A-Za-z0-9_\-\.]+)') + + +def render_content_provider(econtext, name): + match = CONTENT_PROVIDER_NAME.match(name.strip()) + if match: + name = match.groups()[0] + else: + raise ContentProviderLookupError(name) + + context = econtext.get('context') + request = econtext.get('request') + if isinstance(request, PyramidPublisherRequest): + request = request._request + view = econtext.get('view') + + registry = request.registry + provider = registry.queryMultiAdapter((context, request, view), IContentProvider, name=name) + + # provide a useful error message, if the provider was not found. + if provider is None: + raise ContentProviderLookupError(name) + + # add the __name__ attribute if it implements ILocation + if ILocation.providedBy(provider): + provider.__name__ = name + + # Insert the data gotten from the context + addTALNamespaceData(provider, econtext) + + # Stage 1: Do the state update + registry.notify(BeforeUpdateEvent(provider, request)) + provider.update() + + # Stage 2: Render the HTML content + return provider.render() + + +class ProviderExpr(ContextExprMixin, StringExpr): + """provider: TALES expression""" + + transform = Symbol(render_content_provider) diff -r 000000000000 -r 6ab01534cc92 src/pyams_viewlet/tests/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_viewlet/tests/__init__.py Wed Dec 05 13:23:08 2018 +0100 @@ -0,0 +1,1 @@ + diff -r 000000000000 -r 6ab01534cc92 src/pyams_viewlet/tests/test_utilsdocs.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_viewlet/tests/test_utilsdocs.py Wed Dec 05 13:23:08 2018 +0100 @@ -0,0 +1,62 @@ +### -*- coding: utf-8 -*- #################################################### +############################################################################## +# +# Copyright (c) 2008-2015 Thierry Florac +# 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_viewlet doctest +""" +__docformat__ = 'restructuredtext' + +import unittest +import doctest +import sys +import os + + +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.""" + suite = [] + if globs is None: + globs = globals() + + 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) + + 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')] + + for test in docs: + suite.append(doctest.DocFileSuite(test, optionflags=flags, + globs=globs, setUp=setUp, + tearDown=tearDown, + module_relative=False)) + + return unittest.TestSuite(suite) + +def test_suite(): + """returns the test suite""" + return doc_suite(current_dir) + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') + diff -r 000000000000 -r 6ab01534cc92 src/pyams_viewlet/tests/test_utilsdocstrings.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_viewlet/tests/test_utilsdocstrings.py Wed Dec 05 13:23:08 2018 +0100 @@ -0,0 +1,65 @@ +### -*- coding: utf-8 -*- #################################################### +############################################################################## +# +# Copyright (c) 2008-2015 Thierry Florac +# 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_viewlet doc strings +""" +__docformat__ = 'restructuredtext' + +import unittest +import doctest +import sys +import os + + +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 = [] + if globs is None: + globs = globals() + + 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) + + # filtering files on extension + docs = [doc for doc in + os.listdir(package_dir) if doc.endswith('.py')] + docs = [doc for doc in docs if not doc.startswith('__')] + + for test in docs: + fd = open(os.path.join(package_dir, test)) + content = fd.read() + fd.close() + if '>>> ' not in content: + continue + test = test.replace('.py', '') + location = 'pyams_viewlet.%s' % test + suite.append(doctest.DocTestSuite(location, optionflags=flags, + globs=globs)) + + return unittest.TestSuite(suite) + +def test_suite(): + """returns the test suite""" + return doc_suite(current_dir) + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') diff -r 000000000000 -r 6ab01534cc92 src/pyams_viewlet/viewlet.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_viewlet/viewlet.py Wed Dec 05 13:23:08 2018 +0100 @@ -0,0 +1,216 @@ +# +# Copyright (c) 2008-2015 Thierry Florac +# 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 logging +logger = logging.getLogger('PyAMS (viewlet)') + +import venusian + +# import interfaces +from pyams_viewlet.interfaces import IViewlet, IViewletManager +from pyramid.interfaces import IRequest, IView +from zope.contentprovider.interfaces import IContentProvider + +# import packages +from pyams_template.template import get_view_template +from pyramid.exceptions import ConfigurationError +from zope.interface import implementer, Interface + + +@implementer(IContentProvider) +class EmptyContentProvider(object): + """Empty content provider base class""" + + permission = None + + def __init__(self, context, request): + self.context = context + self.request = request + + def __call__(self): + if self.permission and not self.request.has_permission(self.permission, context=self.context): + return '' + self.update() + return self.render() + + def update(self): + pass + + def render(self): + return '' + + +class BaseContentProvider(EmptyContentProvider): + """Base template based content provider""" + + render = get_view_template() + + +@implementer(IContentProvider) +class ViewContentProvider(BaseContentProvider): + """Template based content provider""" + + def __init__(self, context, request, view): + super(ViewContentProvider, self).__init__(context, request) + self.view = self.__parent__ = view + + +class contentprovider_config(object): + """Class decorator used to declare a content provider + + You can provide same arguments as in 'viewlet' ZCML directive: + @name = name of the viewlet; may be unique for a given viewlet manager + @view = the view class or interface for which viewlet is displayed + @for = the context class or interface for which viewlet is displayed + @permission = name of a permission required to display the viewlet + @layer = request interface required to display the viewlet + """ + + venusian = venusian # for testing injection + + def __init__(self, **settings): + if not settings.get('name'): + raise ConfigurationError("You must provide a name for a Viewlet") + 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): + cdict = { + '__name__': settings.get('name'), + '__module__': ob.__module__ + } + if 'permission' in settings: + settings['permission'] = settings.get('permission') + + bases = (ob,) + if not IContentProvider.implementedBy(ob): + bases = bases + (ViewContentProvider,) + new_class = type('' % settings.get('name'), bases, cdict) + + logger.debug("Registering content provider {0} ({1})".format(settings.get('name'), + str(new_class))) + config = context.config.with_package(info.module) + config.registry.registerAdapter(new_class, + (settings.get('context', Interface), + settings.get('layer', IRequest), + settings.get('view', IView)), + IContentProvider, settings.get('name')) + + info = self.venusian.attach(wrapped, callback, category='pyams_viewlet') + + 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 + + +@implementer(IViewlet) +class EmptyViewlet(object): + """Empty viewlet base class""" + + permission = None + + def __init__(self, context, request, view, manager): + self.context = context + self.request = request + self.__parent__ = view + self.manager = manager + + def update(self): + pass + + def render(self): + return '' + + +class Viewlet(EmptyViewlet): + """Viewlet adapter class used in meta directive as a mixin class.""" + + render = get_view_template() + + +class viewlet_config(object): + """Class decorator used to declare a viewlet + + You can provide same arguments as in 'viewlet' ZCML directive: + @name = name of the viewlet; may be unique for a given viewlet manager + @manager = manager class or interface holding the viewlet + @view = the view class or interface for which viewlet is displayed + @for = the context class or interface for which viewlet is displayed + @permission = name of a permission required to display the viewlet + @layer = request interface required to display the viewlet + @weight = weight of the viewlet when using a weight ordered viewlet manager + """ + + venusian = venusian # for testing injection + + def __init__(self, **settings): + if not settings.get('name'): + raise ConfigurationError("You must provide a name for a Viewlet") + 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): + cdict = { + '__name__': settings.get('name'), + '__module__': ob.__module__ + } + if 'permission' in settings: + cdict['permission'] = settings.get('permission') + if 'weight' in settings: + cdict['weight'] = settings.get('weight') + + bases = (ob,) + if not IViewlet.implementedBy(ob): + bases = bases + (Viewlet,) + new_class = type('' % settings.get('name'), bases, cdict) + + logger.debug("Registering viewlet {0} ({1})".format(settings.get('name'), + str(new_class))) + config = context.config.with_package(info.module) + config.registry.registerAdapter(new_class, + (settings.get('context', Interface), + settings.get('layer', IRequest), + settings.get('view', IView), + settings.get('manager', IViewletManager)), + IViewlet, settings.get('name')) + + info = self.venusian.attach(wrapped, callback, category='pyams_viewlet') + + 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