--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils.egg-info/PKG-INFO Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,194 @@
+Metadata-Version: 1.1
+Name: pyams-utils
+Version: 0.1.0
+Summary: Utility functions and classes for PyAMS
+Home-page: http://www.ztfy.org
+Author: Thierry Florac
+Author-email: tflorac@ulthar.net
+License: ZPL
+Description: ===================
+ pyams_utils package
+ ===================
+
+ .. contents::
+
+ What is pyams_utils ?
+ =====================
+
+ pyams_utils is a set of classes and functions which can be used to provide many small services and
+ handle common operations in the context of a Pyramid application.
+
+ Internal sub-packages include :
+ - date : convert dates to unicode ISO format, parse ISO datetime, convert date to datetime
+ - request : get current request, get request annotations, get and set request data via annotations
+ - timezone : convert datetime to a given timezone ; provides a server default timezone utility
+ - traversing : get object parents until a given interface is implemented
+ - unicode : convert any text to unicode for easy storage
+ - protocol : utility functions and modules for several nerwork protocols
+ - catalog : TextIndexNG index for Zope catalog and hurry.query "Text" query item
+ - text : simple text operations and text to HTML conversion
+ - html : HTML parser and HTML to text converter
+ - file : file upload data converter
+ - tal : text and HTML conversions for use from within TAL
+
+
+ How to use pyams_utils ?
+ ========================
+
+ A set of pyams_utils usages are given as doctests in pyams_utils/doctests/README.txt
+
+
+ Changelog
+ =========
+
+ 0.4.11
+ ------
+ - added "encode" and "decode" functions in "ztfy.utils.unicode" module (with updated doctests)
+
+ 0.4.10
+ ------
+ - added configuration directives to remove static dependencies with "ztfy.skin" and "zopyx.txng3.core"
+ - updated Buildout's bootstrap
+
+ 0.4.9
+ -----
+ - added "ztfy.utils.decorator" module with "@deprecated" decorator
+
+ 0.4.8
+ -----
+ - remove security proxy in ITransactionManager adapter
+
+ 0.4.7
+ -----
+ - added new "component" registry utility module
+ - added date and datetime display formats
+ - added "keep_chars" argument in translateString function
+
+ 0.4.6
+ -----
+ - added ISet interface with permissions on zc.set.Set class
+
+ 0.4.5
+ -----
+ - make current participation request optional in "indexObject()" function
+
+ 0.4.4
+ -----
+ - added "allow_none" and "headers" arguments in XML-RPC "getClient()" methods
+
+ 0.4.3
+ -----
+ - change test for date types in tztime/gmtime functions (because datetime
+ inherits from date!)
+
+ 0.4.2
+ -----
+ - small correction in getHumanSize() function
+ - added dates formatting functions
+ - added check between date and datetime types in timezone module
+
+ 0.4.1
+ -----
+ - use request locale formatter in getHumanSize function
+
+ 0.4.0
+ -----
+ - move custom schema fields widgets to ZTFY.skin package
+
+ 0.3.14
+ ------
+ - added legend on ZEO connection properties edit form
+ - force usage of "escapeSlashes" argument when checking new content name
+
+ 0.3.13
+ ------
+ - added ZEO connection interface, utility and tools
+ - added "ztfy.utils.container" utility module
+ - added a persistent utility to store ZEO connection settings
+ - added "TextLine list" schema field and widget
+ - added request and session cached properties
+ - added Python 2.7 compatibility code and timeout parameter to XML-RPC
+ protocol helper
+ - changed request "data:" TAL namespace to basic HTTP request so it can be used
+ in views called via JSON-RPC or XML-RPC
+
+ 0.3.12
+ ------
+ - updated package source layout
+
+ 0.3.11
+ ------
+ - added dotted decimal schema field, not handling locales :-/
+
+ 0.3.10
+ ------
+ - upgraded for ztfy.jqueryui 0.6.0
+ - added Color schema field and widget
+ - added StringLine schema field
+ - added "text:translate" TAL adapter
+ - moved ITransactionManager adapter from ztfy.scheduler package
+
+ 0.3.9
+ -----
+ - added HTTP client based on httplib2, handling authentication and proxies
+
+ 0.3.8
+ -----
+ - corrected encodings vocabulary
+
+ 0.3.7
+ -----
+ - added encodings vocabulary
+
+ 0.3.6
+ -----
+ - corrected code and translations in MissingPrincipal class
+ - added permissions on TextIndexNG index
+
+ 0.3.5
+ -----
+ - re-add IList and IDict interfaces forgotten from bad merge :-(
+
+ 0.3.4
+ -----
+ - better check for missing requests
+
+ 0.3.3
+ -----
+ - Added "fanstatic:" TALES expression
+
+ 0.3.2
+ -----
+ - Mark ztfy.utils.security functions and classes as deprecated
+
+ 0.3.1
+ -----
+ - Updated signature in ztfy.utils.catalog.index to match last hurry.query release
+
+ 0.3
+ ---
+ - Switched to ZTK-1.1.2 and Python 2.6
+ - Added "getAge" function in date module
+ - Added session module and TALES adapter to get/set session values
+ - Check None value in catalog.getObjectId(...) and catalog.getObject(...) methods
+
+ 0.2.1
+ -----
+ - Added 'site.locateAndRegister' facility function
+ - Update ServerTimezoneUtility parent classes
+
+ 0.2
+ ---
+ - Added 'data' namespace to access request data
+
+ 0.1
+ ---
+ - Initial release
+
+Keywords: Pyramid PyAMS utilities
+Platform: UNKNOWN
+Classifier: License :: OSI Approved :: Zope Public License
+Classifier: Development Status :: 4 - Beta
+Classifier: Programming Language :: Python
+Classifier: Framework :: Zope3
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils.egg-info/SOURCES.txt Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,67 @@
+MANIFEST.in
+setup.py
+docs/HISTORY.txt
+docs/README.txt
+src/pyams_utils/__init__.py
+src/pyams_utils/adapter.py
+src/pyams_utils/attr.py
+src/pyams_utils/configure.zcml
+src/pyams_utils/context.py
+src/pyams_utils/data.py
+src/pyams_utils/date.py
+src/pyams_utils/decorator.py
+src/pyams_utils/encoding.py
+src/pyams_utils/html.py
+src/pyams_utils/i18n.py
+src/pyams_utils/list.py
+src/pyams_utils/property.py
+src/pyams_utils/registry.py
+src/pyams_utils/request.py
+src/pyams_utils/schema.py
+src/pyams_utils/session.py
+src/pyams_utils/site.py
+src/pyams_utils/size.py
+src/pyams_utils/tales.py
+src/pyams_utils/text.py
+src/pyams_utils/traversing.py
+src/pyams_utils/unicode.py
+src/pyams_utils/url.py
+src/pyams_utils/wsgi.py
+src/pyams_utils/zodb.py
+src/pyams_utils.egg-info/PKG-INFO
+src/pyams_utils.egg-info/SOURCES.txt
+src/pyams_utils.egg-info/dependency_links.txt
+src/pyams_utils.egg-info/entry_points.txt
+src/pyams_utils.egg-info/namespace_packages.txt
+src/pyams_utils.egg-info/not-zip-safe
+src/pyams_utils.egg-info/requires.txt
+src/pyams_utils.egg-info/top_level.txt
+src/pyams_utils/browser/__init__.py
+src/pyams_utils/browser/configure.zcml
+src/pyams_utils/browser/decimal.py
+src/pyams_utils/doctests/README.txt
+src/pyams_utils/interfaces/__init__.py
+src/pyams_utils/interfaces/data.py
+src/pyams_utils/interfaces/site.py
+src/pyams_utils/interfaces/size.py
+src/pyams_utils/interfaces/tales.py
+src/pyams_utils/interfaces/text.py
+src/pyams_utils/interfaces/timezone.py
+src/pyams_utils/locales/pyams_utils.pot
+src/pyams_utils/locales/fr/LC_MESSAGES/pyams_utils.mo
+src/pyams_utils/locales/fr/LC_MESSAGES/pyams_utils.po
+src/pyams_utils/protocol/__init__.py
+src/pyams_utils/protocol/http.py
+src/pyams_utils/protocol/xmlrpc.py
+src/pyams_utils/scripts/__init__.py
+src/pyams_utils/scripts/zodb.py
+src/pyams_utils/tests/__init__.py
+src/pyams_utils/tests/test_utilsdocs.py
+src/pyams_utils/tests/test_utilsdocstrings.py
+src/pyams_utils/timezone/__init__.py
+src/pyams_utils/timezone/configure.zcml
+src/pyams_utils/timezone/utility.py
+src/pyams_utils/timezone/vocabulary.py
+src/pyams_utils/zmi/__init__.py
+src/pyams_utils/zmi/configure.zcml
+src/pyams_utils/zmi/timezone.py
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils.egg-info/dependency_links.txt Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,1 @@
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils.egg-info/entry_points.txt Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,5 @@
+
+ # -*- Entry points: -*-
+ [console_scripts]
+ pyams_upgrade = pyams_utils.scripts.zodb:upgrade_site
+
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils.egg-info/namespace_packages.txt Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,1 @@
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils.egg-info/not-zip-safe Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,1 @@
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils.egg-info/requires.txt Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,27 @@
+setuptools
+babel
+chameleon
+docutils
+httplib2
+persistent
+pyramid
+pyramid_zodbconn
+pysocks
+pytz
+transaction
+z3c.form
+z3c.pt
+z3c.ptcompat
+ZODB
+zope.annotation
+zope.component
+zope.container
+zope.datetime
+zope.interface
+zope.schema
+zope.security
+zope.site
+zope.traversing
+
+[test]
+pyramid_zcml
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils.egg-info/top_level.txt Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,1 @@
+pyams_utils
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/__init__.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,64 @@
+#
+# 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 interfaces
+
+# import packages
+from chameleon import PageTemplateFile
+from pyams_utils.context import ContextSelector
+from pyams_utils.request import get_annotations, get_debug
+from pyams_utils.site import site_factory
+from pyams_utils.tales import ExtensionExpr
+from pyams_utils.traversing import NamespaceTraverser
+from z3c.pt.pagetemplate import PageTemplateFile as Z3cPageTemplateFile
+
+from pyramid.i18n import TranslationStringFactory
+_ = TranslationStringFactory('pyams_utils')
+
+
+def includeme(config):
+ """pyams_utils features include"""
+
+ # add translations
+ config.add_translation_dirs('pyams_utils:locales')
+
+ # define root factory
+ config.set_root_factory(site_factory)
+
+ # add request annotations
+ config.add_request_method(get_annotations, 'annotations', reify=True)
+ config.add_request_method(get_debug, 'debug', reify=True)
+
+ # add traverser handling namespaces via "++ns++(options)" URLs
+ config.add_traverser(NamespaceTraverser)
+
+ # add custom subscriber predicate to filter events via supported interface(s)
+ config.add_subscriber_predicate('context_selector', ContextSelector)
+
+ # load registry components
+ try:
+ import pyams_zmi
+ except ImportError:
+ config.scan(ignore='pyams_utils.zmi')
+ else:
+ config.scan()
+
+ if hasattr(config, 'load_zcml'):
+ config.load_zcml('configure.zcml')
+
+ PageTemplateFile.expression_types['extension'] = ExtensionExpr
+ Z3cPageTemplateFile.expression_types['extension'] = ExtensionExpr
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/adapter.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,96 @@
+#
+# 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 venusian
+
+# import interfaces
+
+# import packages
+from zope.interface import implementedBy
+
+
+class ContextAdapter(object):
+ """Context adapter"""
+
+ def __init__(self, context):
+ self.context = context
+
+
+class ContextRequestAdapter(object):
+ """Context + request adapter"""
+
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+
+class ContextRequestViewAdapter(object):
+ """Context + request + view adapter"""
+
+ def __init__(self, context, request, view):
+ self.context = context
+ self.request = request
+ self.view = view
+
+
+class adapter_config(object):
+ """Function or class decorator to declare an adapter"""
+
+ venusian = venusian
+
+ def __init__(self, **settings):
+ if 'for_' in settings:
+ if settings.get('context') is None:
+ settings['context'] = settings.pop('for_')
+ self.__dict__.update(settings)
+
+ def __call__(self, wrapped):
+ settings = self.__dict__.copy()
+ depth = settings.pop('_depth', 0)
+
+ def callback(context, name, ob):
+ adapts = settings.get('context')
+ if adapts is None:
+ adapts = getattr(ob, '__component_adapts__', None)
+ if adapts is None:
+ raise TypeError("No for argument was provided for %r and "
+ "can't determine what the factory adapts." % ob)
+ if not isinstance(adapts, tuple):
+ adapts = (adapts,)
+
+ provides = settings.get('provides')
+ if provides is None:
+ intfs = list(implementedBy(ob))
+ if len(intfs) == 1:
+ provides = intfs[0]
+ if provides is None:
+ raise TypeError("Missing 'provided' argument")
+
+ config = context.config.with_package(info.module)
+ config.registry.registerAdapter(ob, adapts, provides, settings.get('name', ''))
+
+ info = self.venusian.attach(wrapped, callback, category='pyams_adapter',
+ depth=depth + 1)
+
+ 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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/attr.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,35 @@
+#
+# 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 interfaces
+from zope.traversing.interfaces import ITraversable
+
+# import packages
+from pyams_utils.adapter import ContextAdapter, adapter_config
+from pyramid.exceptions import NotFound
+from zope.interface import Interface
+
+
+@adapter_config(name='attr', context=Interface, provides=ITraversable)
+class AttributeTraverser(ContextAdapter):
+ """++attr++ namespace traverser"""
+
+ def traverse(self, name, furtherpath=None):
+ try:
+ return getattr(self.context, name)
+ except AttributeError:
+ raise NotFound
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/configure.zcml Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,29 @@
+<configure
+ xmlns="http://pylonshq.com/pyramid"
+ xmlns:zcml="http://namespaces.zope.org/zcml">
+
+ <include package="pyramid_zcml" />
+
+ <!-- Registration of external components -->
+ <include package="zope.component" file="meta.zcml" />
+ <include package="zope.browserpage" file="meta.zcml" />
+ <include package="zope.i18n" file="meta.zcml" />
+
+ <include package="z3c.form" file="meta.zcml" />
+
+ <include package="zope.component" />
+ <include package="zope.annotation" />
+ <include package="zope.dublincore" />
+ <include package="zope.site" />
+ <include package="zope.traversing" />
+
+ <include package="z3c.form" />
+ <include package="z3c.pt" />
+ <include package="z3c.ptcompat" />
+
+
+ <configure zcml:condition="installed pyams_zmi">
+ <include package=".zmi" />
+ </configure>
+
+</configure>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/context.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,49 @@
+#
+# 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 interfaces
+
+# import packages
+
+
+class ContextSelector(object):
+ """Interface based context selector
+
+ This selector can be used on any subscriber to define
+ interfaces that the context must support for the event
+ to be applied
+ """
+
+ def __init__(self, ifaces, config):
+ if not isinstance(ifaces, (list, tuple)):
+ ifaces = (ifaces,)
+ self.interfaces = ifaces
+
+ def text(self):
+ return 'context_selector = %s' % str(self.interfaces)
+
+ phash = text
+
+ def __call__(self, event):
+ for intf in self.interfaces:
+ try:
+ if intf.providedBy(event.object):
+ return True
+ except (AttributeError, TypeError):
+ if isinstance(event.object, intf):
+ return True
+ return False
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/data.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,64 @@
+#
+# 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 json
+
+# import interfaces
+from pyams_utils.interfaces.data import IObjectData, IObjectDataRenderer
+from pyams_utils.interfaces.tales import ITALESExtension
+from pyramid.interfaces import IRequest
+from zope.publisher.interfaces.browser import IBrowserRequest
+
+# import packages
+from pyams_utils.adapter import ContextAdapter, ContextRequestViewAdapter, adapter_config
+from zope.interface import Interface
+
+
+@adapter_config(context=IObjectData, provides=IObjectDataRenderer)
+class ObjectDataRenderer(ContextAdapter):
+ """Object data JSON renderer"""
+
+ def get_object_data(self):
+ data = IObjectData(self.context)
+ return json.dumps(data.object_data) if data is not None else None
+
+
+@adapter_config(name='object_data', context=(Interface, Interface, Interface), provides=ITALESExtension)
+class ObjectDataExtension(ContextRequestViewAdapter):
+ """extension:object_data TALES extension"""
+
+ def render(self, context=None):
+ if context is None:
+ context = self.context
+ renderer = IObjectDataRenderer(context, None)
+ if renderer is not None:
+ return renderer.get_object_data()
+
+
+@adapter_config(name='request_data', context=(Interface, IRequest, Interface), provides=ITALESExtension)
+class PyramidRequestDataExtension(ContextRequestViewAdapter):
+ """extension:request_data TALES extension for Pyramid request"""
+
+ def render(self, params=None):
+ return self.request.annotations.get(params)
+
+
+@adapter_config(name='request_data', context=(Interface, IBrowserRequest, Interface), provides=ITALESExtension)
+class BrowserRequestDataExtension(ContextRequestViewAdapter):
+ """extension:request_data TALES extension for Zope browser request"""
+
+ def render(self, params=None):
+ return self.request.annotations.get(params)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/date.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,194 @@
+#
+# 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
+from datetime import datetime
+
+# import interfaces
+
+# import packages
+from pyams_utils.request import check_request
+from pyams_utils.timezone import gmtime, tztime
+from zope.datetime import parseDatetimetz
+
+from pyams_utils import _
+
+
+def unidate(value):
+ """Get specified date converted to unicode ISO format
+
+ Dates are always assumed to be stored in GMT timezone
+
+ @param value: input date to convert to unicode
+ @type value: date or datetime
+ @return: input date converted to unicode
+ @rtype: unicode
+ """
+ if value is not None:
+ value = gmtime(value)
+ return value.isoformat('T')
+ return None
+
+
+def parse_date(value):
+ """Get date specified in unicode ISO format to Python datetime object
+
+ Dates are always assumed to be stored in GMT timezone
+
+ @param value: unicode date to be parsed
+ @type value: unicode
+ @return: the specified value, converted to datetime
+ @rtype: datetime
+ """
+ if value is not None:
+ return gmtime(parseDatetimetz(value))
+ return None
+
+
+def date_to_datetime(value):
+ """Get datetime value converted from a date or datetime object
+
+ @param value: a date or datetime value to convert
+ @type value: date or datetime
+ @return: input value converted to datetime
+ @rtype: datetime
+ """
+ if type(value) is datetime:
+ return value
+ return datetime(value.year, value.month, value.day)
+
+
+SH_DATE_FORMAT = _("%d/%m/%Y")
+SH_DATETIME_FORMAT = _("%d/%m/%Y - %H:%M")
+
+EXT_DATE_FORMAT = _("on %d/%m/%Y")
+EXT_DATETIME_FORMAT = _("on %d/%m/%Y at %H:%M")
+
+
+def format_date(value, format=EXT_DATE_FORMAT, request=None):
+ """Format given date with the given format"""
+ if request is None:
+ request = check_request()
+ localizer = request.localizer
+ return datetime.strftime(tztime(value), localizer.translate(format))
+
+
+def format_datetime(value, format=EXT_DATETIME_FORMAT, request=None):
+ """Format given datetime with the given format"""
+ return format_date(value, format, request)
+
+
+def get_age(value):
+ """Get age of a given datetime (including timezone) compared to current datetime (in UTC)
+
+ @param value: a datetime value, including timezone
+ @type value: datetime
+ @return: string representing value age
+ @rtype: gettext translated string
+ """
+ request = check_request()
+ translate = request.localizer.translate
+ now = gmtime(datetime.utcnow())
+ delta = now - value
+ if delta.days > 60:
+ return translate(_("%d months ago")) % int(round(delta.days * 1.0 / 30))
+ elif delta.days > 10:
+ return translate(_("%d weeks ago")) % int(round(delta.days * 1.0 / 7))
+ elif delta.days > 2:
+ return translate(_("%d days ago")) % delta.days
+ elif delta.days == 2:
+ return translate(_("the day before yesterday"))
+ elif delta.days == 1:
+ return translate(_("yesterday"))
+ else:
+ hours = int(round(delta.seconds * 1.0 / 3600))
+ if hours > 1:
+ return translate(_("%d hours ago")) % hours
+ elif delta.seconds > 300:
+ return translate(_("%d minutes ago")) % int(round(delta.seconds * 1.0 / 60))
+ else:
+ return translate(_("less than 5 minutes ago"))
+
+
+def get_duration(v1, v2=None, request=None):
+ """Get delta as string between two dates
+
+ >>> from datetime import datetime
+ >>> from pyams_utils.date import get_duration
+ >>> from pyramid.testing import DummyRequest
+ >>> request = DummyRequest()
+ >>> date1 = datetime(2015, 1, 1)
+ >>> date2 = datetime(2014, 3, 1)
+ >>> get_duration(date1, date2, request)
+ '10 months'
+
+ Dates order is not important:
+ >>> get_duration(date2, date1, request)
+ '10 months'
+ >>> date2 = datetime(2014, 11, 10)
+ >>> get_duration(date1, date2, request)
+ '7 weeks'
+ >>> date2 = datetime(2014, 12, 26)
+ >>> get_duration(date1, date2, request)
+ '6 days'
+
+ For durations lower than 2 days, duration also display hours:
+ >>> date1 = datetime(2015, 1, 1)
+ >>> date2 = datetime(2015, 1, 2, 15, 10, 0)
+ >>> get_duration(date1, date2, request)
+ '1 day and 15 hours'
+ >>> date2 = datetime(2015, 1, 2)
+ >>> get_duration(date1, date2, request)
+ '24 hours'
+ >>> date2 = datetime(2015, 1, 1, 13, 12)
+ >>> get_duration(date1, date2, request)
+ '13 hours'
+ >>> date2 = datetime(2015, 1, 1, 1, 15)
+ >>> get_duration(date1, date2, request)
+ '75 minutes'
+ >>> date2 = datetime(2015, 1, 1, 0, 0, 15)
+ >>> get_duration(date1, date2, request)
+ '15 seconds'
+ """
+ if v2 is None:
+ v2 = datetime.utcnow()
+ assert isinstance(v1, datetime) and isinstance(v2, datetime)
+ if request is None:
+ request = check_request()
+ translate = request.localizer.translate
+ v1, v2 = min(v1, v2), max(v1, v2)
+ delta = v2 - v1
+ if delta.days > 60:
+ return translate(_("%d months")) % int(round(delta.days * 1.0 / 30))
+ elif delta.days > 10:
+ return translate(_("%d weeks")) % int(round(delta.days * 1.0 / 7))
+ elif delta.days >= 2:
+ return translate(_("%d days")) % delta.days
+ else:
+ hours = int(round(delta.seconds * 1.0 / 3600))
+ if delta.days == 1:
+ if hours == 0:
+ return translate(_("24 hours"))
+ else:
+ return translate(_("%d day and %d hours")) % (delta.days, hours)
+ else:
+ if hours > 2:
+ return translate(_("%d hours")) % hours
+ else:
+ minutes = int(round(delta.seconds * 1.0 / 60))
+ if minutes > 2:
+ return translate(_("%d minutes")) % minutes
+ else:
+ return translate(_("%d seconds")) % delta.seconds
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/decorator.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,47 @@
+#
+# 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 functools
+import warnings
+
+# import interfaces
+
+# import packages
+
+
+def deprecated(*msg):
+ """This is a decorator which can be used to mark functions
+ as deprecated. It will result in a warning being emitted
+ when the function is used.
+ """
+
+ def decorator(func):
+
+ @functools.wraps(func)
+ def new_func(*args, **kwargs):
+ warnings.warn_explicit("Function %s is deprecated. %s" % (func.__name__, message),
+ category=DeprecationWarning,
+ filename=func.func_code.co_filename,
+ lineno=func.func_code.co_firstlineno + 1)
+ return func(*args, **kwargs)
+ return new_func
+
+ if len(msg) == 1 and callable(msg[0]):
+ message = u''
+ return decorator(msg[0])
+ else:
+ message = msg[0]
+ return decorator
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/doctests/README.txt Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,135 @@
+==================
+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 pyams_utils import unicode
+
+'translate_string' 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.translate_string(sample)
+ 'mon titre accentue'
+
+Results are lower-cased by default ; this can be avoided be setting the 'force_lower' argument
+to False:
+
+ >>> unicode.translate_string(sample, force_lower=False)
+ 'Mon titre accentue'
+ >>> unicode.translate_string(sample, force_lower=True, spaces='-')
+ 'mon-titre-accentue'
+
+If input string can contain 'slashes' (/) or 'backslashes' (\), they are normally removed ;
+by using the 'escape_slashes' 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.translate_string(sample)
+ 'autre chaine accentuee'
+ >>> unicode.translate_string(sample, escape_slashes=True)
+ 'accentuee'
+ >>> sample = 'C:\\Program Files\\My Application\\test.txt'
+ >>> unicode.translate_string(sample)
+ 'cprogram filesmy applicationtest.txt'
+ >>> unicode.translate_string(sample, escape_slashes=True)
+ '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.translate_string(sample, spaces=' ')
+ 'cprogram filesmy applicationtest.txt'
+ >>> unicode.translate_string(sample, spaces='-')
+ 'cprogram-filesmy-applicationtest.txt'
+
+Spaces replacement is made in the last step, so using it with "escape_slashes" parameter only affects
+the final result:
+
+ >>> unicode.translate_string(sample, escape_slashes=True, spaces='-')
+ 'test.txt'
+
+Unicode module also provides encoding and decoding functions:
+
+ >>> var = b'Cha\xeene accentu\xe9e'
+ >>> unicode.decode(var, 'latin1')
+ 'Chaîne accentuée'
+ >>> unicode.encode(unicode.decode(var, 'latin1'), 'latin1') == var
+ True
+
+ >>> utf = 'Chaîne accentuée'
+ >>> unicode.encode(utf, 'latin1')
+ b'Cha\xeene accentu\xe9e'
+ >>> unicode.decode(unicode.encode(utf, 'latin1'), 'latin1') == utf
+ True
+
+
+Dates functions
+---------------
+
+Dates functions are used to convert dates from/to string representation:
+
+ >>> from datetime import datetime
+ >>> from pyams_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
+ '2008-03-08T19:13:20+00:00'
+
+'parse_date' can be used to convert ASCII format into datetime:
+
+ >>> ddate = date.parse_date(udate)
+ >>> ddate
+ datetime.datetime(2008, 3, 8, 19, 13, 20, tzinfo=<StaticTzInfo 'GMT'>)
+
+'date_to_datetime' 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.date_to_datetime(ddate)
+ datetime.datetime(2008, 3, 8, 19, 13, 20, tzinfo=<StaticTzInfo 'GMT'>)
+ >>> date.date_to_datetime(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 pyams_utils import timezone
+ >>> timezone.tztime(ddate)
+ datetime.datetime(2008, 3, 8, 19, 13, 20, tzinfo=<StaticTzInfo 'GMT'>)
+
+'gmtime' function can be used to convert a datetime to GMT:
+
+ >>> timezone.gmtime(now)
+ datetime.datetime(2008, 3, 8, 19, 13, 20, tzinfo=<StaticTzInfo 'GMT'>)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/encoding.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,153 @@
+#
+# 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 interfaces
+from zope.schema.interfaces import IVocabularyFactory, IChoice
+
+# import packages
+from pyams_utils.request import check_request
+from zope.interface import provider, implementer
+from zope.schema import Choice
+from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary, getVocabularyRegistry
+
+from pyams_utils import _
+
+
+ENCODINGS = {
+ 'ascii': _('English (ASCII)'),
+ 'big5': _('Traditional Chinese (big5)'),
+ 'big5hkscs': _('Traditional Chinese (big5hkscs)'),
+ 'cp037': _('English (cp037)'),
+ 'cp424': _('Hebrew (cp424)'),
+ 'cp437': _('English (cp437)'),
+ 'cp500': _('Western Europe (cp500)'),
+ 'cp720': _('Arabic (cp720)'),
+ 'cp737': _('Greek (cp737)'),
+ 'cp775': _('Baltic languages (cp775)'),
+ 'cp850': _('Western Europe (cp850)'),
+ 'cp852': _('Central and Eastern Europe (cp852)'),
+ 'cp855': _('Bulgarian, Byelorussian, Macedonian, Russian, Serbian (cp855)'),
+ 'cp856': _('Hebrew (cp856)'),
+ 'cp857': _('Turkish (cp857)'),
+ 'cp858': _('Western Europe (cp858)'),
+ 'cp860': _('Portuguese (cp860)'),
+ 'cp861': _('Icelandic (cp861)'),
+ 'cp862': _('Hebrew (cp862)'),
+ 'cp863': _('Canadian (cp863)'),
+ 'cp864': _('Arabic (cp864)'),
+ 'cp865': _('Danish, Norwegian (cp865)'),
+ 'cp866': _('Russian (cp866)'),
+ 'cp869': _('Greek (cp869)'),
+ 'cp874': _('Thai (cp874)'),
+ 'cp875': _('Greek (cp875)'),
+ 'cp932': _('Japanese (cp932)'),
+ 'cp949': _('Korean (cp949)'),
+ 'cp950': _('Traditional Chinese (cp950)'),
+ 'cp1006': _('Urdu (cp1006)'),
+ 'cp1026': _('Turkish (cp1026)'),
+ 'cp1140': _('Western Europe (cp1140)'),
+ 'cp1250': _('Central and Eastern Europe (cp1250)'),
+ 'cp1251': _('Bulgarian, Byelorussian, Macedonian, Russian, Serbian (cp1251)'),
+ 'cp1252': _('Western Europe (cp1252)'),
+ 'cp1253': _('Greek (cp1253)'),
+ 'cp1254': _('Turkish (cp1254)'),
+ 'cp1255': _('Hebrew (cp1255)'),
+ 'cp1256': _('Arabic (cp1256)'),
+ 'cp1257': _('Baltic languages (cp1257)'),
+ 'cp1258': _('Vietnamese (cp1258)'),
+ 'euc_jp': _('Japanese (euc_jp)'),
+ 'euc_jis_2004': _('Japanese (euc_jis_2004)'),
+ 'euc_jisx0213': _('Japanese (euc_jisx0213)'),
+ 'euc_kr': _('Korean (euc_kr)'),
+ 'gb2312': _('Simplified Chinese (gb2312)'),
+ 'gbk': _('Unified Chinese (gbk)'),
+ 'gb18030': _('Unified Chinese (gb18030)'),
+ 'hz': _('Simplified Chinese (hz)'),
+ 'iso2022_jp': _('Japanese (iso2022_jp)'),
+ 'iso2022_jp_1': _('Japanese (iso2022_jp_1)'),
+ 'iso2022_jp_2': _('Japanese, Korean, Simplified Chinese, Western Europe, Greek (iso2022_jp_2)'),
+ 'iso2022_jp_2004': _('Japanese (iso2022_jp_2004)'),
+ 'iso2022_jp_3': _('Japanese (iso2022_jp_3)'),
+ 'iso2022_jp_ext': _('Japanese (iso2022_jp_ext)'),
+ 'iso2022_kr': _('Korean (iso2022_kr)'),
+ 'latin_1': _('West Europe (latin_1)'),
+ 'iso8859_2': _('Central and Eastern Europe (iso8859_2)'),
+ 'iso8859_3': _('Esperanto, Maltese (iso8859_3)'),
+ 'iso8859_4': _('Baltic languages (iso8859_4)'),
+ 'iso8859_5': _('Bulgarian, Byelorussian, Macedonian, Russian, Serbian (iso8859_5)'),
+ 'iso8859_6': _('Arabic (iso8859_6)'),
+ 'iso8859_7': _('Greek (iso8859_7)'),
+ 'iso8859_8': _('Hebrew (iso8859_8)'),
+ 'iso8859_9': _('Turkish (iso8859_9)'),
+ 'iso8859_10': _('Nordic languages (iso8859_10)'),
+ 'iso8859_13': _('Baltic languages (iso8859_13)'),
+ 'iso8859_14': _('Celtic languages (iso8859_14)'),
+ 'iso8859_15': _('Western Europe (iso8859_15)'),
+ 'iso8859_16': _('South-Eastern Europe (iso8859_16)'),
+ 'johab': _('Korean (johab)'),
+ 'koi8_r': _('Russian (koi8_r)'),
+ 'koi8_u': _('Ukrainian (koi8_u)'),
+ 'mac_cyrillic': _('Bulgarian, Byelorussian, Macedonian, Russian, Serbian (mac_cyrillic)'),
+ 'mac_greek': _('Greek (mac_greek)'),
+ 'mac_iceland': _('Icelandic (mac_iceland)'),
+ 'mac_latin2': _('Central and Eastern Europe (mac_latin2)'),
+ 'mac_roman': _('Western Europe (mac_roman)'),
+ 'mac_turkish': _('Turkish (mac_turkish)'),
+ 'ptcp154': _('Kazakh (ptcp154)'),
+ 'shift_jis': _('Japanese (shift_jis)'),
+ 'shift_jis_2004': _('Japanese (shift_jis_2004)'),
+ 'shift_jisx0213': _('Japanese (shift_jisx0213)'),
+ 'utf_32': _('all languages (utf_32)'),
+ 'utf_32_be': _('all languages (utf_32_be)'),
+ 'utf_32_le': _('all languages (utf_32_le)'),
+ 'utf_16': _('all languages (utf_16)'),
+ 'utf_16_be': _('all languages (BMP only - utf_16_be)'),
+ 'utf_16_le': _('all languages (BMP only - utf_16_le)'),
+ 'utf_7': _('all languages (utf_7)'),
+ 'utf_8': _('all languages (utf_8)'),
+ 'utf_8_sig': _('all languages (utf_8_sig)'),
+}
+
+
+@provider(IVocabularyFactory)
+class EncodingsVocabulary(SimpleVocabulary):
+
+ def __init__(self, terms, *interfaces):
+ request = check_request()
+ translate = request.localizer.translate
+ terms = [SimpleTerm(v, title=translate(t)) for v, t in ENCODINGS.items()]
+ terms.sort(key=lambda x: x.title)
+ super(EncodingsVocabulary, self).__init__(terms, *interfaces)
+
+getVocabularyRegistry().register('PyAMS encodings', EncodingsVocabulary)
+
+
+class IEncodingField(IChoice):
+ """Encoding field interface"""
+
+
+@implementer(IEncodingField)
+class EncodingField(Choice):
+ """Encoding field"""
+
+ def __init__(self, vocabulary='PyAMS encodings', **kw):
+ if 'values' in kw:
+ del kw['values']
+ if 'source' in kw:
+ del kw['source']
+ kw['vocabulary'] = vocabulary
+ super(EncodingField, self).__init__(**kw)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/html.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,124 @@
+#
+# 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
+from html.parser import HTMLParser
+
+# import interfaces
+
+# import packages
+
+
+class MyHTMLParser(HTMLParser):
+ """HTML parser"""
+ data = ''
+ entitydefs = {'amp': '&', 'lt': '<', 'gt': '>',
+ 'nbsp': ' ',
+ 'apos': "'", 'quot': '"',
+ 'Agrave': 'À', 'Aacute': 'A', 'Acirc': 'Â', 'Atilde': 'A', 'Auml': 'Ä', 'Aring': 'A',
+ 'AElig': 'AE',
+ 'Ccedil': 'Ç',
+ 'Egrave': 'É', 'Eacute': 'È', 'Ecirc': 'Ê', 'Euml': 'Ë',
+ 'Igrave': 'I', 'Iacute': 'I', 'Icirc': 'I', 'Iuml': 'I',
+ 'Ntilde': 'N',
+ 'Ograve': 'O', 'Oacute': 'O', 'Ocirc': 'Ô', 'Otilde': 'O', 'Ouml': 'Ö', 'Oslash': 'O',
+ 'Ugrave': 'Ù', 'Uacute': 'U', 'Ucirc': 'Û', 'Uuml': 'Ü',
+ 'Yacute': 'Y',
+ 'THORN': 'T',
+ 'agrave': 'à', 'aacute': 'a', 'acirc': 'â', 'atilde': 'a', 'auml': 'ä', 'aring': 'a', 'aelig': 'ae',
+ 'ccedil': 'ç',
+ 'egrave': 'è', 'eacute': 'é', 'ecirc': 'ê', 'euml': 'ë',
+ 'igrave': 'i', 'iacute': 'i', 'icirc': 'î', 'iuml': 'ï',
+ 'ntilde': 'n',
+ 'ograve': 'o', 'oacute': 'o', 'ocirc': 'ô', 'otilde': 'o', 'ouml': 'ö', 'oslash': 'o',
+ 'ugrave': 'ù', 'uacute': 'u', 'ucirc': 'û', 'uuml': 'ü',
+ 'yacute': 'y',
+ 'thorn': 't',
+ 'yuml': 'ÿ'}
+
+ charrefs = {34: '"', 38: '&', 39: "'",
+ 60: '<', 62: '>',
+ 192: 'À', 193: 'A', 194: 'Â', 195: 'A', 196: 'Ä', 197: 'A',
+ 198: 'AE',
+ 199: 'Ç',
+ 200: 'È', 201: 'É', 202: 'Ê', 203: 'Ë',
+ 204: 'I', 205: 'I', 206: 'Î', 207: 'Ï',
+ 208: 'D',
+ 209: 'N',
+ 210: 'O', 211: 'O', 212: 'Ô', 213: 'O', 214: 'Ö', 216: 'O',
+ 215: 'x',
+ 217: 'Ù', 218: 'U', 219: 'Û', 220: 'Ü',
+ 221: 'Y', 222: 'T',
+ 223: 'sz',
+ 224: 'à', 225: 'a', 226: 'â', 227: 'a', 228: 'ä', 229: 'a',
+ 230: 'ae',
+ 231: 'ç',
+ 232: 'è', 233: 'é', 234: 'ê', 235: 'ë',
+ 236: 'i', 237: 'i', 238: 'î', 239: 'ï',
+ 240: 'e',
+ 241: 'n',
+ 242: 'o', 243: 'o', 244: 'ô', 245: 'o', 246: 'ö', 248: 'o',
+ 249: 'ù', 250: 'u', 251: 'û', 252: 'ü',
+ 253: 'y', 255: 'ÿ'}
+
+ def handle_data(self, data):
+ try:
+ self.data += data
+ except:
+ self.data += data.decode('utf8')
+
+ def handle_entityref(self, name):
+ self.data += self.entitydefs.get(name, '')
+
+ def handle_charref(self, name):
+ try:
+ n = int(name)
+ except ValueError:
+ return
+ if not 0 <= n <= 255:
+ return
+ self.handle_data(self.charrefs.get(n))
+
+ def handle_starttag(self, tag, attrs):
+ if tag == 'td':
+ self.data += ' '
+ elif tag == 'br':
+ self.data += '\n'
+
+ def handle_endtag(self, tag):
+ if tag == 'p':
+ self.data += '\n'
+
+
+def html_to_text(value):
+ """Utility function to extract text content from HTML
+
+ >>> from pyams_utils.html import html_to_text
+ >>> html = '''<p>This is a HTML text part.</p>'''
+ >>> html_to_text(html)
+ 'This is a HTML text part.\\n'
+
+ HTML parser should handle entities correctly:
+ >>> html = '''<div><p>Header</p><p>This is an < ò > entity.<br /></p></div>'''
+ >>> html_to_text(html)
+ 'Header\\nThis is an < o > entity.\\n\\n'
+
+ """
+ if value is None:
+ return ''
+ parser = MyHTMLParser()
+ parser.feed(value)
+ parser.close()
+ return parser.data
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/i18n.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,76 @@
+#
+# 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 interfaces
+
+# import packages
+
+
+def normalize_lang(lang):
+ lang = lang.strip().lower()
+ lang = lang.replace('_', '-')
+ lang = lang.replace(' ', '')
+ return lang
+
+
+def get_browser_language(request):
+ """Custom locale negotiator
+
+ Copied from zope.publisher code
+ """
+ accept_langs = request.headers.get('Accept-Language', '').split(',')
+
+ # Normalize lang strings
+ accept_langs = [normalize_lang(l) for l in accept_langs]
+ # Then filter out empty ones
+ accept_langs = [l for l in accept_langs if l]
+
+ accepts = []
+ for index, lang in enumerate(accept_langs):
+ l = lang.split(';', 2)
+
+ # If not supplied, quality defaults to 1...
+ quality = 1.0
+
+ if len(l) == 2:
+ q = l[1]
+ if q.startswith('q='):
+ q = q.split('=', 2)[1]
+ try:
+ quality = float(q)
+ except ValueError:
+ # malformed quality value, skip it.
+ continue
+
+ if quality == 1.0:
+ # ... but we use 1.9 - 0.001 * position to
+ # keep the ordering between all items with
+ # 1.0 quality, which may include items with no quality
+ # defined, and items with quality defined as 1.
+ quality = 1.9 - (0.001 * index)
+
+ accepts.append((quality, l[0]))
+
+ # Filter langs with q=0, which means
+ # unwanted lang according to the spec
+ # See: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
+ accepts = [acc for acc in accepts if acc[0]]
+
+ accepts.sort()
+ accepts.reverse()
+
+ return [lang for qual, lang in accepts][0] if accepts else None
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/interfaces/__init__.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,30 @@
+#
+# 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 interfaces
+
+# import packages
+
+
+# ZODB application name settings key
+PYAMS_APPLICATION_SETTINGS_KEY = 'pyams.application_name'
+
+# ZODB default application name
+PYAMS_APPLICATION_DEFAULT_NAME = 'application'
+
+# Settings key to define site root factory
+PYAMS_APPLICATION_FACTORY_KEY = 'pyams.application_factory'
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/interfaces/data.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,36 @@
+#
+# 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 interfaces
+
+# import packages
+from zope.interface import Interface
+from zope.schema import Dict
+
+
+class IObjectData(Interface):
+ """Object data generic interface"""
+
+ object_data = Dict(title="Data associated with this object",
+ required=False)
+
+
+class IObjectDataRenderer(Interface):
+ """Object data rendering interface"""
+
+ def get_object_data(self):
+ """Get object data as JSON string"""
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/interfaces/site.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,58 @@
+#
+# 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 interfaces
+from zope.annotation.interfaces import IAttributeAnnotatable
+from zope.component.interfaces import IObjectEvent
+
+# import packages
+from zope.interface import Interface, Attribute
+
+
+class ISiteRoot(IAttributeAnnotatable):
+ """Marker interface for site root"""
+
+
+class INewLocalSiteCreatedEvent(IObjectEvent):
+ """Event interface when a new site root has been created"""
+
+
+class ISiteUpgradeEvent(IObjectEvent):
+ """Event interface when a site upgrade is requested"""
+
+
+SITE_GENERATIONS_KEY = 'pyams.generations'
+
+
+class ISiteGenerations(Interface):
+ """Site generations interface"""
+
+ generation = Attribute("Current schema generation")
+
+ def evolve(self, site, current=None):
+ """Evolve from current generation to last one"""
+
+
+class IConfigurationManager(IAttributeAnnotatable):
+ """Configuration manager marker interface"""
+
+
+class IConfigurationFactory(Interface):
+ """Configuration factory interface
+
+ This factory may be loaded through an adapter
+ """
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/interfaces/size.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,28 @@
+#
+# 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 interfaces
+
+# import packages
+from zope.interface import Interface
+from zope.schema import Int
+
+
+class ILength(Interface):
+ """Length interface"""
+
+ length = Int(title="Object length")
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/interfaces/tales.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,31 @@
+#
+# 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 interfaces
+
+# import packages
+from zope.interface import Interface
+
+
+class ITALESExtension(Interface):
+ """Custom TALES extension
+
+ These extensions will be registered throught adapters for
+ (context, request, view) or (context, request)
+ """
+
+ def render(self, context=None):
+ """Render extension"""
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/interfaces/text.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,37 @@
+#
+# 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 interfaces
+
+# import packages
+from zope.interface import Interface, Attribute
+
+from pyams_utils import _
+
+
+class IHTMLRenderer(Interface):
+ """Text renderer interface
+
+ HTML renderers are implemented as adapters for a source object (which can
+ be a string) and a request, so that you can easily implement custom renderers
+ for any object and/or for any request layer.
+ """
+
+ title = Attribute(_("Renderer name"))
+
+ def render(self, **kwargs):
+ """Render adapted text"""
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/interfaces/timezone.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,49 @@
+#
+# 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 interfaces
+from zope.schema.interfaces import IChoice
+
+# import packages
+from zope.interface import implementer, Interface
+from zope.schema import Choice
+
+from pyams_utils import _
+
+
+class ITimezone(IChoice):
+ """Marker interface for timezone field"""
+
+
+@implementer(ITimezone)
+class Timezone(Choice):
+ """Timezone choice field"""
+
+ def __init__(self, **kw):
+ if 'vocabulary' in kw:
+ kw.pop('vocabulary')
+ if 'default' not in kw:
+ kw['default'] = u'GMT'
+ super(Timezone, self).__init__(vocabulary='PyAMS timezones', **kw)
+
+
+class IServerTimezone(Interface):
+ """Server timezone interface"""
+
+ timezone = Timezone(title=_("Server timezone"),
+ description=_("Default server timezone"),
+ required=True)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/list.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,41 @@
+#
+# 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 interfaces
+
+# import packages
+
+
+def unique(seq, idfun=None):
+ """Extract unique values from list, preserving order
+
+ >>> from pyams_utils.list import unique
+ >>> mylist = [1, 2, 3, 2, 1]
+ >>> unique(mylist)
+ [1, 2, 3]
+ """
+ if idfun is None:
+ def idfun(x): return x
+ seen = {}
+ result = []
+ for item in seq:
+ marker = idfun(item)
+ if marker in seen:
+ continue
+ seen[marker] = 1
+ result.append(item)
+ return result
Binary file src/pyams_utils/locales/fr/LC_MESSAGES/pyams_utils.mo has changed
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/locales/fr/LC_MESSAGES/pyams_utils.po Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,521 @@
+# French translations for PACKAGE package
+# This file is distributed under the same license as the PACKAGE package.
+# Thierry Florac <tflorac@ulthar.net>, 2015.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE 1.0\n"
+"POT-Creation-Date: 2015-01-24 16:53+0100\n"
+"PO-Revision-Date: 2015-01-18 01:01+0100\n"
+"Last-Translator: Thierry Florac <tflorac@ulthar.net>\n"
+"Language-Team: French\n"
+"Language: fr\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+"Generated-By: Lingua 3.7\n"
+
+#: src/pyams_utils/encoding.py:33
+msgid "English (ASCII)"
+msgstr "Anglais (ASCII)"
+
+#: src/pyams_utils/encoding.py:34
+msgid "Traditional Chinese (big5)"
+msgstr "Chinois traditionnel (big5)"
+
+#: src/pyams_utils/encoding.py:35
+msgid "Traditional Chinese (big5hkscs)"
+msgstr "Chinois traditionnel (big5hkscs)"
+
+#: src/pyams_utils/encoding.py:36
+msgid "English (cp037)"
+msgstr "Anglais (cp037)"
+
+#: src/pyams_utils/encoding.py:37
+msgid "Hebrew (cp424)"
+msgstr "Hébreu (cp424)"
+
+#: src/pyams_utils/encoding.py:38
+msgid "English (cp437)"
+msgstr "Anglais (cp437)"
+
+#: src/pyams_utils/encoding.py:39
+msgid "Western Europe (cp500)"
+msgstr "Europe de l'ouest (cp500)"
+
+#: src/pyams_utils/encoding.py:40
+msgid "Arabic (cp720)"
+msgstr "Arabe (cp720)"
+
+#: src/pyams_utils/encoding.py:41
+msgid "Greek (cp737)"
+msgstr "Grec (cp737)"
+
+#: src/pyams_utils/encoding.py:42
+msgid "Baltic languages (cp775)"
+msgstr "Langues baltes (cp775)"
+
+#: src/pyams_utils/encoding.py:43
+msgid "Western Europe (cp850)"
+msgstr "Europe de l'ouest (cp850)"
+
+#: src/pyams_utils/encoding.py:44
+msgid "Central and Eastern Europe (cp852)"
+msgstr "Europe centrale et de l'est (cp852)"
+
+#: src/pyams_utils/encoding.py:45
+msgid "Bulgarian, Byelorussian, Macedonian, Russian, Serbian (cp855)"
+msgstr "Bulgare, Biélorusse, Macédonien, Russe, Serbe (cp855)"
+
+#: src/pyams_utils/encoding.py:46
+msgid "Hebrew (cp856)"
+msgstr "Hébreu (cp856)"
+
+#: src/pyams_utils/encoding.py:47
+msgid "Turkish (cp857)"
+msgstr "Turc (cp857)"
+
+#: src/pyams_utils/encoding.py:48
+msgid "Western Europe (cp858)"
+msgstr "Europe de l'ouest (cp858)"
+
+#: src/pyams_utils/encoding.py:49
+msgid "Portuguese (cp860)"
+msgstr "Portugais (cp860)"
+
+#: src/pyams_utils/encoding.py:50
+msgid "Icelandic (cp861)"
+msgstr "Islandais (cp861)"
+
+#: src/pyams_utils/encoding.py:51
+msgid "Hebrew (cp862)"
+msgstr "Hébreu (cp862)"
+
+#: src/pyams_utils/encoding.py:52
+msgid "Canadian (cp863)"
+msgstr "Canadien (cp863)"
+
+#: src/pyams_utils/encoding.py:53
+msgid "Arabic (cp864)"
+msgstr "Arabe (cp864)"
+
+#: src/pyams_utils/encoding.py:54
+msgid "Danish, Norwegian (cp865)"
+msgstr "Danois, Norvégien (cp865)"
+
+#: src/pyams_utils/encoding.py:55
+msgid "Russian (cp866)"
+msgstr "Russe (cp866)"
+
+#: src/pyams_utils/encoding.py:56
+msgid "Greek (cp869)"
+msgstr "Grec (cp869)"
+
+#: src/pyams_utils/encoding.py:57
+msgid "Thai (cp874)"
+msgstr "Thaï (cp874)"
+
+#: src/pyams_utils/encoding.py:58
+msgid "Greek (cp875)"
+msgstr "Grec (cp875)"
+
+#: src/pyams_utils/encoding.py:59
+msgid "Japanese (cp932)"
+msgstr "Japonais (cp932)"
+
+#: src/pyams_utils/encoding.py:60
+msgid "Korean (cp949)"
+msgstr "Coréen (cp949)"
+
+#: src/pyams_utils/encoding.py:61
+msgid "Traditional Chinese (cp950)"
+msgstr "Chinois traditionnel (cp950)"
+
+#: src/pyams_utils/encoding.py:62
+msgid "Urdu (cp1006)"
+msgstr "Ourdou (cp1006)"
+
+#: src/pyams_utils/encoding.py:63
+msgid "Turkish (cp1026)"
+msgstr "Turc (cp1026)"
+
+#: src/pyams_utils/encoding.py:64
+msgid "Western Europe (cp1140)"
+msgstr "Europe de l'ouest (cp1140)"
+
+#: src/pyams_utils/encoding.py:65
+msgid "Central and Eastern Europe (cp1250)"
+msgstr "Europe centrale et de l'est (cp1250)"
+
+#: src/pyams_utils/encoding.py:66
+msgid "Bulgarian, Byelorussian, Macedonian, Russian, Serbian (cp1251)"
+msgstr "Bulgare, Biélorusse, Macédonien, Russe, Serbe (cp1251)"
+
+#: src/pyams_utils/encoding.py:67
+msgid "Western Europe (cp1252)"
+msgstr "Europe de l'ouest (cp1252)"
+
+#: src/pyams_utils/encoding.py:68
+msgid "Greek (cp1253)"
+msgstr "Grec (cp1253)"
+
+#: src/pyams_utils/encoding.py:69
+msgid "Turkish (cp1254)"
+msgstr "Turc (cp1254)"
+
+#: src/pyams_utils/encoding.py:70
+msgid "Hebrew (cp1255)"
+msgstr "Hébreu (cp1255)"
+
+#: src/pyams_utils/encoding.py:71
+msgid "Arabic (cp1256)"
+msgstr "Arabe (cp1256)"
+
+#: src/pyams_utils/encoding.py:72
+msgid "Baltic languages (cp1257)"
+msgstr "Langues baltes (cp1257)"
+
+#: src/pyams_utils/encoding.py:73
+msgid "Vietnamese (cp1258)"
+msgstr "Viernamien (cp1258)"
+
+#: src/pyams_utils/encoding.py:74
+msgid "Japanese (euc_jp)"
+msgstr "Japonais (euc-jp)"
+
+#: src/pyams_utils/encoding.py:75
+msgid "Japanese (euc_jis_2004)"
+msgstr "Japonais (euc-jis-2004)"
+
+#: src/pyams_utils/encoding.py:76
+msgid "Japanese (euc_jisx0213)"
+msgstr "Japonais (euc-jisx0213)"
+
+#: src/pyams_utils/encoding.py:77
+msgid "Korean (euc_kr)"
+msgstr "Coréen (euc-kr)"
+
+#: src/pyams_utils/encoding.py:78
+msgid "Simplified Chinese (gb2312)"
+msgstr "Chinois simplifié (gb2312)"
+
+#: src/pyams_utils/encoding.py:79
+msgid "Unified Chinese (gbk)"
+msgstr "Chinois unifié (gbk)"
+
+#: src/pyams_utils/encoding.py:80
+msgid "Unified Chinese (gb18030)"
+msgstr "Chinois unifié (gb18030)"
+
+#: src/pyams_utils/encoding.py:81
+msgid "Simplified Chinese (hz)"
+msgstr "Chinois simplifié (hz)"
+
+#: src/pyams_utils/encoding.py:82
+msgid "Japanese (iso2022_jp)"
+msgstr "Japonais (iso2022-jp)"
+
+#: src/pyams_utils/encoding.py:83
+msgid "Japanese (iso2022_jp_1)"
+msgstr "Japonais (iso2022-jp-1)"
+
+#: src/pyams_utils/encoding.py:84
+msgid ""
+"Japanese, Korean, Simplified Chinese, Western Europe, Greek (iso2022_jp_2)"
+msgstr ""
+"Japonais, Coréen, Chinois simplifié, Europe de l'ouest, Grec (iso2022-jp-2)"
+
+#: src/pyams_utils/encoding.py:85
+msgid "Japanese (iso2022_jp_2004)"
+msgstr "Japonais (iso2022-jp-2004)"
+
+#: src/pyams_utils/encoding.py:86
+msgid "Japanese (iso2022_jp_3)"
+msgstr "Japonais (iso2022-jp-3)"
+
+#: src/pyams_utils/encoding.py:87
+msgid "Japanese (iso2022_jp_ext)"
+msgstr "Japonais (iso2022-jp-ext)"
+
+#: src/pyams_utils/encoding.py:88
+msgid "Korean (iso2022_kr)"
+msgstr "Coréen (iso2022-kr)"
+
+#: src/pyams_utils/encoding.py:89
+msgid "West Europe (latin_1)"
+msgstr "Europe de l'ouest (latin-1)"
+
+#: src/pyams_utils/encoding.py:90
+msgid "Central and Eastern Europe (iso8859_2)"
+msgstr "Europe centrale et de l'est (iso8859-2)"
+
+#: src/pyams_utils/encoding.py:91
+msgid "Esperanto, Maltese (iso8859_3)"
+msgstr "Espéranto, Maltais (iso8859-3)"
+
+#: src/pyams_utils/encoding.py:92
+msgid "Baltic languages (iso8859_4)"
+msgstr "Langues baltes (iso8859-4)"
+
+#: src/pyams_utils/encoding.py:93
+msgid "Bulgarian, Byelorussian, Macedonian, Russian, Serbian (iso8859_5)"
+msgstr "Bulgare, Biélorusse, Macédonien, Russe, Serbe (iso8859-5)"
+
+#: src/pyams_utils/encoding.py:94
+msgid "Arabic (iso8859_6)"
+msgstr "Arabe (iso8859-6)"
+
+#: src/pyams_utils/encoding.py:95
+msgid "Greek (iso8859_7)"
+msgstr "Grec (iso8869-7)"
+
+#: src/pyams_utils/encoding.py:96
+msgid "Hebrew (iso8859_8)"
+msgstr "Hébreu (iso8859-8)"
+
+#: src/pyams_utils/encoding.py:97
+msgid "Turkish (iso8859_9)"
+msgstr "Turc (iso8859-9)"
+
+#: src/pyams_utils/encoding.py:98
+msgid "Nordic languages (iso8859_10)"
+msgstr "Langues nordiques (iso8859-10)"
+
+#: src/pyams_utils/encoding.py:99
+msgid "Baltic languages (iso8859_13)"
+msgstr "Langues baltes (iso8859-13)"
+
+#: src/pyams_utils/encoding.py:100
+msgid "Celtic languages (iso8859_14)"
+msgstr "Langues celtes (iso8859-14)"
+
+#: src/pyams_utils/encoding.py:101
+msgid "Western Europe (iso8859_15)"
+msgstr "Europe de l'ouest (iso8859-15)"
+
+#: src/pyams_utils/encoding.py:102
+msgid "South-Eastern Europe (iso8859_16)"
+msgstr "Europe du sud-est (iso8859-16)"
+
+#: src/pyams_utils/encoding.py:103
+msgid "Korean (johab)"
+msgstr "Coréen (johab)"
+
+#: src/pyams_utils/encoding.py:104
+msgid "Russian (koi8_r)"
+msgstr "Russe (kio8-r)"
+
+#: src/pyams_utils/encoding.py:105
+msgid "Ukrainian (koi8_u)"
+msgstr "Ukrainien (kio8-u)"
+
+#: src/pyams_utils/encoding.py:106
+msgid "Bulgarian, Byelorussian, Macedonian, Russian, Serbian (mac_cyrillic)"
+msgstr "Bulgare, Biolorusse, Macédonien, Russe, Serve (mac-cyrillic)"
+
+#: src/pyams_utils/encoding.py:107
+msgid "Greek (mac_greek)"
+msgstr "Grec (mac-greek)"
+
+#: src/pyams_utils/encoding.py:108
+msgid "Icelandic (mac_iceland)"
+msgstr "Islandais (mac-iceland)"
+
+#: src/pyams_utils/encoding.py:109
+msgid "Central and Eastern Europe (mac_latin2)"
+msgstr "Europe centrale et de l'ouest (mac-latin2)"
+
+#: src/pyams_utils/encoding.py:110
+msgid "Western Europe (mac_roman)"
+msgstr "Europe de l'Ouest (mac-roman)"
+
+#: src/pyams_utils/encoding.py:111
+msgid "Turkish (mac_turkish)"
+msgstr "Turc (mac-turkish)"
+
+#: src/pyams_utils/encoding.py:112
+msgid "Kazakh (ptcp154)"
+msgstr "Kazak (ptcp154)"
+
+#: src/pyams_utils/encoding.py:113
+msgid "Japanese (shift_jis)"
+msgstr "Japonais (shift_jis)"
+
+#: src/pyams_utils/encoding.py:114
+msgid "Japanese (shift_jis_2004)"
+msgstr "Japonais (shift-jis-2004)"
+
+#: src/pyams_utils/encoding.py:115
+msgid "Japanese (shift_jisx0213)"
+msgstr "Japonais (shift-jisx0213)"
+
+#: src/pyams_utils/encoding.py:116
+msgid "all languages (utf_32)"
+msgstr "toutes les langues (utf-32)"
+
+#: src/pyams_utils/encoding.py:117
+msgid "all languages (utf_32_be)"
+msgstr "toutes les langues (utf-32-be)"
+
+#: src/pyams_utils/encoding.py:118
+msgid "all languages (utf_32_le)"
+msgstr "toutes les langues (utf-32-le)"
+
+#: src/pyams_utils/encoding.py:119
+msgid "all languages (utf_16)"
+msgstr "toutes les langues (utf-16)"
+
+#: src/pyams_utils/encoding.py:120
+msgid "all languages (BMP only - utf_16_be)"
+msgstr "toutes les langues (BMP seulement - utf-16-be"
+
+#: src/pyams_utils/encoding.py:121
+msgid "all languages (BMP only - utf_16_le)"
+msgstr "toutes les langues (BMP seulement - utf-16-le)"
+
+#: src/pyams_utils/encoding.py:122
+msgid "all languages (utf_7)"
+msgstr "toutes les langues (utf-7)"
+
+#: src/pyams_utils/encoding.py:123
+msgid "all languages (utf_8)"
+msgstr "toutes les langues (utf-8)"
+
+#: src/pyams_utils/encoding.py:124
+msgid "all languages (utf_8_sig)"
+msgstr "toutes les langues (utf-8-sig)"
+
+#: src/pyams_utils/date.py:75
+msgid "%d/%m/%Y"
+msgstr "%d/%m/%Y"
+
+#: src/pyams_utils/date.py:76
+msgid "%d/%m/%Y - %H:%M"
+msgstr "%d/%m/%Y - %H:%M"
+
+#: src/pyams_utils/date.py:78
+msgid "on %d/%m/%Y"
+msgstr "le %d/%m/%Y"
+
+#: src/pyams_utils/date.py:79
+msgid "on %d/%m/%Y at %H:%M"
+msgstr "le %d/%m/%Y à %H:%M"
+
+#: src/pyams_utils/date.py:106
+#, c-format
+msgid "%d months ago"
+msgstr "Il y a %d mois"
+
+#: src/pyams_utils/date.py:135
+#, c-format
+msgid "%d months"
+msgstr "%d mois"
+
+#: src/pyams_utils/date.py:108
+#, c-format
+msgid "%d weeks ago"
+msgstr "Il y a %d semaines"
+
+#: src/pyams_utils/date.py:137
+#, c-format
+msgid "%d weeks"
+msgstr "%d semaines"
+
+#: src/pyams_utils/date.py:110
+#, c-format
+msgid "%d days ago"
+msgstr "Il y a %d jours"
+
+#: src/pyams_utils/date.py:112
+msgid "the day before yesterday"
+msgstr "avant-hier"
+
+#: src/pyams_utils/date.py:139
+#, c-format
+msgid "%d days"
+msgstr "%d jours"
+
+#: src/pyams_utils/date.py:114
+msgid "yesterday"
+msgstr "hier"
+
+#: src/pyams_utils/date.py:143
+#, c-format
+msgid "%d day and %d hours"
+msgstr "%d jours et %d heures"
+
+#: src/pyams_utils/date.py:146
+#, c-format
+msgid "%d hours"
+msgstr "%d heures"
+
+#: src/pyams_utils/date.py:118
+#, c-format
+msgid "%d hours ago"
+msgstr "Il y a %d heures"
+
+#: src/pyams_utils/date.py:122
+msgid "less than 5 minutes ago"
+msgstr "Il y a moins de 5 minutes"
+
+#: src/pyams_utils/date.py:150
+#, c-format
+msgid "%d minutes"
+msgstr "%d minutes"
+
+#: src/pyams_utils/date.py:152
+#, c-format
+msgid "%d seconds"
+msgstr "%d secondes"
+
+#: src/pyams_utils/date.py:120
+#, c-format
+msgid "%d minutes ago"
+msgstr "Il y a %d minutes"
+
+#: src/pyams_utils/size.py:61
+msgid "0.0## Gb"
+msgstr "0.0## Go"
+
+#: src/pyams_utils/size.py:53
+msgid "0 bytes"
+msgstr "0 octets"
+
+#: src/pyams_utils/size.py:56
+msgid "0.# Kb"
+msgstr "0.# Ko"
+
+#: src/pyams_utils/size.py:59
+msgid "0.0# Mb"
+msgstr "0.0# Mo"
+
+#: src/pyams_utils/schema.py:48
+msgid "Color length must be 3 or 6 characters"
+msgstr "La longueur d'une couleur doit être de 3 ou 6 caractères"
+
+#: src/pyams_utils/schema.py:51
+msgid ""
+"Color value must contain only valid hexadecimal color codes (numbers or "
+"letters between 'A' end 'F')"
+msgstr ""
+"Une couleur ne doit contenir que des valeurs hexadécimales correctes (nombres "
+"ou lettres de 'A' à 'F')"
+
+#: src/pyams_utils/timezone/interfaces.py:32
+msgid "Server timezone"
+msgstr "Fuseau horaire du serveur"
+
+#: src/pyams_utils/timezone/interfaces.py:33
+msgid "Default server timezone"
+msgstr "Fuseau horaire par défaut"
+
+#: src/pyams_utils/interfaces/text.py:36
+msgid "Renderer name"
+msgstr "Nom de l'outil de rendu"
+
+#: src/pyams_utils/browser/decimal.py:35
+msgid "The entered value is not a valid decimal literal."
+msgstr "La valeur saisie n'est pas une valeur décimale correcte."
+
+#~ msgid "Test"
+#~ msgstr "test"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/locales/pyams_utils.pot Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,516 @@
+#
+# SOME DESCRIPTIVE TITLE
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, 2015.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE 1.0\n"
+"POT-Creation-Date: 2015-01-24 16:53+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Lingua 3.8\n"
+
+#: ./src/pyams_utils/encoding.py:33
+msgid "English (ASCII)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:34
+msgid "Traditional Chinese (big5)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:35
+msgid "Traditional Chinese (big5hkscs)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:36
+msgid "English (cp037)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:37
+msgid "Hebrew (cp424)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:38
+msgid "English (cp437)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:39
+msgid "Western Europe (cp500)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:40
+msgid "Arabic (cp720)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:41
+msgid "Greek (cp737)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:42
+msgid "Baltic languages (cp775)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:43
+msgid "Western Europe (cp850)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:44
+msgid "Central and Eastern Europe (cp852)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:45
+msgid "Bulgarian, Byelorussian, Macedonian, Russian, Serbian (cp855)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:46
+msgid "Hebrew (cp856)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:47
+msgid "Turkish (cp857)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:48
+msgid "Western Europe (cp858)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:49
+msgid "Portuguese (cp860)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:50
+msgid "Icelandic (cp861)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:51
+msgid "Hebrew (cp862)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:52
+msgid "Canadian (cp863)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:53
+msgid "Arabic (cp864)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:54
+msgid "Danish, Norwegian (cp865)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:55
+msgid "Russian (cp866)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:56
+msgid "Greek (cp869)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:57
+msgid "Thai (cp874)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:58
+msgid "Greek (cp875)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:59
+msgid "Japanese (cp932)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:60
+msgid "Korean (cp949)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:61
+msgid "Traditional Chinese (cp950)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:62
+msgid "Urdu (cp1006)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:63
+msgid "Turkish (cp1026)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:64
+msgid "Western Europe (cp1140)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:65
+msgid "Central and Eastern Europe (cp1250)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:66
+msgid "Bulgarian, Byelorussian, Macedonian, Russian, Serbian (cp1251)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:67
+msgid "Western Europe (cp1252)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:68
+msgid "Greek (cp1253)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:69
+msgid "Turkish (cp1254)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:70
+msgid "Hebrew (cp1255)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:71
+msgid "Arabic (cp1256)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:72
+msgid "Baltic languages (cp1257)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:73
+msgid "Vietnamese (cp1258)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:74
+msgid "Japanese (euc_jp)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:75
+msgid "Japanese (euc_jis_2004)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:76
+msgid "Japanese (euc_jisx0213)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:77
+msgid "Korean (euc_kr)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:78
+msgid "Simplified Chinese (gb2312)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:79
+msgid "Unified Chinese (gbk)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:80
+msgid "Unified Chinese (gb18030)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:81
+msgid "Simplified Chinese (hz)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:82
+msgid "Japanese (iso2022_jp)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:83
+msgid "Japanese (iso2022_jp_1)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:84
+msgid ""
+"Japanese, Korean, Simplified Chinese, Western Europe, Greek (iso2022_jp_2)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:85
+msgid "Japanese (iso2022_jp_2004)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:86
+msgid "Japanese (iso2022_jp_3)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:87
+msgid "Japanese (iso2022_jp_ext)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:88
+msgid "Korean (iso2022_kr)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:89
+msgid "West Europe (latin_1)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:90
+msgid "Central and Eastern Europe (iso8859_2)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:91
+msgid "Esperanto, Maltese (iso8859_3)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:92
+msgid "Baltic languages (iso8859_4)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:93
+msgid "Bulgarian, Byelorussian, Macedonian, Russian, Serbian (iso8859_5)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:94
+msgid "Arabic (iso8859_6)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:95
+msgid "Greek (iso8859_7)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:96
+msgid "Hebrew (iso8859_8)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:97
+msgid "Turkish (iso8859_9)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:98
+msgid "Nordic languages (iso8859_10)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:99
+msgid "Baltic languages (iso8859_13)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:100
+msgid "Celtic languages (iso8859_14)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:101
+msgid "Western Europe (iso8859_15)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:102
+msgid "South-Eastern Europe (iso8859_16)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:103
+msgid "Korean (johab)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:104
+msgid "Russian (koi8_r)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:105
+msgid "Ukrainian (koi8_u)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:106
+msgid "Bulgarian, Byelorussian, Macedonian, Russian, Serbian (mac_cyrillic)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:107
+msgid "Greek (mac_greek)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:108
+msgid "Icelandic (mac_iceland)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:109
+msgid "Central and Eastern Europe (mac_latin2)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:110
+msgid "Western Europe (mac_roman)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:111
+msgid "Turkish (mac_turkish)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:112
+msgid "Kazakh (ptcp154)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:113
+msgid "Japanese (shift_jis)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:114
+msgid "Japanese (shift_jis_2004)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:115
+msgid "Japanese (shift_jisx0213)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:116
+msgid "all languages (utf_32)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:117
+msgid "all languages (utf_32_be)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:118
+msgid "all languages (utf_32_le)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:119
+msgid "all languages (utf_16)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:120
+msgid "all languages (BMP only - utf_16_be)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:121
+msgid "all languages (BMP only - utf_16_le)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:122
+msgid "all languages (utf_7)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:123
+msgid "all languages (utf_8)"
+msgstr ""
+
+#: ./src/pyams_utils/encoding.py:124
+msgid "all languages (utf_8_sig)"
+msgstr ""
+
+#: ./src/pyams_utils/date.py:75
+msgid "%d/%m/%Y"
+msgstr ""
+
+#: ./src/pyams_utils/date.py:76
+msgid "%d/%m/%Y - %H:%M"
+msgstr ""
+
+#: ./src/pyams_utils/date.py:78
+msgid "on %d/%m/%Y"
+msgstr ""
+
+#: ./src/pyams_utils/date.py:79
+msgid "on %d/%m/%Y at %H:%M"
+msgstr ""
+
+#: ./src/pyams_utils/date.py:106
+#, c-format
+msgid "%d months ago"
+msgstr ""
+
+#: ./src/pyams_utils/date.py:135
+#, c-format
+msgid "%d months"
+msgstr ""
+
+#: ./src/pyams_utils/date.py:108
+#, c-format
+msgid "%d weeks ago"
+msgstr ""
+
+#: ./src/pyams_utils/date.py:137
+#, c-format
+msgid "%d weeks"
+msgstr ""
+
+#: ./src/pyams_utils/date.py:110
+#, c-format
+msgid "%d days ago"
+msgstr ""
+
+#: ./src/pyams_utils/date.py:112
+msgid "the day before yesterday"
+msgstr ""
+
+#: ./src/pyams_utils/date.py:139
+#, c-format
+msgid "%d days"
+msgstr ""
+
+#: ./src/pyams_utils/date.py:114
+msgid "yesterday"
+msgstr ""
+
+#: ./src/pyams_utils/date.py:143
+#, c-format
+msgid "%d day and %d hours"
+msgstr ""
+
+#: ./src/pyams_utils/date.py:146
+#, c-format
+msgid "%d hours"
+msgstr ""
+
+#: ./src/pyams_utils/date.py:118
+#, c-format
+msgid "%d hours ago"
+msgstr ""
+
+#: ./src/pyams_utils/date.py:122
+msgid "less than 5 minutes ago"
+msgstr ""
+
+#: ./src/pyams_utils/date.py:150
+#, c-format
+msgid "%d minutes"
+msgstr ""
+
+#: ./src/pyams_utils/date.py:152
+#, c-format
+msgid "%d seconds"
+msgstr ""
+
+#: ./src/pyams_utils/date.py:120
+#, c-format
+msgid "%d minutes ago"
+msgstr ""
+
+#: ./src/pyams_utils/size.py:61
+msgid "0.0## Gb"
+msgstr ""
+
+#: ./src/pyams_utils/size.py:53
+msgid "0 bytes"
+msgstr ""
+
+#: ./src/pyams_utils/size.py:56
+msgid "0.# Kb"
+msgstr ""
+
+#: ./src/pyams_utils/size.py:59
+msgid "0.0# Mb"
+msgstr ""
+
+#: ./src/pyams_utils/schema.py:48
+msgid "Color length must be 3 or 6 characters"
+msgstr ""
+
+#: ./src/pyams_utils/schema.py:51
+msgid ""
+"Color value must contain only valid hexadecimal color codes (numbers or "
+"letters between 'A' end 'F')"
+msgstr ""
+
+#: ./src/pyams_utils/timezone/interfaces.py:32
+msgid "Server timezone"
+msgstr ""
+
+#: ./src/pyams_utils/timezone/interfaces.py:33
+msgid "Default server timezone"
+msgstr ""
+
+#: ./src/pyams_utils/interfaces/text.py:36
+msgid "Renderer name"
+msgstr ""
+
+#: ./src/pyams_utils/browser/decimal.py:35
+msgid "The entered value is not a valid decimal literal."
+msgstr ""
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/property.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,105 @@
+#
+# 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 interfaces
+
+# import packages
+from pyams_utils.request import check_request, get_request_data, set_request_data
+from pyams_utils.session import get_session_data, set_session_data
+
+
+class cached(object):
+ """Custom property decorator to define a property or function
+ which is calculated only once
+
+ When applied on a function, caching is based on input arguments
+ """
+
+ def __init__(self, function):
+ self._function = function
+ self._cache = {}
+
+ def __call__(self, *args):
+ try:
+ return self._cache[args]
+ except KeyError:
+ self._cache[args] = self._function(*args)
+ return self._cache[args]
+
+ def expire(self, *args):
+ del self._cache[args]
+
+
+class cached_property(object):
+ """A read-only @property decorator that is only evaluated once. The value is cached
+ on the object itself rather than the function or class; this should prevent
+ memory leakage.
+ """
+ def __init__(self, fget, doc=None):
+ self.fget = fget
+ self.__doc__ = doc or fget.__doc__
+ self.__name__ = fget.__name__
+ self.__module__ = fget.__module__
+
+ def __get__(self, obj, cls):
+ if obj is None:
+ return self
+ obj.__dict__[self.__name__] = result = self.fget(obj)
+ return result
+
+
+_marker = object()
+
+class request_property(object):
+ """Define a property stored in request annotations"""
+
+ def __init__(self, fget, key=None):
+ self.fget = fget
+ if key is None:
+ key = "%s.%s" % (fget.__module__, fget.__name__)
+ self.key = key
+
+ def __get__(self, obj, cls):
+ if obj is None:
+ return self
+ request = check_request()
+ data = get_request_data(request, self.key, _marker)
+ if data is _marker:
+ data = self.fget(obj)
+ set_request_data(request, self.key, data)
+ return data
+
+
+class session_property(object):
+ """Define a property stored into session"""
+
+ def __init__(self, fget, app, key=None):
+ self.fget = fget
+ self.app = app
+ if key is None:
+ key = "%s.%s" % (fget.__module__, fget.__name__)
+ self.key = key
+
+ def __get__(self, obj, cls):
+ if obj is None:
+ return self
+ request = check_request()
+ data = get_session_data(request, self.app, self.key, _marker)
+ if data is _marker:
+ data = self.fget(obj)
+ set_session_data(request, self.app, self.key, data)
+ return data
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/protocol/__init__.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,1 @@
+#
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/protocol/http.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,79 @@
+#
+# 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 httplib2
+import urllib.parse
+
+# import interfaces
+
+# import packages
+
+
+class HTTPClient(object):
+ """HTTP client"""
+
+ def __init__(self, method, protocol, servername, url, params={}, credentials=(),
+ proxy=(), rdns=True, proxy_auth=(), timeout=None, headers={}):
+ """Intialize HTTP connection"""
+ self.connection = None
+ self.method = method
+ self.protocol = protocol
+ self.servername = servername
+ self.url = url
+ self.params = params
+ self.location = None
+ self.credentials = credentials
+ self.proxy = proxy
+ self.rdns = rdns
+ self.proxy_auth = proxy_auth
+ self.timeout = timeout
+ self.headers = headers
+ if 'User-Agent' not in headers:
+ self.headers['User-Agent'] = 'PyAMS HTTP Client/1.0'
+
+ def get_response(self):
+ """Common HTTP request"""
+ if self.proxy:
+ proxy_info = httplib2.ProxyInfo(httplib2.socks.PROXY_TYPE_HTTP,
+ proxy_host=self.proxy[0],
+ proxy_port=self.proxy[1],
+ proxy_rdns=self.rdns,
+ proxy_user=self.proxy_auth and self.proxy_auth[0] or None,
+ proxy_pass=self.proxy_auth and self.proxy_auth[1] or None)
+ else:
+ proxy_info = None
+ http = httplib2.Http(timeout=self.timeout, proxy_info=proxy_info)
+ if self.credentials:
+ http.add_credentials(self.credentials[0], self.credentials[1])
+ uri = '%s://%s%s' % (self.protocol, self.servername, self.url)
+ if self.params:
+ uri += '?' + urllib.parse.urlencode(self.params)
+ response, content = http.request(uri, self.method, headers=self.headers)
+ return response, content
+
+
+def get_client(method, protocol, servername, url, params={}, credentials=(), proxy=(),
+ rdns=True, proxy_auth=(), timeout=None, headers={}):
+ """HTTP client factory"""
+ return HTTPClient(method, protocol, servername, url, params, credentials, proxy,
+ rdns, proxy_auth, timeout, headers)
+
+
+def get_client_from_url(url, credentials=(), proxy=(), rdns=True, proxy_auth=(), timeout=None, headers={}):
+ """HTTP client factory from URL"""
+ elements = urllib.parse.urlparse(url)
+ return HTTPClient('GET', elements.scheme, elements.netloc, elements.path, elements.params,
+ credentials, proxy, rdns, proxy_auth, timeout, headers)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/protocol/xmlrpc.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,144 @@
+#
+# 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 base64
+import http.client
+import http.cookiejar
+import socket
+import urllib.request
+import xmlrpc.client
+
+# import interfaces
+
+# import packages
+
+
+class XMLRPCCookieAuthTransport(xmlrpc.client.Transport):
+ """An XML-RPC transport handling authentication via cookies"""
+
+ _http_connection = http.client.HTTPConnection
+
+ def __init__(self, user_agent, credentials=(), cookies=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, headers=None):
+ xmlrpc.client.Transport.__init__(self)
+ self.user_agent = user_agent
+ self.credentials = credentials
+ self.cookies = cookies
+ self.timeout = timeout
+ self.headers = headers
+
+ def make_connection(self, host):
+ # This is the make_connection that runs under Python 2.7 and newer.
+ # The code is pulled straight from 2.7 xmlrpclib, except replacing
+ # HTTPConnection with self._http_connection
+ if self._connection and host == self._connection[0]:
+ return self._connection[1]
+ chost, self._extra_headers, _x509 = self.get_host_info(host)
+ self._connection = host, self._http_connection(chost, timeout=self.timeout)
+ return self._connection[1]
+
+ # override the send_host hook to also send authentication info
+ def send_host(self, connection, host):
+ connection.putheader('Host', host)
+ if (self.cookies is not None) and (len(self.cookies) > 0):
+ for cookie in self.cookies:
+ connection.putheader('Cookie', '%s=%s' % (cookie.name, cookie.value))
+ elif self.credentials:
+ auth = 'Basic %s' % base64.encodebytes("%s:%s" % self.credentials).strip()
+ connection.putheader('Authorization', auth)
+
+ # send user agent
+ def send_user_agent(self, connection):
+ connection.putheader('User-Agent', self.user_agent)
+
+ # send custom headers
+ def send_headers(self, connection, headers):
+ xmlrpc.client.Transport.send_headers(self, connection, headers)
+ for k, v in (self.headers or {}).iteritems():
+ connection.putheader(k, v)
+
+ # dummy request class for extracting cookies
+ class CookieRequest(urllib.request.Request):
+ pass
+
+ # dummy response info headers helper
+ class CookieResponseHelper:
+ def __init__(self, response):
+ self.response = response
+ def getheaders(self, header):
+ return self.response.msg.getallmatchingheaders(header)
+
+ # dummy response class for extracting cookies
+ class CookieResponse:
+ def __init__(self, response):
+ self.response = response
+ def info(self):
+ return XMLRPCCookieAuthTransport.CookieResponseHelper(self.response)
+
+ def request(self, host, handler, request_body, verbose=False):
+ # issue XML-RPC request
+ connection = self.make_connection(host)
+ self.verbose = verbose
+ if verbose:
+ connection.set_debuglevel(1)
+ self.send_request(connection, handler, request_body)
+ self.send_host(connection, host)
+ self.send_user_agent(connection)
+ self.send_headers(connection)
+ self.send_content(connection, request_body)
+ # get response
+ return self.get_response(connection, host, handler)
+
+ def get_response(self, connection, host, handler):
+ response = connection.getresponse()
+ # extract cookies from response headers
+ if self.cookies is not None:
+ crequest = XMLRPCCookieAuthTransport.CookieRequest('http://%s/' % host)
+ cresponse = XMLRPCCookieAuthTransport.CookieResponse(response)
+ self.cookies.extract_cookies(cresponse, crequest)
+ if response.status != 200:
+ raise xmlrpc.client.ProtocolError(host + handler, response.status, response.reason, response.getheaders())
+ return self.parse_response(response)
+
+
+class SecureXMLRPCCookieAuthTransport(XMLRPCCookieAuthTransport):
+ """Secure XML-RPC transport"""
+
+ _http_connection = http.client.HTTPSConnection
+
+
+def get_client(uri, credentials=(), verbose=False, allow_none=0, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, headers=None):
+ """Get an XML-RPC client which supports basic authentication"""
+ if uri.startswith('https:'):
+ transport = SecureXMLRPCCookieAuthTransport('Python XML-RPC Client/0.1 (PyAMS secure transport)', credentials,
+ timeout=timeout, headers=headers)
+ else:
+ transport = XMLRPCCookieAuthTransport('Python XML-RPC Client/0.1 (PyAMS basic transport)', credentials,
+ timeout=timeout, headers=headers)
+ return xmlrpc.client.Server(uri, transport=transport, verbose=verbose, allow_none=allow_none)
+
+
+def get_client_with_cookies(uri, credentials=(), verbose=False, allow_none=0, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
+ headers=None, cookies=None):
+ """Get an XML-RPC client which supports authentication through cookies"""
+ if cookies is None:
+ cookies = http.cookiejar.CookieJar()
+ if uri.startswith('https:'):
+ transport = SecureXMLRPCCookieAuthTransport('Python XML-RPC Client/0.1 (PyAMS secure cookie transport)',
+ credentials, cookies, timeout, headers)
+ else:
+ transport = XMLRPCCookieAuthTransport('Python XML-RPC Client/0.1 (PyAMS basic cookie transport)',
+ credentials, cookies, timeout, headers)
+ return xmlrpc.client.Server(uri, transport=transport, verbose=verbose, allow_none=allow_none)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/registry.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,123 @@
+#
+# 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 venusian
+
+# import interfaces
+from zope.component.interfaces import ComponentLookupError
+
+# import packages
+from pyramid.threadlocal import get_current_registry, get_current_request
+from zope.interface import implementedBy, providedBy
+
+
+def _get_registries():
+ """Get list of component registries"""
+ registry = get_current_registry()
+ yield registry
+ request = get_current_request()
+ if (request is not None) and (request.registry != registry):
+ yield request.registry
+
+
+def registered_utilities():
+ """Get utilities registrations as generator"""
+ for registry in _get_registries():
+ for utility in registry.registeredUtilities():
+ yield utility
+
+
+def query_utility(provided, name='', default=None):
+ """Query utility registered with given interface"""
+ for registry in _get_registries():
+ utility = registry.queryUtility(provided, name, default)
+ if utility is not None:
+ return utility
+ return default
+
+
+def get_utility(provided, name=''):
+ """Get utility registered with given interface"""
+ for registry in _get_registries():
+ utility = registry.queryUtility(provided, name)
+ if utility is not None:
+ return utility
+ raise ComponentLookupError(provided, name)
+
+
+def get_utilities_for(interface):
+ """Get utilities registered with given interface as (name, util)"""
+ for registry in _get_registries():
+ for utility in registry.getUtilitiesFor(interface):
+ yield utility
+
+
+def get_all_utilities_registered_for(interface):
+ """Get list of registered utilities for given interface"""
+ result = []
+ for registry in _get_registries():
+ for utilities in registry.getAllUtilitiesRegisteredFor(interface):
+ result.extend(utilities)
+ return result
+
+
+class utility_config(object):
+ """Function or class decorator to declare a utility"""
+
+ venusian = venusian
+
+ def __init__(self, **settings):
+ self.__dict__.update(settings)
+
+ def __call__(self, wrapped):
+ settings = self.__dict__.copy()
+ depth = settings.pop('_depth', 0)
+
+ def callback(context, name, ob):
+ if type(ob) is type:
+ factory = ob
+ component = None
+ else:
+ factory = None
+ component = ob
+
+ provides = settings.get('provides')
+ if provides is None:
+ if factory:
+ provides = list(implementedBy(factory))
+ else:
+ provides = list(providedBy(component))
+ if len(provides) == 1:
+ provides = provides[0]
+ else:
+ raise TypeError("Missing 'provides' argument")
+
+ config = context.config.with_package(info.module)
+ config.registry.registerUtility(component=component, factory=factory,
+ provided=provides, name=settings.get('name', ''))
+
+ info = self.venusian.attach(wrapped, callback, category='pyams_utility',
+ depth=depth + 1)
+
+ 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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/request.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,80 @@
+#
+# 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 interfaces
+from zope.annotation.interfaces import IAttributeAnnotatable, IAnnotations
+from zope.security.interfaces import NoInteraction
+
+# import packages
+from pyramid.request import Request
+from pyramid.threadlocal import get_current_request
+from zope.interface import alsoProvides
+
+
+def get_request(raise_exception=True):
+ """Get current request
+
+ Raises a NoInteraction exception if there is no active request"""
+ request = get_current_request()
+ if (request is None) and raise_exception:
+ raise NoInteraction("No request")
+ return request
+
+
+def query_request():
+ """Query current request
+
+ Returns None if there is no active request"""
+ try:
+ return get_request()
+ except NoInteraction:
+ return None
+
+
+def check_request(path='/', environ=None, base_url=None, headers=None, POST=None, **kw):
+ """Get current request, or create a new blank one if missing"""
+ try:
+ return get_request()
+ except NoInteraction:
+ return Request.blank(path, environ, base_url, headers, POST, **kw)
+
+
+def get_annotations(request):
+ """Define 'annotations' request property"""
+ alsoProvides(request, IAttributeAnnotatable)
+ return IAnnotations(request)
+
+
+def get_debug(request):
+ """Define 'debug' request property"""
+ class Debug():
+ def __init__(self):
+ self.showTAL = False
+ self.sourceAnnotations = False
+ return Debug()
+
+
+def get_request_data(request, key, default=None):
+ """Get data associated with request"""
+ annotations = request.annotations
+ return annotations.get(key, default)
+
+
+def set_request_data(request, key, value):
+ """Associate data with request"""
+ annotations = request.annotations
+ annotations[key] = value
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/schema.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,120 @@
+#
+# 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 string
+
+# import interfaces
+from zope.schema.interfaces import ITextLine, IDecimal, IList, ITuple, IPassword
+
+# import Zope3 packages
+from zope.interface import implementer
+from zope.schema import TextLine, Decimal, List, Tuple, Password, ValidationError
+
+# import local packages
+
+from pyams_utils import _
+
+
+#
+# Encoded password field
+#
+
+class IEncodedPassword(IPassword):
+ """Encoded password field interface"""
+
+
+@implementer(IEncodedPassword)
+class EncodedPassword(Password):
+ """Encoded password field"""
+
+ _type = None
+
+ def fromUnicode(self, str):
+ return str
+
+ def constraint(self, value):
+ return True
+
+
+#
+# Color field
+#
+
+class IColorField(ITextLine):
+ """Marker interface for color fields"""
+
+
+@implementer(IColorField)
+class ColorField(TextLine):
+ """Color field"""
+
+ def __init__(self, *args, **kw):
+ super(ColorField, self).__init__(max_length=6, *args, **kw)
+
+ def _validate(self, value):
+ if len(value) not in (3, 6):
+ raise ValidationError(_("Color length must be 3 or 6 characters"))
+ for v in value:
+ if v not in string.hexdigits:
+ raise ValidationError(_("Color value must contain only valid hexadecimal color codes (numbers or "
+ "letters between 'A' end 'F')"))
+ super(ColorField, self)._validate(value)
+
+
+#
+# Pointed decimal field
+#
+
+class IDottedDecimalField(IDecimal):
+ """Marker interface for dotted decimal fields"""
+
+
+@implementer(IDottedDecimalField)
+class DottedDecimalField(Decimal):
+ """Dotted decimal field"""
+
+
+#
+# Dates range field
+#
+
+class IDatesRangeField(ITuple):
+ """Marker interface for dates range fields"""
+
+
+@implementer(IDatesRangeField)
+class DatesRangeField(Tuple):
+ """Dates range field"""
+
+ def __init__(self, value_type=None, unique=False, **kw):
+ super(DatesRangeField, self).__init__(value_type=None, unique=False,
+ min_length=2, max_length=2, **kw)
+
+
+#
+# TextLine list field
+#
+
+class ITextLineListField(IList):
+ """Marker interface for textline list field"""
+
+
+@implementer(ITextLineListField)
+class TextLineListField(List):
+ """TextLine list field"""
+
+ def __init__(self, value_type=None, unique=False, **kw):
+ super(TextLineListField, self).__init__(value_type=TextLine(), unique=True, **kw)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/scripts/__init__.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,1 @@
+#
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/scripts/zodb.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,46 @@
+#
+# 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 optparse
+import sys
+import textwrap
+
+# import interfaces
+
+# import packages
+from pyams_utils.site import site_upgrade
+from pyramid.paster import bootstrap
+
+
+def upgrade_site():
+ """Check for site upgrade"""
+ usage = "usage: %prog config_uri"
+ description = """Check for database upgrade.
+ Usage: pyams_upgrade production.ini
+ """
+ parser = optparse.OptionParser(usage=usage,
+ description=textwrap.dedent(description))
+ options, args = parser.parse_args(sys.argv[1:])
+ if not len(args) >= 1:
+ print("You must provide at least one configuration file")
+ return 2
+ config_uri = args[0]
+ env = bootstrap(config_uri)
+ settings, closer = env['registry'].settings, env['closer']
+ try:
+ site_upgrade(env['request'])
+ finally:
+ closer()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/session.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,32 @@
+#
+# 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 interfaces
+
+# import packages
+
+
+def get_session_data(request, app, key, default=None):
+ """Get data associated with a given session"""
+ session = request.session
+ return session.get('{0}::{1}'.format(app, key), default)
+
+
+def set_session_data(request, app, key, value):
+ """Set data associated to a given session"""
+ session = request.session
+ session['{0}::{1}'.format(app, key)] = value
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/site.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,167 @@
+#
+# 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 interfaces
+from pyams_utils.interfaces import PYAMS_APPLICATION_SETTINGS_KEY, PYAMS_APPLICATION_DEFAULT_NAME, \
+ PYAMS_APPLICATION_FACTORY_KEY
+from pyams_utils.interfaces.site import ISiteRoot, INewLocalSiteCreatedEvent, ISiteUpgradeEvent, ISiteGenerations, \
+ SITE_GENERATIONS_KEY, IConfigurationManager
+from zope.annotation.interfaces import IAnnotations
+from zope.component.interfaces import IPossibleSite, ISite, ObjectEvent
+from zope.traversing.interfaces import IBeforeTraverseEvent, ITraversable
+
+# import packages
+from persistent.dict import PersistentDict
+from pyams_utils.adapter import adapter_config
+from pyams_utils.registry import get_utilities_for, query_utility
+from pyramid.events import subscriber
+from pyramid.exceptions import NotFound
+from pyramid.path import DottedNameResolver
+from pyramid.security import Allow, ALL_PERMISSIONS
+from pyramid.threadlocal import manager, get_current_registry
+from pyramid_zodbconn import get_connection
+from zope.container.folder import Folder
+from zope.interface import implementer
+from zope.lifecycleevent import ObjectCreatedEvent
+from zope.site import hooks
+from zope.site.site import LocalSiteManager, SiteManagerContainer
+
+
+@implementer(ISiteRoot, IConfigurationManager)
+class BaseSiteRoot(Folder, SiteManagerContainer):
+ """Default site root"""
+
+ __acl__ = [(Allow, 'system:admin', ALL_PERMISSIONS)]
+
+ config_klass = None
+
+
+@adapter_config(name='etc', context=ISiteRoot, provides=ITraversable)
+class SiteRootEtcTraverser(object):
+ """Site root ++etc++ namespace traverser"""
+
+ def __init__(self, context):
+ self.context = context
+
+ def traverse(self, name, furtherpath=None):
+ if name == 'site':
+ return self.context.getSiteManager()
+ raise NotFound
+
+
+@implementer(INewLocalSiteCreatedEvent)
+class NewLocalSiteCreatedEvent(ObjectEvent):
+ """New local site created event"""
+
+
+def site_factory(request):
+ """Build a new site including registered utilities"""
+ conn = get_connection(request)
+ root = conn.root()
+ application_key = request.registry.settings.get(PYAMS_APPLICATION_SETTINGS_KEY,
+ PYAMS_APPLICATION_DEFAULT_NAME)
+ application = root.get(application_key)
+ if application is None:
+ factory = request.registry.settings.get(PYAMS_APPLICATION_FACTORY_KEY)
+ if factory:
+ resolver = DottedNameResolver()
+ factory = resolver.maybe_resolve(factory)
+ else:
+ factory = BaseSiteRoot
+ application = root[application_key] = factory()
+ if IPossibleSite.providedBy(application):
+ sm = LocalSiteManager(application, default_folder=False)
+ application.setSiteManager(sm)
+ try:
+ # if some components require a valid and complete registry
+ # with all registered utilities, they can subscribe to
+ # INewLocalSiteCreatedEvent event interface
+ hooks.setSite(application)
+ get_current_registry().notify(NewLocalSiteCreatedEvent(application))
+ finally:
+ hooks.setSite(None)
+ import transaction
+ transaction.commit()
+ return application
+
+
+@implementer(ISiteUpgradeEvent)
+class SiteUpgradeEvent(ObjectEvent):
+ """Site upgrade request event"""
+
+
+def site_upgrade(request):
+ """Upgrade site when needed
+
+ This function is executed by pyams_upgrade console script.
+ Site generations are registered as named utilities providing
+ ISiteGenerations interface.
+ Current site generations are stored into annotations.
+ """
+ application = site_factory(request)
+ if application is not None:
+ try:
+ hooks.setSite(application)
+ annotations = IAnnotations(application)
+ generations = annotations.get(SITE_GENERATIONS_KEY)
+ if generations is None:
+ generations = annotations[SITE_GENERATIONS_KEY] = PersistentDict()
+ for name, utility in get_utilities_for(ISiteGenerations):
+ if not name:
+ name = '.'.join((utility.__module__, utility.__class__.__name__))
+ current = generations.get(name)
+ if (not current) or (current < utility.generation):
+ print("Upgrading {0} from generation {1} to {2}...".format(name, current, utility.generation))
+ utility.evolve(application, current)
+ generations[name] = utility.generation
+ finally:
+ hooks.setSite(None)
+ import transaction
+ transaction.commit()
+ return application
+
+
+@subscriber(IBeforeTraverseEvent, context_selector=ISite)
+def handle_site_before_traverse(event):
+ """Push registry and request to threadlocal manager when an
+ object implementing ISite is traversed
+ """
+ manager.push({'registry': event.object.getSiteManager(),
+ 'request': event.request})
+ hooks.setSite(event.object)
+
+
+def check_required_utilities(site, utilities):
+ """Utility function to check for required utilities
+
+ utilities argument is a tuple made of:
+ - the utility interface
+ - the utility name
+ - the utility factory
+ - the default name when creating the utility
+ """
+ registry = get_current_registry()
+ for interface, name, factory, default_id in utilities:
+ utility = query_utility(interface, name=name)
+ if utility is None:
+ sm = site.getSiteManager()
+ if default_id in sm:
+ continue
+ utility = factory()
+ registry.notify(ObjectCreatedEvent(utility))
+ sm[default_id] = utility
+ sm.registerUtility(utility, interface, name=name)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/size.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,59 @@
+#
+# 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 interfaces
+
+# import packages
+from babel.core import Locale
+from babel.numbers import format_decimal
+from pyams_utils.request import check_request
+
+from pyams_utils import _
+
+
+def get_human_size(value, request=None):
+ """Convert given bytes value in human readable format
+
+ >>> from pyramid.testing import DummyRequest
+ >>> request = DummyRequest(params={'_LOCALE_': 'fr'})
+ >>> request.locale_name
+ 'fr'
+
+ >>> from pyams_utils.size import get_human_size
+ >>> get_human_size(256, request)
+ '256 bytes'
+ >>> get_human_size(3678, request)
+ '3,6 Kb'
+ >>> get_human_size(6785342, request)
+ '6,47 Mb'
+ >>> get_human_size(3674815342, request)
+ '3,422 Gb'
+ """
+ if request is None:
+ request = check_request()
+ translate = request.localizer.translate
+ locale = Locale(request.locale_name)
+ if value < 1024:
+ return format_decimal(value, translate(_('0 bytes')), locale)
+ value /= 1024
+ if value < 1024:
+ return format_decimal(value, translate(_('0.# Kb')), locale)
+ value /= 1024
+ if value < 1024:
+ return format_decimal(value, translate(_('0.0# Mb')), locale)
+ value /= 1024
+ return format_decimal(value, translate(_('0.0## Gb')), locale)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/tales.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,80 @@
+#
+# 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 re
+
+# import interfaces
+from pyams_utils.interfaces.tales import ITALESExtension
+
+# import packages
+from chameleon.astutil import Symbol
+from chameleon.codegen import template
+from chameleon.tales import StringExpr
+from zope.contentprovider.tales import addTALNamespaceData
+
+
+class ContextExprMixin(object):
+ """Mixin-class for expression compilers."""
+
+ transform = None
+
+ def __call__(self, target, engine):
+ # Make call to superclass to assign value to target
+ assignment = super(ContextExprMixin, self).__call__(target, engine)
+ transform = template("target = transform(econtext, target)",
+ target=target,
+ transform=self.transform)
+ return assignment + transform
+
+
+FUNCTION_EXPRESSION = re.compile('(.+)\((.+)\)')
+
+
+def render_extension(econtext, name):
+ name = name.strip()
+
+ context = econtext.get('context')
+ request = econtext.get('request')
+ view = econtext.get('view')
+
+ func_match = FUNCTION_EXPRESSION.match(name)
+ if func_match:
+ name, argument = func_match.groups()
+ arg_value = econtext.get(argument, argument)
+ else:
+ arg_value = None
+
+ registry = request.registry
+ extension = registry.queryMultiAdapter((context, request, view), ITALESExtension, name=name)
+ if extension is None:
+ extension = registry.queryMultiAdapter((context, request), ITALESExtension, name=name)
+ if extension is None:
+ extension = registry.queryAdapter(context, ITALESExtension, name=name)
+
+ # provide a useful error message, if the extension was not found.
+ if extension is None:
+ return None
+
+ # Insert the data gotten from the context
+ addTALNamespaceData(extension, econtext)
+
+ return extension.render(arg_value)
+
+
+class ExtensionExpr(ContextExprMixin, StringExpr):
+ """extension: TALES expression"""
+
+ transform = Symbol(render_extension)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/tests/__init__.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,1 @@
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/tests/test_utilsdocs.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,62 @@
+### -*- coding: utf-8 -*- ####################################################
+##############################################################################
+#
+# Copyright (c) 2008-2010 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 ztfy.utils 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')
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/tests/test_utilsdocstrings.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,65 @@
+### -*- coding: utf-8 -*- ####################################################
+##############################################################################
+#
+# Copyright (c) 2008-2010 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_utils 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_utils.%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')
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/text.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,140 @@
+#
+# 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 html
+import docutils.core
+
+# import interfaces
+from pyams_utils.interfaces.tales import ITALESExtension
+from pyams_utils.interfaces.text import IHTMLRenderer
+from pyramid.interfaces import IRequest
+from zope.schema.interfaces import IVocabularyFactory
+
+# import packages
+from pyams_utils.adapter import ContextRequestViewAdapter, adapter_config
+from pyams_utils.request import check_request
+from zope.component import adapter
+from zope.interface import implementer, provider, Interface
+from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm, getVocabularyRegistry
+
+
+def get_text_start(text, length, max=0):
+ """Get first words of given text with maximum given length
+
+ If @max is specified, text is shortened only if remaining text is longer than @max
+
+ @param text: initial text
+ @param length: maximum length of resulting text
+ @param max: if > 0, @text is shortened only if remaining text is longer than max
+
+ >>> from pyams_utils.text import get_text_start
+ >>> get_text_start('This is a long string', 10)
+ 'This is a…'
+ >>> get_text_start('This is a long string', 20)
+ 'This is a long…'
+ >>> get_text_start('This is a long string', 20, 7)
+ 'This is a long string'
+ """
+ result = text or ''
+ if length > len(result):
+ return result
+ index = length - 1
+ text_length = len(result)
+ while (index > 0) and (result[index] != ' '):
+ index -= 1
+ if (index > 0) and (text_length > index + max):
+ return result[:index] + '…'
+ return text
+
+
+@adapter_config(name='text', context=(str, IRequest), provides=IHTMLRenderer)
+class BaseHTMLRenderer(object):
+ """Raw text renderer utility class"""
+
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+ def render(self, **kwargs):
+ return self.context
+
+
+@adapter_config(name='text', context=(str, IRequest), provides=IHTMLRenderer)
+class TextRenderer(BaseHTMLRenderer):
+ """Render raw text to HTML"""
+
+ def render(self, **kwargs):
+ return html.escape(self.context).replace('\n', '<br />\n')
+
+
+@adapter_config(name='rest', context=(str, IRequest), provides=IHTMLRenderer)
+class ReStructuredTextRenderer(BaseHTMLRenderer):
+ """Render reStructuredText to HTML"""
+
+ def render(self, **kwargs):
+ """Render reStructuredText to HTML"""
+ overrides = {
+ 'halt_level': 6,
+ 'input_encoding': 'unicode',
+ 'output_encoding': 'unicode',
+ 'initial_header_level': 3,
+ }
+ if 'settings' in kwargs:
+ overrides.update(kwargs['settings'])
+ parts = docutils.core.publish_parts(self.context,
+ writer_name='html',
+ settings_overrides=overrides)
+ return ''.join((parts['body_pre_docinfo'], parts['docinfo'], parts['body']))
+
+
+def text_to_html(text, renderer='text'):
+ """Convert text to HTML using the given renderer"""
+ request = check_request()
+ registry = request.registry
+ renderer = registry.queryMultiAdapter((text, request), IHTMLRenderer, name=renderer)
+ if renderer is not None:
+ return renderer.render()
+
+
+@adapter_config(name='html', context=(Interface, Interface, Interface), provides=ITALESExtension)
+class HTMLTalesExtension(ContextRequestViewAdapter):
+ """extension:html TALES expression"""
+
+ def render(self, context):
+ if context is None:
+ context = self.context
+ renderer = self.request.registry.queryMultiAdapter((context, self.request, self.view), IHTMLRenderer)
+ if renderer is not None:
+ return renderer.render()
+ elif isinstance(context, str):
+ return text_to_html(context, 'text')
+ else:
+ return str(context)
+
+
+@provider(IVocabularyFactory)
+class RenderersVocabulary(SimpleVocabulary):
+ """Text renderers vocabulary"""
+
+ def __init__(self):
+ request = check_request()
+ registry = request.registry
+ translate = registry.localizer.translate
+ terms = [SimpleTerm(name, name, translate(adapt.title).label)
+ for name, adapt in registry.getAdapters(('', request), IHTMLRenderer)]
+ super(RenderersVocabulary, self).__init__(terms)
+
+getVocabularyRegistry().register('PyAMS HTML renderers', RenderersVocabulary)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/timezone/__init__.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,68 @@
+#
+# 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
+from datetime import datetime
+
+import pytz
+
+
+# import interfaces
+from pyams_utils.interfaces.timezone import IServerTimezone
+from pyramid.interfaces import IRequest
+from zope.interface.common.idatetime import ITZInfo
+
+# import packages
+from pyams_utils.adapter import adapter_config
+from pyams_utils.registry import query_utility
+
+
+GMT = pytz.timezone('GMT')
+_tz = pytz.timezone('Europe/Paris')
+tz = _tz
+
+
+@adapter_config(context=IRequest, provides=ITZInfo)
+def tzinfo(request=None):
+ """request to timezone adapter
+
+ There is no easy way to get timezone from a request.
+ This adapter assumes that the timezone is given by
+ a registered utility...
+ """
+ util = query_utility(IServerTimezone)
+ if util is not None:
+ return pytz.timezone(util.timezone)
+ return GMT
+
+
+def tztime(value):
+ if not value:
+ return None
+ if not isinstance(value, datetime):
+ return value
+ if not value.tzinfo:
+ value = GMT.localize(value)
+ return value.astimezone(tzinfo())
+
+
+def gmtime(value):
+ if not value:
+ return None
+ if not isinstance(value, datetime):
+ return value
+ if not value.tzinfo:
+ value = GMT.localize(value)
+ return value.astimezone(GMT)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/timezone/utility.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,57 @@
+#
+# 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 interfaces
+from pyams_utils.interfaces.site import ISiteGenerations
+from pyams_utils.interfaces.timezone import IServerTimezone
+from zope.site.interfaces import INewLocalSite
+
+# import packages
+from persistent import Persistent
+from pyams_utils.registry import utility_config
+from pyams_utils.site import check_required_utilities
+from pyramid.events import subscriber
+from zope.container.contained import Contained
+from zope.interface import implementer
+from zope.schema.fieldproperty import FieldProperty
+
+
+@implementer(IServerTimezone)
+class ServerTimezoneUtility(Persistent, Contained):
+
+ timezone = FieldProperty(IServerTimezone['timezone'])
+
+
+REQUIRED_UTILITIES = ((IServerTimezone, '', ServerTimezoneUtility, 'Server timezone'),)
+
+
+@subscriber(INewLocalSite)
+def handle_new_local_site(event):
+ """Create a new ServerTimezoneUtility when a site is created"""
+ site = event.manager.__parent__
+ check_required_utilities(site, REQUIRED_UTILITIES)
+
+
+@utility_config(name='PyAMS timezone checker', provides=ISiteGenerations)
+class TimezoneGenerationsChecker(object):
+ """Timezone generations checker"""
+
+ generation = 1
+
+ def evolve(self, site, current=None):
+ """Check for required utilities"""
+ check_required_utilities(site, REQUIRED_UTILITIES)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/timezone/vocabulary.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,35 @@
+#
+# 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 pytz
+
+# import interfaces
+from zope.schema.interfaces import IVocabularyFactory
+
+# import packages
+from zope.interface import provider
+from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary, getVocabularyRegistry
+
+
+@provider(IVocabularyFactory)
+class TimezonesVocabulary(SimpleVocabulary):
+ """Timezones vocabulary"""
+
+ def __init__(self, *args, **kw):
+ terms = [SimpleTerm(t, t, t) for t in pytz.all_timezones]
+ super(TimezonesVocabulary, self).__init__(terms)
+
+getVocabularyRegistry().register('PyAMS timezones', TimezonesVocabulary)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/traversing.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,174 @@
+#
+# 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 interfaces
+from pyramid.interfaces import VH_ROOT_KEY
+from zope.traversing.interfaces import ITraversable, BeforeTraverseEvent
+
+# import packages
+from pyramid.compat import decode_path_info, is_nonstr_iter
+from pyramid.exceptions import URLDecodeError, NotFound
+from pyramid.threadlocal import get_current_registry
+from pyramid.traversal import ResourceTreeTraverser, slash, split_path_info, empty
+from zope.interface import Interface
+
+
+class NamespaceTraverser(ResourceTreeTraverser):
+ """Custom traverser handling views and namespaces
+
+ This is an upgraded version of native Pyramid traverser.
+ It adds:
+ - a new BeforeTraverseEvent before traversing each object in the path
+ - support for namespaces with "++" notation
+ """
+
+ NAMESPACE_SELECTOR = '++'
+
+ def __call__(self, request):
+
+ environ = request.environ
+ matchdict = request.matchdict
+
+ if matchdict is not None:
+ path = matchdict.get('traverse', slash) or slash
+ if is_nonstr_iter(path):
+ # this is a *traverse stararg (not a {traverse})
+ # routing has already decoded these elements, so we just
+ # need to join them
+ path = '/' + slash.join(path) or slash
+
+ subpath = matchdict.get('subpath', ())
+ if not is_nonstr_iter(subpath):
+ # this is not a *subpath stararg (just a {subpath})
+ # routing has already decoded this string, so we just need
+ # to split it
+ subpath = split_path_info(subpath)
+
+ else:
+ subpath = ()
+ try:
+ # empty if mounted under a path in mod_wsgi, for example
+ path = request.path_info or slash
+ except KeyError:
+ # if environ['PATH_INFO'] is just not there
+ path = slash
+ except UnicodeDecodeError as e:
+ raise URLDecodeError(e.encoding, e.object, e.start, e.end, e.reason)
+
+ if VH_ROOT_KEY in environ:
+ # HTTP_X_VHM_ROOT
+ vroot_path = decode_path_info(environ[VH_ROOT_KEY])
+ vroot_tuple = split_path_info(vroot_path)
+ vpath = vroot_path + path
+ vroot_idx = len(vroot_tuple) - 1
+ else:
+ vroot_tuple = ()
+ vpath = path
+ vroot_idx = -1
+
+ root = self.root
+ ob = vroot = root
+
+ request.registry.notify(BeforeTraverseEvent(root, request))
+
+ if vpath == slash:
+ # invariant: vpath must not be empty
+ # prevent a call to traversal_path if we know it's going
+ # to return the empty tuple
+ vpath_tuple = ()
+
+ else:
+ # we do dead reckoning here via tuple slicing instead of
+ # pushing and popping temporary lists for speed purposes
+ # and this hurts readability; apologies
+ i = 0
+ view_selector = self.VIEW_SELECTOR
+ ns_selector = self.NAMESPACE_SELECTOR
+ vpath_tuple = split_path_info(vpath)
+
+ for segment in vpath_tuple:
+ request.registry.notify(BeforeTraverseEvent(ob, request))
+
+ if segment[:2] == view_selector:
+ return {'context': ob,
+ 'view_name': segment[2:],
+ 'subpath': vpath_tuple[i + 1:],
+ 'traversed': vpath_tuple[:vroot_idx + i + 1],
+ 'virtual_root': vroot,
+ 'virtual_root_path': vroot_tuple,
+ 'root': root}
+
+ if segment[:2] == ns_selector:
+ ns, name = segment[2:].split(ns_selector, 1)
+ registry = get_current_registry()
+ traverser = registry.queryMultiAdapter((ob, request), ITraversable, ns)
+ if traverser is None:
+ traverser = registry.queryAdapter(ob, ITraversable, ns)
+ if traverser is None:
+ traverser = registry.queryAdapter(request, ITraversable, ns)
+ if traverser is None:
+ raise NotFound()
+ ob = traverser.traverse(name, vpath_tuple[vroot_idx + i + 1:])
+ i += 1
+ continue
+
+ try:
+ getitem = ob.__getitem__
+ except AttributeError:
+ return {'context': ob,
+ 'view_name': segment,
+ 'subpath': vpath_tuple[i + 1:],
+ 'traversed': vpath_tuple[:vroot_idx + i + 1],
+ 'virtual_root': vroot,
+ 'virtual_root_path': vroot_tuple,
+ 'root': root}
+
+ try:
+ next = getitem(segment)
+ except KeyError:
+ return {'context': ob,
+ 'view_name': segment,
+ 'subpath': vpath_tuple[i + 1:],
+ 'traversed': vpath_tuple[:vroot_idx + i + 1],
+ 'virtual_root': vroot,
+ 'virtual_root_path': vroot_tuple,
+ 'root': root}
+ if i == vroot_idx:
+ vroot = next
+ ob = next
+ i += 1
+
+ return {'context': ob,
+ 'view_name': empty,
+ 'subpath': subpath,
+ 'traversed': vpath_tuple,
+ 'virtual_root': vroot,
+ 'virtual_root_path': vroot_tuple,
+ 'root': root}
+
+
+def get_parent(context, interface=Interface, allow_context=True):
+ """Get first parent of the context that implements given interface"""
+ if allow_context:
+ parent = context
+ else:
+ parent = getattr(context, '__parent__', None)
+ while parent is not None:
+ if interface.providedBy(parent):
+ return interface(parent)
+ parent = getattr(parent, '__parent__', None)
+ return None
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/unicode.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,173 @@
+#
+# 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 codecs
+import string
+
+# import interfaces
+
+# import packages
+
+
+_unicodeTransTable = {}
+def _fillUnicodeTransTable():
+ _corresp = [
+ ("A", [0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x0100, 0x0102, 0x0104]),
+ ("AE", [0x00C6]),
+ ("a", [0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x0101, 0x0103, 0x0105]),
+ ("ae", [0x00E6]),
+ ("C", [0x00C7, 0x0106, 0x0108, 0x010A, 0x010C]),
+ ("c", [0x00E7, 0x0107, 0x0109, 0x010B, 0x010D]),
+ ("D", [0x00D0, 0x010E, 0x0110]),
+ ("d", [0x00F0, 0x010F, 0x0111]),
+ ("E", [0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x0112, 0x0114, 0x0116, 0x0118, 0x011A]),
+ ("e", [0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x0113, 0x0115, 0x0117, 0x0119, 0x011B]),
+ ("G", [0x011C, 0x011E, 0x0120, 0x0122]),
+ ("g", [0x011D, 0x011F, 0x0121, 0x0123]),
+ ("H", [0x0124, 0x0126]),
+ ("h", [0x0125, 0x0127]),
+ ("I", [0x00CC, 0x00CD, 0x00CE, 0x00CF, 0x0128, 0x012A, 0x012C, 0x012E, 0x0130]),
+ ("i", [0x00EC, 0x00ED, 0x00EE, 0x00EF, 0x0129, 0x012B, 0x012D, 0x012F, 0x0131]),
+ ("IJ", [0x0132]),
+ ("ij", [0x0133]),
+ ("J", [0x0134]),
+ ("j", [0x0135]),
+ ("K", [0x0136]),
+ ("k", [0x0137, 0x0138]),
+ ("L", [0x0139, 0x013B, 0x013D, 0x013F, 0x0141]),
+ ("l", [0x013A, 0x013C, 0x013E, 0x0140, 0x0142]),
+ ("N", [0x00D1, 0x0143, 0x0145, 0x0147, 0x014A]),
+ ("n", [0x00F1, 0x0144, 0x0146, 0x0148, 0x0149, 0x014B]),
+ ("O", [0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D8, 0x014C, 0x014E, 0x0150]),
+ ("o", [0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F8, 0x014D, 0x014F, 0x0151]),
+ ("OE", [0x0152]),
+ ("oe", [0x0153]),
+ ("R", [0x0154, 0x0156, 0x0158]),
+ ("r", [0x0155, 0x0157, 0x0159]),
+ ("S", [0x015A, 0x015C, 0x015E, 0x0160]),
+ ("s", [0x015B, 0x015D, 0x015F, 0x01610, 0x017F]),
+ ("T", [0x0162, 0x0164, 0x0166]),
+ ("t", [0x0163, 0x0165, 0x0167]),
+ ("U", [0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x0168, 0x016A, 0x016C, 0x016E, 0x0170, 0x172]),
+ ("u", [0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x0169, 0x016B, 0x016D, 0x016F, 0x0171]),
+ ("W", [0x0174]),
+ ("w", [0x0175]),
+ ("Y", [0x00DD, 0x0176, 0x0178]),
+ ("y", [0x00FD, 0x00FF, 0x0177]),
+ ("Z", [0x0179, 0x017B, 0x017D]),
+ ("z", [0x017A, 0x017C, 0x017E])
+ ]
+ for char, codes in _corresp:
+ for code in codes:
+ _unicodeTransTable[code] = char
+
+_fillUnicodeTransTable()
+
+
+def translate_string(s, escape_slashes=False, force_lower=True, spaces=' ', keep_chars='_-.'):
+ """Remove extended characters from string and replace them with 'basic' ones
+
+ @param s: text to be cleaned.
+ @type s: str or unicode
+ @param escape_slashes: if True, slashes are also converted
+ @type escape_slashes: boolean
+ @param force_lower: if True, result is automatically converted to lower case
+ @type force_lower: boolean
+ @return: text without diacritics
+ @rtype: unicode
+ """
+ if escape_slashes:
+ s = s.replace("\\", "/").split("/")[-1]
+ s = s.strip()
+ if isinstance(s, bytes):
+ s = s.decode("utf-8", "replace")
+ s = s.translate(_unicodeTransTable)
+ s = ''.join([a for a in s.translate(_unicodeTransTable)
+ if a.replace(' ', '-') in (string.ascii_letters + string.digits + (keep_chars or ''))])
+ if force_lower:
+ s = s.lower()
+ if spaces != ' ':
+ s = s.replace(' ', spaces)
+ return s
+
+
+def nvl(value, default=''):
+ """Get specified value, or an empty string if value is empty
+
+ @param value: text to be checked
+ @param default: default value
+ @return: value, or default if value is empty
+ """
+ return value or default
+
+
+def uninvl(value, default='', encoding='utf-8'):
+ """Get specified value converted to unicode, or an empty unicode string if value is empty
+
+ @param value: text to be checked
+ @type value: str or unicode
+ @param default: default value
+ @return: value, or default if value is empty
+ @rtype: unicode
+ """
+ if isinstance(value, str):
+ return value
+ try:
+ return codecs.decode(value or default, encoding)
+ except:
+ return codecs.decode(value or default, 'latin1')
+
+
+def unidict(value, encoding='utf-8'):
+ """Get specified dict with values converted to unicode
+
+ @param value: input dict of strings which may be converted to unicode
+ @type value: dict
+ @return: input dict converted to unicode
+ @rtype: dict
+ """
+ result = {}
+ for key in value:
+ result[key] = uninvl(value[key], encoding)
+ return result
+
+
+def unilist(value, encoding='utf-8'):
+ """Get specified list with values converted to unicode
+
+ @param value: input list of strings which may be converted to unicode
+ @type value: list
+ @return: input list converted to unicode
+ @rtype: list
+ """
+ if not isinstance(value, (list, tuple)):
+ return uninvl(value, encoding)
+ return [uninvl(v, encoding) for v in value]
+
+
+def encode(value, encoding='utf-8'):
+ """Encode given Unicode value to bytes with given encoding"""
+ return value.encode(encoding) if isinstance(value, str) else value
+
+
+def utf8(value):
+ """Encode given unicode value to UTF-8 encoded bytes"""
+ return encode(value, 'utf-8')
+
+
+def decode(value, encoding='utf-8'):
+ """Decode given bytes value to unicode with given encoding"""
+ return value.decode(encoding) if isinstance(value, bytes) else value
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/url.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,48 @@
+#
+# 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 interfaces
+from persistent.interfaces import IPersistent
+from pyams_utils.interfaces.tales import ITALESExtension
+
+# import packages
+from pyams_utils.adapter import ContextRequestViewAdapter, adapter_config
+from pyramid.url import resource_url
+from zope.interface import Interface
+
+
+def absolute_url(context, request, view_name=None):
+ """Get resource absolute_url"""
+ result = resource_url(context, request)
+ if result.endswith('/'):
+ result = result[:-1]
+ if view_name:
+ if view_name.startswith('#'):
+ result += view_name
+ else:
+ result += '/' + view_name
+ return result
+
+
+@adapter_config(name='absolute_url', context=(IPersistent, Interface, Interface), provides=ITALESExtension)
+class AbsoluteUrlTalesExtension(ContextRequestViewAdapter):
+ """extension:absolute_url(context) TALES extension"""
+
+ def render(self, context):
+ if context is None:
+ context = self.context
+ return absolute_url(context, self.request)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/views/__init__.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,1 @@
+#
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/views/decimal.py Thu Feb 19 00:46:48 2015 +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.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+import decimal
+
+# import interfaces
+from z3c.form.interfaces import IWidget, IDataConverter
+
+# import packages
+from pyams_utils.adapter import adapter_config
+from pyams_utils.schema import IDottedDecimalField
+from z3c.form.converter import BaseDataConverter, FormatterValidationError
+
+from pyams_utils import _
+
+
+@adapter_config(context=(IDottedDecimalField, IWidget), provides=IDataConverter)
+class DottedDecimalDataConverter(BaseDataConverter):
+ """Dotted decimal field data converter"""
+
+ errorMessage = _('The entered value is not a valid decimal literal.')
+
+ def __init__(self, field, widget):
+ super(DottedDecimalDataConverter, self).__init__(field, widget)
+
+ def toWidgetValue(self, value):
+ if not value:
+ return self.field.missing_value
+ return value
+
+ def toFieldValue(self, value):
+ if value is self.field.missing_value:
+ return ''
+ if not value:
+ return None
+ try:
+ return decimal.Decimal(value)
+ except decimal.InvalidOperation:
+ raise FormatterValidationError(self.errorMessage, value)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/wsgi.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,42 @@
+#
+# 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 interfaces
+
+# import packages
+
+
+def wsgi_environ_cache(*names):
+ """Wrap a function/method to cache its result for call into request.environ
+
+ :param list[string] names: keys to cache into environ, the len(names) must
+ be equal to the result's length or scalar
+ :return:
+ """
+ def decorator(fn):
+ def function_wrapper(self, request):
+ scalar = len(names) == 1
+ try:
+ rs = [request.environ[cached_key] for cached_key in names]
+ except KeyError:
+ rs = fn(self, request)
+ if scalar:
+ rs = [rs, ]
+ request.environ.update(zip(names, rs))
+ return rs[0] if scalar else rs
+ return function_wrapper
+
+ return decorator
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/zmi/__init__.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,20 @@
+#
+# 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 interfaces
+
+# import packages
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/zmi/configure.zcml Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,6 @@
+<configure
+ xmlns="http://pylonshq.com/pyramid">
+
+ <include package="pyramid_zcml" />
+
+</configure>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/zmi/timezone.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,53 @@
+#
+# 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 interfaces
+from pyams_skin.layer import IPyAMSLayer
+from pyams_utils.interfaces.timezone import IServerTimezone
+
+# import packages
+from pyams_form.form import AJAXEditForm
+from pyams_pagelet.pagelet import pagelet_config
+from pyams_zmi.form import AdminDialogEditForm
+from pyramid.view import view_config
+from z3c.form import field
+
+from pyams_utils import _
+
+
+@pagelet_config(name='properties.html', context=IServerTimezone, layer=IPyAMSLayer,
+ permission='system.view')
+class ServerTimezonePropertiesEditForm(AdminDialogEditForm):
+ """Server timezone properties edit form"""
+
+ legend = _("Update server timezone properties")
+ fields = field.Fields(IServerTimezone)
+ ajax_handler = 'properties.json'
+
+ @property
+ def title(self):
+ return self.context.__name__
+
+ def updateWidgets(self, prefix=None):
+ super(ServerTimezonePropertiesEditForm, self).updateWidgets()
+ self.widgets['timezone'].addClass('select2')
+
+
+@view_config(name='properties.json', context=IServerTimezone, request_type=IPyAMSLayer,
+ permission='system.manage', renderer='json', xhr=True)
+class ServerTimezonePropertiesAJAXEditForm(AJAXEditForm, ServerTimezonePropertiesEditForm):
+ """Server timezone properties edit form, AJAX renderer"""
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/zodb.py Thu Feb 19 00:46:48 2015 +0100
@@ -0,0 +1,55 @@
+#
+# 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 interfaces
+from persistent.interfaces import IPersistent
+from transaction.interfaces import ITransactionManager
+from ZODB.interfaces import IConnection
+
+# import packages
+from pyams_utils.adapter import adapter_config
+
+
+@adapter_config(context=IPersistent, provides=IConnection)
+def get_connection(obj):
+ """An adapter which gets a ZODB connection of a persistent object.
+
+ We are assuming the object has a parent if it has been created in
+ this transaction.
+
+ Raises ValueError if it is impossible to get a connection.
+ """
+ cur = obj
+ while not getattr(cur, '_p_jar', None):
+ cur = getattr(cur, '__parent__', None)
+ if cur is None:
+ return None
+ return cur._p_jar
+
+
+# IPersistent adapters copied from zc.twist package
+# also register this for adapting from IConnection
+@adapter_config(context=IPersistent, provides=ITransactionManager)
+def get_transaction_manager(obj):
+ conn = IConnection(obj) # typically this will be
+ # zope.app.keyreference.persistent.connectionOfPersistent
+ try:
+ return conn.transaction_manager
+ except AttributeError:
+ return conn._txn_mgr
+ # or else we give up; who knows. transaction_manager is the more
+ # recent spelling.