# HG changeset patch # User Thierry Florac # Date 1426868923 -3600 # Node ID a44a73ee12f91f4c2d0e50d8836049c536d4c0e0 # Parent c62b53e70d9d3e23a62c2588f0494116f80306e0 Added I18n fields, properties and widgets diff -r c62b53e70d9d -r a44a73ee12f9 .installed.cfg --- a/.installed.cfg Wed Mar 11 11:58:56 2015 +0100 +++ b/.installed.cfg Fri Mar 20 17:28:43 2015 +0100 @@ -1,26 +1,35 @@ [buildout] -installed_develop_eggs = /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/develop-eggs/pyams-i18n.egg-link -parts = package i18n pyflakes pyflakesrun test +installed_develop_eggs = /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/develop-eggs/pyams-file.egg-link + /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/develop-eggs/pyams-i18n.egg-link + /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/develop-eggs/pyams-utils.egg-link +parts = package i18n pyflakes test [package] -__buildout_installed__ = /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin/pserve +__buildout_installed__ = /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin/pyams_upgrade + /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin/proutes + /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin/pshell + /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin/pviews + /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin/pdistreport /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin/pcreate - /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin/proutes - /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin/pviews /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin/prequest - /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin/pdistreport - /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin/pshell + /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin/pserve /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin/ptweens -__buildout_signature__ = zc.recipe.egg-2.0.1-py3.4.egg setuptools-12.0.4-py3.4.egg zc.buildout-2.3.1-py3.4.egg +__buildout_signature__ = zc.recipe.egg-2.0.1-py3.4.egg setuptools-14.3-py3.4.egg zc.buildout-2.3.1-py3.4.egg _b = /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin _d = /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/develop-eggs _e = /var/local/env/pyams/eggs bin-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin develop-eggs-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/develop-eggs eggs = pyams_i18n + persistent + pyams_file + pyams_utils pyramid zope.component + zope.container zope.interface + zope.schema + zope.site eggs-directory = /var/local/env/pyams/eggs recipe = zc.recipe.egg @@ -28,7 +37,7 @@ __buildout_installed__ = /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin/pybabel /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin/pot-create /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin/polint -__buildout_signature__ = zc.recipe.egg-2.0.1-py3.4.egg setuptools-12.0.4-py3.4.egg zc.buildout-2.3.1-py3.4.egg +__buildout_signature__ = zc.recipe.egg-2.0.1-py3.4.egg setuptools-14.3-py3.4.egg zc.buildout-2.3.1-py3.4.egg _b = /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin _d = /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/develop-eggs _e = /var/local/env/pyams/eggs @@ -42,7 +51,7 @@ [pyflakes] __buildout_installed__ = /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin/pyflakes /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin/pyflakes -__buildout_signature__ = zc.recipe.egg-2.0.1-py3.4.egg setuptools-12.0.4-py3.4.egg zc.buildout-2.3.1-py3.4.egg +__buildout_signature__ = zc.recipe.egg-2.0.1-py3.4.egg setuptools-14.3-py3.4.egg zc.buildout-2.3.1-py3.4.egg _b = /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin _d = /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/develop-eggs _e = /var/local/env/pyams/eggs @@ -55,17 +64,10 @@ recipe = zc.recipe.egg scripts = pyflakes -[pyflakesrun] -__buildout_installed__ = -__buildout_signature__ = collective.recipe.cmd-0.9-py3.4.egg zc.buildout-2.3.1-py3.4.egg setuptools-12.0.4-py3.4.egg -cmds = ./bin/pyflakes -on_install = true -recipe = collective.recipe.cmd - [test] __buildout_installed__ = /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/parts/test /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin/test -__buildout_signature__ = zc.recipe.testrunner-2.0.0-py3.4.egg zc.recipe.egg-2.0.1-py3.4.egg setuptools-12.0.4-py3.4.egg zope.testrunner-4.4.6-py3.4.egg zc.buildout-2.3.1-py3.4.egg zope.interface-4.1.2-py3.4-linux-x86_64.egg zope.exceptions-4.0.7-py3.4.egg six-45a2be65d681713a598787ec39be3290 +__buildout_signature__ = zc.recipe.testrunner-2.0.0-py3.4.egg zc.recipe.egg-2.0.1-py3.4.egg setuptools-14.3-py3.4.egg zope.testrunner-4.4.6-py3.4.egg zc.buildout-2.3.1-py3.4.egg zope.interface-4.1.2-py3.4-linux-x86_64.egg zope.exceptions-4.0.7-py3.4.egg six-1482e89f68d85eea27f4ed7749df2819 _b = /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin _d = /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/develop-eggs _e = /var/local/env/pyams/eggs @@ -76,3 +78,21 @@ location = /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/parts/test recipe = zc.recipe.testrunner script = /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin/test + +[buildout] +installed_develop_eggs = /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/develop-eggs/pyams-file.egg-link + /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/develop-eggs/lingua.egg-link + /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/develop-eggs/pyams-i18n.egg-link + /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/develop-eggs/pyams-utils.egg-link + +[buildout] +parts = i18n pyflakes test package + +[buildout] +parts = pyflakes test package i18n + +[buildout] +parts = test package i18n pyflakes + +[buildout] +parts = package i18n pyflakes test diff -r c62b53e70d9d -r a44a73ee12f9 buildout.cfg --- a/buildout.cfg Wed Mar 11 11:58:56 2015 +0100 +++ b/buildout.cfg Fri Mar 20 17:28:43 2015 +0100 @@ -18,12 +18,13 @@ src = src develop = . - + /var/local/src/pyams/ext/lingua + ../pyams_file + ../pyams_utils parts = package i18n pyflakes - pyflakesrun test [package] @@ -31,6 +32,7 @@ eggs = pyams_i18n persistent + pyams_file pyams_utils pyramid zope.component diff -r c62b53e70d9d -r a44a73ee12f9 setup.py --- a/setup.py Wed Mar 11 11:58:56 2015 +0100 +++ b/setup.py Fri Mar 20 17:28:43 2015 +0100 @@ -60,6 +60,7 @@ 'setuptools', # -*- Extra requirements: -*- 'persistent', + 'pyams_file', 'pyams_utils', 'pyramid', 'zope.component', diff -r c62b53e70d9d -r a44a73ee12f9 src/pyams_i18n.egg-info/SOURCES.txt --- a/src/pyams_i18n.egg-info/SOURCES.txt Wed Mar 11 11:58:56 2015 +0100 +++ b/src/pyams_i18n.egg-info/SOURCES.txt Fri Mar 20 17:28:43 2015 +0100 @@ -3,10 +3,14 @@ docs/HISTORY.txt docs/README.txt src/pyams_i18n/__init__.py +src/pyams_i18n/attr.py src/pyams_i18n/configure.zcml src/pyams_i18n/language.py src/pyams_i18n/negotiator.py +src/pyams_i18n/property.py +src/pyams_i18n/schema.py src/pyams_i18n/site.py +src/pyams_i18n/vocabulary.py src/pyams_i18n.egg-info/PKG-INFO src/pyams_i18n.egg-info/SOURCES.txt src/pyams_i18n.egg-info/dependency_links.txt @@ -16,6 +20,8 @@ src/pyams_i18n.egg-info/requires.txt src/pyams_i18n.egg-info/top_level.txt src/pyams_i18n/interfaces/__init__.py +src/pyams_i18n/interfaces/schema.py +src/pyams_i18n/interfaces/widget.py src/pyams_i18n/locales/pyams_i18n.pot src/pyams_i18n/locales/fr/LC_MESSAGES/pyams_i18n.mo src/pyams_i18n/locales/fr/LC_MESSAGES/pyams_i18n.po @@ -275,6 +281,8 @@ src/pyams_i18n/resources/img/flags/zh_SG.png src/pyams_i18n/resources/img/flags/zh_TW.png src/pyams_i18n/resources/img/flags/zu_ZA.png +src/pyams_i18n/widget/__init__.py +src/pyams_i18n/widget/templates/i18n-input.pt src/pyams_i18n/zmi/__init__.py src/pyams_i18n/zmi/configure.zcml src/pyams_i18n/zmi/negotiator.py \ No newline at end of file diff -r c62b53e70d9d -r a44a73ee12f9 src/pyams_i18n.egg-info/requires.txt --- a/src/pyams_i18n.egg-info/requires.txt Wed Mar 11 11:58:56 2015 +0100 +++ b/src/pyams_i18n.egg-info/requires.txt Fri Mar 20 17:28:43 2015 +0100 @@ -1,5 +1,6 @@ setuptools persistent +pyams_file pyams_utils pyramid zope.component diff -r c62b53e70d9d -r a44a73ee12f9 src/pyams_i18n/attr.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_i18n/attr.py Fri Mar 20 17:28:43 2015 +0100 @@ -0,0 +1,36 @@ +# +# Copyright (c) 2008-2015 Thierry Florac +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# + +__docformat__ = 'restructuredtext' + + +# import standard library + +# import interfaces +from zope.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='i18n', context=Interface, provides=ITraversable) +class I18nAttributeTraverser(ContextAdapter): + """++i18n++attr:lang namespace traverser""" + + def traverse(self, name, furtherpath=None): + try: + attr, lang = name.split(':') + return getattr(self.context, attr, {}).get(lang) + except AttributeError: + raise NotFound diff -r c62b53e70d9d -r a44a73ee12f9 src/pyams_i18n/interfaces/__init__.py --- a/src/pyams_i18n/interfaces/__init__.py Wed Mar 11 11:58:56 2015 +0100 +++ b/src/pyams_i18n/interfaces/__init__.py Fri Mar 20 17:28:43 2015 +0100 @@ -19,7 +19,7 @@ from zope.interface import Interface, invariant, Invalid # import packages -from zope.schema import Choice, Set, Bool +from zope.schema import Choice, Set, Bool, List from pyams_i18n import _ @@ -55,8 +55,8 @@ "user select languages which are offered in " "a skin."""), value_type=Choice(vocabulary='PyAMS base languages'), - default={'en', }, - required=False) + default={'en'}, + required=True) cache_enabled = Bool(title=_("Language caching enabled"), description=_("Language caching enabled (per request)"), @@ -77,6 +77,18 @@ """Clear cached language value""" +class II18nManager(Interface): + """Context languages manager + + This interface is used to handle contents providing several languages + """ + + languages = List(title=_("Content languages"), + description=_("List of languages available for this content"), + required=True, + value_type=Choice(vocabulary='PyAMS offered languages')) + + class IUserPreferredLanguage(Interface): """This interface provides language negotiation based on user preferences""" diff -r c62b53e70d9d -r a44a73ee12f9 src/pyams_i18n/interfaces/schema.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_i18n/interfaces/schema.py Fri Mar 20 17:28:43 2015 +0100 @@ -0,0 +1,45 @@ +# +# Copyright (c) 2008-2015 Thierry Florac +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# + +__docformat__ = 'restructuredtext' + + +# import standard library + +# import interfaces +from zope.schema.interfaces import IDict + +# import packages + + +class II18nField(IDict): + """I18n field marker interface""" + + +class II18nTextLineField(II18nField): + """I18n text line field marker interface""" + + +class II18nTextField(II18nField): + """I18n text field marker interface""" + + +class II18nFileField(II18nField): + """I18n file field marker interface""" + + +class II18nImageField(II18nFileField): + """I18n image field marker interface""" + + +class II18nThumbnailImageField(II18nImageField): + """I18n image field with thumbnail marker interface""" diff -r c62b53e70d9d -r a44a73ee12f9 src/pyams_i18n/interfaces/widget.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_i18n/interfaces/widget.py Fri Mar 20 17:28:43 2015 +0100 @@ -0,0 +1,37 @@ +# +# Copyright (c) 2008-2015 Thierry Florac +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# + +__docformat__ = 'restructuredtext' + + +# import standard library + +# import interfaces +from z3c.form.interfaces import IWidget + +# import packages + + +class II18nWidget(IWidget): + """I18n base widget interface""" + + +class II18nTextLineWidget(II18nWidget): + """I18n text line widget interface""" + + +class II18nTextWidget(II18nWidget): + """I18n text widget interface""" + + +class II18nFileWidget(II18nWidget): + """I18n file widget interface""" diff -r c62b53e70d9d -r a44a73ee12f9 src/pyams_i18n/locales/fr/LC_MESSAGES/pyams_i18n.mo Binary file src/pyams_i18n/locales/fr/LC_MESSAGES/pyams_i18n.mo has changed diff -r c62b53e70d9d -r a44a73ee12f9 src/pyams_i18n/locales/fr/LC_MESSAGES/pyams_i18n.po --- a/src/pyams_i18n/locales/fr/LC_MESSAGES/pyams_i18n.po Wed Mar 11 11:58:56 2015 +0100 +++ b/src/pyams_i18n/locales/fr/LC_MESSAGES/pyams_i18n.po Fri Mar 20 17:28:43 2015 +0100 @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE 1.0\n" -"POT-Creation-Date: 2015-02-03 21:41+0100\n" +"POT-Creation-Date: 2015-03-20 17:26+0100\n" "PO-Revision-Date: 2015-02-03 21:43+0100\n" "Last-Translator: Thierry Florac \n" "Language-Team: French\n" @@ -1540,7 +1540,11 @@ msgid "Zulu (South Africa)" msgstr "Zoulou (Afrique du Sud)" -#: src/pyams_i18n/zmi/negotiator.py:37 +#: src/pyams_i18n/vocabulary.py:43 src/pyams_i18n/vocabulary.py:59 +msgid "" +msgstr "" + +#: src/pyams_i18n/zmi/negotiator.py:40 msgid "Update languages negotiator properties" msgstr "Mise à jour des propriétés du gestionnaire de langues" @@ -1580,6 +1584,14 @@ msgid "Language caching enabled (per request)" msgstr "Un cache de langue peut être activé pour chaque requête..." +#: src/pyams_i18n/interfaces/__init__.py:86 +msgid "Content languages" +msgstr "Langues proposées" + +#: src/pyams_i18n/interfaces/__init__.py:87 +msgid "List of languages available for this content" +msgstr "Liste des langues disponibles pour la traduction des contenus" + #: src/pyams_i18n/interfaces/__init__.py:68 msgid "Unsupported language policy" msgstr "Cette politique de sélection de langue n'est pas supportée..." diff -r c62b53e70d9d -r a44a73ee12f9 src/pyams_i18n/locales/pyams_i18n.pot --- a/src/pyams_i18n/locales/pyams_i18n.pot Wed Mar 11 11:58:56 2015 +0100 +++ b/src/pyams_i18n/locales/pyams_i18n.pot Fri Mar 20 17:28:43 2015 +0100 @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE 1.0\n" -"POT-Creation-Date: 2015-02-03 21:41+0100\n" +"POT-Creation-Date: 2015-03-20 17:26+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" @@ -14,7 +14,7 @@ "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Lingua 3.8\n" +"Generated-By: Lingua 3.10.dev0\n" #: ./src/pyams_i18n/language.py:28 msgid "Afar" @@ -1540,7 +1540,11 @@ msgid "Zulu (South Africa)" msgstr "" -#: ./src/pyams_i18n/zmi/negotiator.py:37 +#: ./src/pyams_i18n/vocabulary.py:43 ./src/pyams_i18n/vocabulary.py:59 +msgid "" +msgstr "" + +#: ./src/pyams_i18n/zmi/negotiator.py:40 msgid "Update languages negotiator properties" msgstr "" @@ -1578,6 +1582,14 @@ msgid "Language caching enabled (per request)" msgstr "" +#: ./src/pyams_i18n/interfaces/__init__.py:86 +msgid "Content languages" +msgstr "" + +#: ./src/pyams_i18n/interfaces/__init__.py:87 +msgid "List of languages available for this content" +msgstr "" + #: ./src/pyams_i18n/interfaces/__init__.py:68 msgid "Unsupported language policy" msgstr "" diff -r c62b53e70d9d -r a44a73ee12f9 src/pyams_i18n/property.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_i18n/property.py Fri Mar 20 17:28:43 2015 +0100 @@ -0,0 +1,120 @@ +# +# Copyright (c) 2008-2015 Thierry Florac +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# + +__docformat__ = 'restructuredtext' + + +# import standard library + +# import interfaces +from pyams_file.interfaces import DELETED_FILE, IFile, IFileInfo, IFileFieldContainer +from zope.annotation.interfaces import IAnnotations +from zope.schema.interfaces import IField + +# import packages +from pyams_file.file import FileFactory +from pyams_file.property import FILE_CONTAINER_ATTRIBUTES +from pyramid.threadlocal import get_current_registry +from z3c.form.interfaces import NOT_CHANGED +from zope.interface import alsoProvides +from zope.lifecycleevent import ObjectCreatedEvent, ObjectRemovedEvent, ObjectAddedEvent +from zope.location import locate + + +_marker = object() + + +class I18nFileProperty(object): + """I18n property class used to handle files""" + + def __init__(self, field, name=None, klass=None, **args): + if not IField.providedBy(field): + raise ValueError("Provided field must implement IField interface...") + if name is None: + name = field.__name__ + self.__field = field + self.__name = name + self.__klass = klass + self.__args = args + + def __get__(self, instance, klass): + if instance is None: + return self + value = instance.__dict__.get(self.__name, _marker) + if value is _marker: + field = self.__field.bind(instance) + value = getattr(field, 'default', _marker) + if value is _marker: + raise AttributeError(self.__name) + return value + + def __set__(self, instance, value): + registry = get_current_registry() + for lang in value: + lang_value = value[lang] + if (lang_value is DELETED_FILE) or (lang_value is NOT_CHANGED): + continue + elif lang_value is not None: + filename = None + # file upload data converter returns a tuple containing + # filename and buffered IO stream extracted from FieldStorage... + if isinstance(lang_value, tuple): + filename, lang_value = lang_value + # initialize file through factory + if not IFile.providedBy(lang_value): + factory = self.__klass or FileFactory + file = factory(lang_value, **self.__args) + registry.notify(ObjectCreatedEvent(file)) + if not file.get_size(): + lang_value.seek(0) # because factory may read until end of file... + file.data = lang_value + lang_value = file + if filename is not None: + info = IFileInfo(lang_value) + if info is not None: + info.filename = filename + value[lang] = lang_value + field = self.__field.bind(instance) + field.validate(value) + if field.readonly and instance.__dict__.has_key(self.__name): + raise ValueError(self.__name, "Field is readonly") + old_value = instance.__dict__.get(self.__name, _marker) + if old_value != value: + # check for previous value + if old_value is _marker: + old_value = {} + for lang in value: + new_lang_value = value.get(lang) + if new_lang_value is NOT_CHANGED: + continue + old_lang_value = old_value.get(lang, _marker) + if (old_lang_value is not _marker) and (old_lang_value is not None): + registry.notify(ObjectRemovedEvent(old_lang_value)) + if new_lang_value is DELETED_FILE: + if self.__name in instance.__dict__: + del old_value[lang] + else: + # set name of new value + name = '++i18n++{0}:{1}'.format(self.__name, lang) + if new_lang_value is not None: + locate(new_lang_value, instance, name) + old_value[lang] = new_lang_value + # store file attributes of instance + if not IFileFieldContainer.providedBy(instance): + alsoProvides(instance, IFileFieldContainer) + annotations = IAnnotations(instance) + attributes = annotations.get(FILE_CONTAINER_ATTRIBUTES) + if attributes is None: + attributes = annotations[FILE_CONTAINER_ATTRIBUTES] = set() + attributes.add('{0}::{1}'.format(self.__name, lang)) + registry.notify(ObjectAddedEvent(new_lang_value, instance, name)) + instance.__dict__[self.__name] = old_value diff -r c62b53e70d9d -r a44a73ee12f9 src/pyams_i18n/schema.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_i18n/schema.py Fri Mar 20 17:28:43 2015 +0100 @@ -0,0 +1,136 @@ +# +# Copyright (c) 2008-2015 Thierry Florac +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# + +__docformat__ = 'restructuredtext' + + +# import standard library + +# import interfaces +from pyams_i18n.interfaces.schema import II18nField, II18nTextLineField, II18nTextField, II18nFileField, II18nImageField, \ + II18nThumbnailImageField +from zope.schema.interfaces import RequiredMissing + +# import packages +from persistent.mapping import PersistentMapping +from pyams_file.schema import FileField, ImageField, ThumbnailImageField +from zope.interface import implementer +from zope.schema import Dict, TextLine, Text + + +_marker = object() + + +class DefaultValueDict(PersistentMapping): + """Persistent mapping with default value""" + + def __init__(self, default=None, *args, **kwargs): + super(DefaultValueDict, self).__init__(*args, **kwargs) + self._default = default + + def __missing__(self, key): + return self._default + + def get(self, key, default=None): + result = super(DefaultValueDict, self).get(key, _marker) + if result is _marker: + if default is not None: + return default + else: + return self._default + else: + return result + + def copy(self): + return DefaultValueDict(default=self._default, **self) + + +@implementer(II18nField) +class I18nField(Dict): + """I18n base field class""" + + def __init__(self, key_type=None, value_type=None, **kwargs): + # DefaultValueDict.__init__(self, default) + Dict.__init__(self, key_type=TextLine(), value_type=value_type, **kwargs) + + def _validate(self, value): + super(I18nField, self)._validate(value) + if self.required: + if self.default: + return + if not value: + raise RequiredMissing + for lang in value.values(): + if lang: + return + raise RequiredMissing + + +@implementer(II18nTextLineField) +class I18nTextLineField(I18nField): + """I18n text line field""" + + def __init__(self, key_type=None, value_type=None, default=None, + value_constraint=None, value_min_length=0, value_max_length=None, **kwargs): + super(I18nTextLineField, self).__init__(value_type=TextLine(constraint=value_constraint, + min_length=value_min_length, + max_length=value_max_length, + default=default, + required=False), + **kwargs) + + +@implementer(II18nTextField) +class I18nTextField(I18nField): + """I18n text field""" + + def __init__(self, key_type=None, value_type=None, default=None, + value_constraint=None, value_min_length=0, value_max_length=None, **kwargs): + super(I18nTextField, self).__init__(value_type=Text(constraint=value_constraint, + min_length=value_min_length, + max_length=value_max_length, + default=default, + required=False), + **kwargs) + + +@implementer(II18nFileField) +class I18nFileField(I18nField): + """I18n file field""" + + def __init__(self, key_type=None, value_type=None, value_min_length=None, value_max_length=None, **kwargs): + super(I18nFileField, self).__init__(value_type=FileField(min_length=value_min_length, + max_length=value_max_length, + required=False), + **kwargs) + + +@implementer(II18nImageField) +class I18nImageField(I18nField): + """I18n image field""" + + def __init__(self, key_type=None, value_type=None, value_min_length=None, value_max_length=None, **kwargs): + super(I18nImageField, self).__init__(value_type=ImageField(min_length=value_min_length, + max_length=value_max_length, + required=False), + **kwargs) + + +@implementer(II18nThumbnailImageField) +class I18nThumbnailImageField(I18nField): + """I18n thumbnail image field""" + + def __init__(self, key_type=None, value_type=None, value_min_length=None, value_max_length=None, **kwargs): + super(I18nThumbnailImageField, self).__init__(value_type=ThumbnailImageField(min_length=value_min_length, + max_length=value_max_length, + required=False), + **kwargs) diff -r c62b53e70d9d -r a44a73ee12f9 src/pyams_i18n/vocabulary.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_i18n/vocabulary.py Fri Mar 20 17:28:43 2015 +0100 @@ -0,0 +1,62 @@ +# +# Copyright (c) 2008-2015 Thierry Florac +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +from pyams_utils.traversing import get_parent + +__docformat__ = 'restructuredtext' + + +# import standard library + +# import interfaces +from pyams_i18n.interfaces import INegotiator, II18nManager +from zope.schema.interfaces import IVocabularyFactory + +# import packages +from pyams_i18n.language import BASE_LANGUAGES +from pyams_utils.registry import query_utility +from pyams_utils.request import check_request +from zope.interface import provider +from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm, getVocabularyRegistry + +from pyams_i18n import _ + + +@provider(IVocabularyFactory) +class I18nOfferedLanguages(SimpleVocabulary): + """I18n offered languages vocabulary""" + + def __init__(self, context): + terms = [] + negotiator = query_utility(INegotiator) + if negotiator is not None: + translate = check_request().localizer.translate + for lang in negotiator.offered_languages: + terms.append(SimpleTerm(lang, title=translate(BASE_LANGUAGES.get(lang) or _("")))) + super(I18nOfferedLanguages, self).__init__(terms) + +getVocabularyRegistry().register('PyAMS offered languages', I18nOfferedLanguages) + + +@provider(IVocabularyFactory) +class I18nContentLanguages(SimpleVocabulary): + """I18n content languages vocabulary""" + + def __init__(self, context): + terms = [] + manager = get_parent(context, II18nManager) + if manager is not None: + translate = check_request().localizer.translate + for lang in manager.languages: + terms.append(SimpleTerm(lang, title=translate(BASE_LANGUAGES.get(lang) or _("")))) + super(I18nContentLanguages, self).__init__(terms) + +getVocabularyRegistry().register('PyAMS content languages', I18nContentLanguages) diff -r c62b53e70d9d -r a44a73ee12f9 src/pyams_i18n/widget/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_i18n/widget/__init__.py Fri Mar 20 17:28:43 2015 +0100 @@ -0,0 +1,152 @@ +# +# Copyright (c) 2008-2015 Thierry Florac +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +from pyramid.decorator import reify + +__docformat__ = 'restructuredtext' + + +# import standard library + +# import interfaces +from pyams_form.interfaces.form import IFormLayer +from pyams_i18n.interfaces import II18nManager, INegotiator +from pyams_i18n.interfaces.schema import II18nField, II18nTextLineField, II18nTextField, II18nFileField +from pyams_i18n.interfaces.widget import II18nWidget, II18nTextLineWidget, II18nTextWidget, II18nFileWidget +from z3c.form.interfaces import IDataConverter, IFieldWidget, NO_VALUE + +# import packages +from pyams_form.widget import widgettemplate_config +from pyams_utils.adapter import adapter_config +from pyams_utils.registry import query_utility +from pyams_utils.traversing import get_parent +from z3c.form.browser.widget import HTMLInputWidget +from z3c.form.converter import BaseDataConverter +from z3c.form.util import expandPrefix +from z3c.form.widget import Widget, FieldWidget +from zope.interface import implementer_only + + +@adapter_config(context=(II18nField, II18nWidget), provides=IDataConverter) +class I18nDataConverter(BaseDataConverter): + """I18n base data converter""" + + def __init__(self, field, widget): + super(I18nDataConverter, self).__init__(field, widget) + + def toWidgetValue(self, value): + # I18n widget is using a dict where each key is a lang + # and each value is matching widget value + result = {} + registry = self.widget.request.registry + for lang, val in value.items(): + converter = registry.queryMultiAdapter((self.field.value_type, self.widget.get_widget(lang)), + IDataConverter) + if converter is not None: + result[lang] = converter.toWidgetValue(val) + return result + + def toFieldValue(self, value): + # I18n widget is using a dict where each key is a lang + # and each value is matching widget value + result = {} + registry = self.widget.request.registry + for lang in self.widget.langs: + converter = registry.queryMultiAdapter((self.field.value_type, self.widget.get_widget(lang)), + IDataConverter) + if converter is not None: + result[lang] = converter.toFieldValue(self.widget.get_value(lang)) + return result + + +@widgettemplate_config(mode='input', template='templates/i18n-input.pt', widget=II18nWidget, layer=IFormLayer) +@widgettemplate_config(mode='display', template='templates/i18n-input.pt', widget=II18nWidget, layer=IFormLayer) +@implementer_only(II18nWidget) +class I18nWidget(HTMLInputWidget, Widget): + """I18n base widget""" + + @reify + def langs(self): + langs = [] + negotiator = query_utility(INegotiator) + if negotiator is not None: + langs.append(negotiator.server_language) + manager = get_parent(self.context, II18nManager) + if manager is not None: + langs.extend(sorted(filter(lambda x: x not in langs, manager.languages))) + elif negotiator is not None: + langs.extend(sorted(filter(lambda x: x not in langs, negotiator.offered_languages))) + else: + langs.append('en') + return langs + + def update(self): + self.widgets = {} + for lang in self.langs: + widget = self.request.registry.queryMultiAdapter((self.field.value_type, self.request), IFieldWidget) + if widget is not None: + prefix = expandPrefix(self.form.prefix) + expandPrefix(self.form.widgets.prefix) + expandPrefix(lang) + name = self.field.value_type.__name__ = self.field.__name__ + widget.name = prefix + name + widget.id = widget.name.replace('.', '-') + widget.form = self.form + widget.field = self.field.value_type + widget.context = self.context + widget.ignoreContext = self.ignoreContext + widget.ignoreRequest = self.ignoreRequest + widget.lang = lang + widget.update() + self.widgets[lang] = widget + super(I18nWidget, self).update() + + def extract(self, default=NO_VALUE): + result = {} + [result.setdefault(lang, self.widgets[lang].extract(default)) for lang in self.widgets.keys()] + return result + + def get_widget(self, lang): + return self.widgets.get(lang) + + def get_value(self, lang): + return self.get_widget(lang).value + + +@implementer_only(II18nTextLineWidget) +class I18nTextLineWidget(I18nWidget): + """I18n text line widget""" + + +@adapter_config(context=(II18nTextLineField, IFormLayer), provides=IFieldWidget) +def I18nTextLineFieldWidget(field, request): + """I18n text line field widget factory""" + return FieldWidget(field, I18nTextLineWidget(request)) + + +@implementer_only(II18nTextWidget) +class I18nTextWidget(I18nWidget): + """I18n text widget""" + + +@adapter_config(context=(II18nTextField, IFormLayer), provides=IFieldWidget) +def I18nTextFieldWidget(field, request): + """I18n text field widget factory""" + return FieldWidget(field, I18nTextWidget(request)) + + +@implementer_only(II18nFileWidget) +class I18nFileWidget(I18nWidget): + """I18n file widget""" + + +@adapter_config(context=(II18nFileField, IFormLayer), provides=IFieldWidget) +def I18nFileFieldWidget(field, request): + """I18n file field widget factory""" + return FieldWidget(field, I18nFileWidget(request)) diff -r c62b53e70d9d -r a44a73ee12f9 src/pyams_i18n/widget/templates/i18n-input.pt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_i18n/widget/templates/i18n-input.pt Fri Mar 20 17:28:43 2015 +0100 @@ -0,0 +1,32 @@ + + +
+ +
+
+ + +
+ +
+ +
+
+
+
+