# HG changeset patch # User Thierry Florac # Date 1424339746 -3600 # Node ID 7a0b409fd4b8ef270e3e69f466e57bd86da5aec2 First commit diff -r 000000000000 -r 7a0b409fd4b8 .hgignore --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.hgignore Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,19 @@ + +syntax: regexp +^develop-eggs$ +syntax: regexp +^parts$ +syntax: regexp +^bin$ +syntax: regexp +^\.installed\.cfg$ +syntax: regexp +^\.settings$ +syntax: regexp +^build$ +syntax: regexp +^dist$ +syntax: regexp +^\.idea$ +syntax: regexp +.*\.pyc$ diff -r 000000000000 -r 7a0b409fd4b8 .installed.cfg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.installed.cfg Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,73 @@ +[buildout] +installed_develop_eggs = /home/tflorac/Dropbox/src/PyAMS/pyams_form/develop-eggs/pyams-utils.egg-link +parts = package i18n pyflakes test + +[package] +__buildout_installed__ = /home/tflorac/Dropbox/src/PyAMS/pyams_form/bin/pcreate + /home/tflorac/Dropbox/src/PyAMS/pyams_form/bin/ptweens + /home/tflorac/Dropbox/src/PyAMS/pyams_form/bin/prequest + /home/tflorac/Dropbox/src/PyAMS/pyams_form/bin/pdistreport + /home/tflorac/Dropbox/src/PyAMS/pyams_form/bin/pserve + /home/tflorac/Dropbox/src/PyAMS/pyams_form/bin/pshell + /home/tflorac/Dropbox/src/PyAMS/pyams_form/bin/proutes + /home/tflorac/Dropbox/src/PyAMS/pyams_form/bin/pviews +__buildout_signature__ = zc.recipe.egg-2.0.1-py3.4.egg setuptools-12.0.5-py3.4.egg zc.buildout-2.3.1-py3.4.egg +_b = /home/tflorac/Dropbox/src/PyAMS/pyams_form/bin +_d = /home/tflorac/Dropbox/src/PyAMS/pyams_form/develop-eggs +_e = /var/local/env/pyams/eggs +bin-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_form/bin +develop-eggs-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_form/develop-eggs +eggs = pyams_form + pyams_skin + pyramid + z3c.form + zope.component + zope.interface +eggs-directory = /var/local/env/pyams/eggs +recipe = zc.recipe.egg + +[i18n] +__buildout_installed__ = /home/tflorac/Dropbox/src/PyAMS/pyams_form/bin/pybabel + /home/tflorac/Dropbox/src/PyAMS/pyams_form/bin/pot-create + /home/tflorac/Dropbox/src/PyAMS/pyams_form/bin/polint +__buildout_signature__ = zc.recipe.egg-2.0.1-py3.4.egg setuptools-12.0.5-py3.4.egg zc.buildout-2.3.1-py3.4.egg +_b = /home/tflorac/Dropbox/src/PyAMS/pyams_form/bin +_d = /home/tflorac/Dropbox/src/PyAMS/pyams_form/develop-eggs +_e = /var/local/env/pyams/eggs +bin-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_form/bin +develop-eggs-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_form/develop-eggs +eggs = babel + lingua +eggs-directory = /var/local/env/pyams/eggs +recipe = zc.recipe.egg + +[pyflakes] +__buildout_installed__ = /home/tflorac/Dropbox/src/PyAMS/pyams_form/bin/pyflakes + /home/tflorac/Dropbox/src/PyAMS/pyams_form/bin/pyflakes +__buildout_signature__ = zc.recipe.egg-2.0.1-py3.4.egg setuptools-12.0.5-py3.4.egg zc.buildout-2.3.1-py3.4.egg +_b = /home/tflorac/Dropbox/src/PyAMS/pyams_form/bin +_d = /home/tflorac/Dropbox/src/PyAMS/pyams_form/develop-eggs +_e = /var/local/env/pyams/eggs +bin-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_form/bin +develop-eggs-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_form/develop-eggs +eggs = pyflakes +eggs-directory = /var/local/env/pyams/eggs +entry-points = pyflakes=pyflakes.scripts.pyflakes:main +initialization = if not sys.argv[1:]: sys.argv[1:] = ["src"] +recipe = zc.recipe.egg +scripts = pyflakes + +[test] +__buildout_installed__ = /home/tflorac/Dropbox/src/PyAMS/pyams_form/parts/test + /home/tflorac/Dropbox/src/PyAMS/pyams_form/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.5-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_form/bin +_d = /home/tflorac/Dropbox/src/PyAMS/pyams_form/develop-eggs +_e = /var/local/env/pyams/eggs +bin-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_form/bin +develop-eggs-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_form/develop-eggs +eggs = pyams_form [test] +eggs-directory = /var/local/env/pyams/eggs +location = /home/tflorac/Dropbox/src/PyAMS/pyams_form/parts/test +recipe = zc.recipe.testrunner +script = /home/tflorac/Dropbox/src/PyAMS/pyams_form/bin/test diff -r 000000000000 -r 7a0b409fd4b8 LICENSE --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/LICENSE Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,42 @@ +Zope Public License (ZPL) Version 2.1 +===================================== + +A copyright notice accompanies this license document that identifies +the copyright holders. + +This license has been certified as open source. It has also been designated +as GPL compatible by the Free Software Foundation (FSF). + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions in source code must retain the accompanying copyright + notice, this list of conditions, and the following disclaimer. + 2. Redistributions in binary form must reproduce the accompanying copyright + notice, this list of conditions, and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Names of the copyright holders must not be used to endorse or promote + products derived from this software without prior written permission + from the copyright holders. + 4. The right to distribute this software or to use it for any purpose does + not give you the right to use Servicemarks (sm) or Trademarks (tm) of the + copyright holders. Use of them is covered by separate agreement with the + copyright holders. + 5. If any files are modified, you must cause the modified files to carry + prominent notices stating that you changed the files and the date of any + change. + + +Disclaimer +========== + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff -r 000000000000 -r 7a0b409fd4b8 MANIFEST.in --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MANIFEST.in Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,5 @@ +include *.txt +recursive-include docs * +recursive-include src * +global-exclude *.pyc +global-exclude *.*~ diff -r 000000000000 -r 7a0b409fd4b8 bootstrap.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bootstrap.py Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,178 @@ +############################################################################## +# +# Copyright (c) 2006 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Bootstrap a buildout-based project + +Simply run this script in a directory containing a buildout.cfg. +The script accepts buildout command-line options, so you can +use the -c option to specify an alternate configuration file. +""" + +import os +import shutil +import sys +import tempfile + +from optparse import OptionParser + +tmpeggs = tempfile.mkdtemp() + +usage = '''\ +[DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options] + +Bootstraps a buildout-based project. + +Simply run this script in a directory containing a buildout.cfg, using the +Python that you want bin/buildout to use. + +Note that by using --find-links to point to local resources, you can keep +this script from going over the network. +''' + +parser = OptionParser(usage=usage) +parser.add_option("-v", "--version", help="use a specific zc.buildout version") + +parser.add_option("-t", "--accept-buildout-test-releases", + dest='accept_buildout_test_releases', + action="store_true", default=False, + help=("Normally, if you do not specify a --version, the " + "bootstrap script and buildout gets the newest " + "*final* versions of zc.buildout and its recipes and " + "extensions for you. If you use this flag, " + "bootstrap and buildout will get the newest releases " + "even if they are alphas or betas.")) +parser.add_option("-c", "--config-file", + help=("Specify the path to the buildout configuration " + "file to be used.")) +parser.add_option("-f", "--find-links", + help=("Specify a URL to search for buildout releases")) +parser.add_option("--allow-site-packages", + action="store_true", default=False, + help=("Let bootstrap.py use existing site packages")) + + +options, args = parser.parse_args() + +###################################################################### +# load/install setuptools + +try: + if options.allow_site_packages: + import setuptools + import pkg_resources + from urllib.request import urlopen +except ImportError: + from urllib2 import urlopen + +ez = {} +exec(urlopen('https://bootstrap.pypa.io/ez_setup.py').read(), ez) + +if not options.allow_site_packages: + # ez_setup imports site, which adds site packages + # this will remove them from the path to ensure that incompatible versions + # of setuptools are not in the path + import site + # inside a virtualenv, there is no 'getsitepackages'. + # We can't remove these reliably + if hasattr(site, 'getsitepackages'): + for sitepackage_path in site.getsitepackages(): + sys.path[:] = [x for x in sys.path if sitepackage_path not in x] + +setup_args = dict(to_dir=tmpeggs, download_delay=0) +ez['use_setuptools'](**setup_args) +import setuptools +import pkg_resources + +# This does not (always?) update the default working set. We will +# do it. +for path in sys.path: + if path not in pkg_resources.working_set.entries: + pkg_resources.working_set.add_entry(path) + +###################################################################### +# Install buildout + +ws = pkg_resources.working_set + +cmd = [sys.executable, '-c', + 'from setuptools.command.easy_install import main; main()', + '-mZqNxd', tmpeggs] + +find_links = os.environ.get( + 'bootstrap-testing-find-links', + options.find_links or + ('http://downloads.buildout.org/' + if options.accept_buildout_test_releases else None) + ) +if find_links: + cmd.extend(['-f', find_links]) + +setuptools_path = ws.find( + pkg_resources.Requirement.parse('setuptools')).location + +requirement = 'zc.buildout' +version = options.version +if version is None and not options.accept_buildout_test_releases: + # Figure out the most recent final version of zc.buildout. + import setuptools.package_index + _final_parts = '*final-', '*final' + + def _final_version(parsed_version): + for part in parsed_version: + if (part[:1] == '*') and (part not in _final_parts): + return False + return True + index = setuptools.package_index.PackageIndex( + search_path=[setuptools_path]) + if find_links: + index.add_find_links((find_links,)) + req = pkg_resources.Requirement.parse(requirement) + if index.obtain(req) is not None: + best = [] + bestv = None + for dist in index[req.project_name]: + distv = dist.parsed_version + if _final_version(distv): + if bestv is None or distv > bestv: + best = [dist] + bestv = distv + elif distv == bestv: + best.append(dist) + if best: + best.sort() + version = best[-1].version +if version: + requirement = '=='.join((requirement, version)) +cmd.append(requirement) + +import subprocess +if subprocess.call(cmd, env=dict(os.environ, PYTHONPATH=setuptools_path)) != 0: + raise Exception( + "Failed to execute command:\n%s" % repr(cmd)[1:-1]) + +###################################################################### +# Import and run buildout + +ws.add_entry(tmpeggs) +ws.require(requirement) +import zc.buildout.buildout + +if not [a for a in args if '=' not in a]: + args.append('bootstrap') + +# if -c was provided, we push it back into args for buildout' main function +if options.config_file is not None: + args[0:0] = ['-c', options.config_file] + +zc.buildout.buildout.main(args) +shutil.rmtree(tmpeggs) diff -r 000000000000 -r 7a0b409fd4b8 buildout.cfg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/buildout.cfg Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,63 @@ +[buildout] +eggs-directory = /var/local/env/pyams/eggs + +socket-timeout = 3 +show-picked-versions = true +newest = false + +allow-hosts = + bitbucket.org + *.python.org + *.sourceforge.net + github.com + +#extends = http://download.ztfy.org/webapp/ztfy.webapp.dev.cfg +versions = versions +newest = false +#allow-picked-versions = false + +src = src +develop = . + ../pyams_skin + ../pyams_utils + +parts = + package + i18n + pyflakes + test + +[package] +recipe = zc.recipe.egg +eggs = + pyams_form + pyams_skin + pyramid + z3c.form + zope.component + zope.interface + +[i18n] +recipe = zc.recipe.egg +eggs = + babel + lingua + +[pyflakes] +recipe = zc.recipe.egg +eggs = pyflakes +scripts = pyflakes +entry-points = pyflakes=pyflakes.scripts.pyflakes:main +initialization = if not sys.argv[1:]: sys.argv[1:] = ["${buildout:src}"] + +[pyflakesrun] +recipe = collective.recipe.cmd +on_install = true +cmds = ${buildout:develop}/bin/${pyflakes:scripts} + +[test] +recipe = zc.recipe.testrunner +eggs = pyams_form [test] + +[versions] +pyams_base = 0.1.0 diff -r 000000000000 -r 7a0b409fd4b8 docs/HISTORY.txt diff -r 000000000000 -r 7a0b409fd4b8 docs/README.txt diff -r 000000000000 -r 7a0b409fd4b8 setup.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/setup.py Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,69 @@ +# +# Copyright (c) 2008-2015 Thierry Florac +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# + +""" +This module contains pyams_ package +""" +import os +from setuptools import setup, find_packages + +DOCS = os.path.join(os.path.dirname(__file__), + 'docs') + +README = os.path.join(DOCS, 'README.txt') +HISTORY = os.path.join(DOCS, 'HISTORY.txt') + +version = '0.1.0' +long_description = open(README).read() + '\n\n' + open(HISTORY).read() + +tests_require = [] + +setup(name='pyams_form', + version=version, + description="PyAMS base form interfaces and classes", + long_description=long_description, + classifiers=[ + "License :: OSI Approved :: Zope Public License", + "Development Status :: 4 - Beta", + "Programming Language :: Python", + "Framework :: Pyramid", + "Topic :: Software Development :: Libraries :: Python Modules", + ], + keywords='Pyramid PyAMS form', + author='Thierry Florac', + author_email='tflorac@ulthar.net', + url='http://hg.ztfy.org/pyams/pyams_form', + license='ZPL', + packages=find_packages('src'), + package_dir={'': 'src'}, + namespace_packages=[], + include_package_data=True, + package_data={'': ['*.zcml', '*.txt', '*.pt', '*.pot', '*.po', '*.mo', '*.png', '*.gif', '*.jpeg', '*.jpg', '*.css', '*.js']}, + zip_safe=False, + # uncomment this to be able to run tests with setup.py + test_suite="pyams_form.tests.test_utilsdocs.test_suite", + tests_require=tests_require, + extras_require=dict(test=tests_require), + install_requires=[ + 'setuptools', + # -*- Extra requirements: -*- + 'pyams_skin', + 'pyramid', + 'pyramid_zope_request', + 'z3c.form', + 'zope.component', + 'zope.interface', + ], + entry_points=""" + # -*- Entry points: -*- + """, + ) diff -r 000000000000 -r 7a0b409fd4b8 src/pyams_form.egg-info/PKG-INFO --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_form.egg-info/PKG-INFO Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,18 @@ +Metadata-Version: 1.1 +Name: pyams-form +Version: 0.1.0 +Summary: PyAMS base form interfaces and classes +Home-page: http://hg.ztfy.org/pyams/pyams_form +Author: Thierry Florac +Author-email: tflorac@ulthar.net +License: ZPL +Description: + + +Keywords: Pyramid PyAMS form +Platform: UNKNOWN +Classifier: License :: OSI Approved :: Zope Public License +Classifier: Development Status :: 4 - Beta +Classifier: Programming Language :: Python +Classifier: Framework :: Pyramid +Classifier: Topic :: Software Development :: Libraries :: Python Modules diff -r 000000000000 -r 7a0b409fd4b8 src/pyams_form.egg-info/SOURCES.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_form.egg-info/SOURCES.txt Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,44 @@ +MANIFEST.in +setup.py +docs/HISTORY.txt +docs/README.txt +src/pyams_form/__init__.py +src/pyams_form/configure.zcml +src/pyams_form/form.py +src/pyams_form/group.py +src/pyams_form/schema.py +src/pyams_form/terms.py +src/pyams_form/viewlet.py +src/pyams_form.egg-info/PKG-INFO +src/pyams_form.egg-info/SOURCES.txt +src/pyams_form.egg-info/dependency_links.txt +src/pyams_form.egg-info/entry_points.txt +src/pyams_form.egg-info/namespace_packages.txt +src/pyams_form.egg-info/not-zip-safe +src/pyams_form.egg-info/requires.txt +src/pyams_form.egg-info/top_level.txt +src/pyams_form/doctests/README.txt +src/pyams_form/interfaces/__init__.py +src/pyams_form/interfaces/form.py +src/pyams_form/locales/pyams_form.pot +src/pyams_form/locales/fr/LC_MESSAGES/pyams_form.mo +src/pyams_form/locales/fr/LC_MESSAGES/pyams_form.po +src/pyams_form/templates/form.pt +src/pyams_form/templates/inner-form.pt +src/pyams_form/templates/widget-form.pt +src/pyams_form/tests/__init__.py +src/pyams_form/tests/test_utilsdocs.py +src/pyams_form/tests/test_utilsdocstrings.py +src/pyams_form/widget/__init__.py +src/pyams_form/widget/configure.zcml +src/pyams_form/widget/templates/button-display.pt +src/pyams_form/widget/templates/button-input.pt +src/pyams_form/widget/templates/close-display.pt +src/pyams_form/widget/templates/close-input.pt +src/pyams_form/widget/templates/radio-input.pt +src/pyams_form/widget/templates/reset-display.pt +src/pyams_form/widget/templates/reset-input.pt +src/pyams_form/widget/templates/select-input.pt +src/pyams_form/widget/templates/submit-display.pt +src/pyams_form/widget/templates/submit-input.pt +src/pyams_form/widget/templates/text-display.pt \ No newline at end of file diff -r 000000000000 -r 7a0b409fd4b8 src/pyams_form.egg-info/dependency_links.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_form.egg-info/dependency_links.txt Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,1 @@ + diff -r 000000000000 -r 7a0b409fd4b8 src/pyams_form.egg-info/entry_points.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_form.egg-info/entry_points.txt Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,3 @@ + + # -*- Entry points: -*- + \ No newline at end of file diff -r 000000000000 -r 7a0b409fd4b8 src/pyams_form.egg-info/namespace_packages.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_form.egg-info/namespace_packages.txt Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,1 @@ + diff -r 000000000000 -r 7a0b409fd4b8 src/pyams_form.egg-info/not-zip-safe --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_form.egg-info/not-zip-safe Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,1 @@ + diff -r 000000000000 -r 7a0b409fd4b8 src/pyams_form.egg-info/requires.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_form.egg-info/requires.txt Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,9 @@ +setuptools +pyams_skin +pyramid +pyramid_zope_request +z3c.form +zope.component +zope.interface + +[test] diff -r 000000000000 -r 7a0b409fd4b8 src/pyams_form.egg-info/top_level.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_form.egg-info/top_level.txt Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,1 @@ +pyams_form diff -r 000000000000 -r 7a0b409fd4b8 src/pyams_form/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_form/__init__.py Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,32 @@ +# +# Copyright (c) 2008-2015 Thierry Florac +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# + +__docformat__ = 'restructuredtext' + + +# import standard library + +# import interfaces + +# import packages + +from pyramid.i18n import TranslationStringFactory +_ = TranslationStringFactory('pyams_form') + + +def includeme(config): + """Pyramid include + + Split in another package to remove cyclic dependencies with TranslationStringFactory + """ + from .include import include_package + include_package(config) diff -r 000000000000 -r 7a0b409fd4b8 src/pyams_form/configure.zcml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_form/configure.zcml Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + diff -r 000000000000 -r 7a0b409fd4b8 src/pyams_form/doctests/README.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_form/doctests/README.txt Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,3 @@ +================== +pyams_ package +================== diff -r 000000000000 -r 7a0b409fd4b8 src/pyams_form/form.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_form/form.py Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,411 @@ +# +# 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 json + +# import interfaces +from pyams_form.interfaces.form import IFormLayer, IForm, IAJAXForm, IInnerSubForm, IInnerTabForm, \ + ICustomUpdateSubForm, IFormCreatedEvent, FormCreatedEvent +from pyams_form.interfaces.form import IAddFormButtons, IModalAddFormButtons, IEditFormButtons, \ + IModalEditFormButtons, IModalDisplayFormButtons +from pyams_form.interfaces.form import FormObjectCreatedEvent, FormObjectModifiedEvent +from pyams_skin.interfaces import IDialog, ISkinnable +from pyams_template.interfaces import IContentTemplate, ILayoutTemplate +from pyramid_chameleon.interfaces import IChameleonTranslate +from z3c.form.interfaces import DISPLAY_MODE, IErrorViewSnippet +from zope.publisher.interfaces.browser import IBrowserRequest + +# import packages +from pyams_form.group import GroupsBasedForm +from pyams_skin.skin import apply_skin +from pyramid.decorator import reify +from pyramid.events import subscriber +from pyramid.response import Response +from pyramid.url import resource_url +from pyramid_zope_request import PyramidToPublisher, PyramidPublisherRequest +from z3c.form.button import Buttons +from z3c.form.form import applyChanges, \ + Form, AddForm as BaseAddForm, EditForm as BaseEditForm, DisplayForm as BaseDisplayForm +from zope.component import queryUtility +from zope.interface import implementer, alsoProvides +from zope.lifecycleevent import Attributes, ObjectCreatedEvent +from zope.schema.fieldproperty import FieldProperty + +from pyams_form import _ + + +def get_form_weight(form): + """Try to get form weight attribute""" + try: + return form.weight + except AttributeError: + return 0 + + +REDIRECT_STATUS_CODES = (300, 301, 302, 303, 304, 305, 307) + + +@PyramidToPublisher(IBrowserRequest) +@implementer(IForm, ISkinnable) +class BaseForm(GroupsBasedForm, Form): + """Base form class""" + + layout = None + layer = IFormLayer + + edit_permission = FieldProperty(IForm['edit_permission']) + + title = FieldProperty(IForm['title']) + legend = FieldProperty(IForm['legend']) + css_class = FieldProperty(IForm['css_class']) + icon_css_class = FieldProperty(IForm['icon_css_class']) + autocomplete = FieldProperty(IForm['autocomplete']) + _warn_on_change = FieldProperty(IForm['warn_on_change']) + + label_css_class = FieldProperty(IForm['label_css_class']) + input_css_class = FieldProperty(IForm['input_css_class']) + + display_hints_on_widgets = FieldProperty(IForm['display_hints_on_widgets']) + handle_upload = FieldProperty(IForm['handle_upload']) + callbacks = FieldProperty(IForm['callbacks']) + + subforms_legend = FieldProperty(IForm['subforms_legend']) + + def __init__(self, context, request): + GroupsBasedForm.__init__(self) + Form.__init__(self, context, request) + for req in (request, request._request): + alsoProvides(req, self.layer) + request.registry.notify(FormCreatedEvent(self)) + + def update(self): + if self.edit_permission and not self.request.has_permission(self.edit_permission, self.getContent()): + self.mode = DISPLAY_MODE + Form.update(self) + [subform.update() for subform in self.subforms] + [tabform.update() for tabform in self.tabforms] + + def get_form_action(self): + return self.action + + @reify + def subforms(self): + registry = self.request.registry + return sorted((adapter[1] for adapter in registry.getAdapters((self,), IInnerSubForm)), + key=get_form_weight) + + @reify + def tabforms(self): + registry = self.request.registry + return sorted((adapter[1] for adapter in registry.getAdapters((self,), IInnerTabForm)), + key=get_form_weight) + + @property + def forms(self): + return [self, ] + self.subforms + self.tabforms + + @reify + def warn_on_change(self): + if self._warn_on_change is True: + return 'true' + elif self._warn_on_change is False: + return 'false' + else: + return None + + @property + def is_dialog(self): + return IDialog.providedBy(self) + + def get_widget_callback(self, widget): + return (self.callbacks or {}).get(widget) + + # Default z3c.form methods + + @property + def errors(self): + result = [] + for form in self.forms: + result.extend(form.widgets.errors) + return result + + def update_content(self, content, data): + changes = applyChanges(self, content, data) + for subform in self.subforms + self.tabforms: + if ICustomUpdateSubForm.providedBy(subform): + updates = ICustomUpdateSubForm(subform).update_content(content, data) + if isinstance(updates, dict): + changes.update(updates) + else: + changes.update(applyChanges(subform, content, data)) + return changes + + def render(self): + request = self.request + if isinstance(request, PyramidPublisherRequest): + request = request._request + cdict = {'context': self.context, + 'request': request, + 'view': self, + 'translate': queryUtility(IChameleonTranslate)} + if self.template is None: + registry = request.registry + template = registry.queryMultiAdapter((self, request, self.context), IContentTemplate) + if template is None: + template = registry.getMultiAdapter((self, request), IContentTemplate) + return template(**cdict) + return self.template(**cdict) + + def __call__(self, **kwargs): + self.update() + if self.request.response.status_code in REDIRECT_STATUS_CODES: + return Response('') + + request = self.request + if isinstance(request, PyramidPublisherRequest): + request = request._request + cdict = {'context': self.context, + 'request': request, + 'view': self, + 'translate': queryUtility(IChameleonTranslate)} + cdict.update(kwargs) + if self.layout is None: + registry = request.registry + layout = registry.queryMultiAdapter((self, request, self.context), + ILayoutTemplate) + if layout is None: + layout = registry.getMultiAdapter((self, request), ILayoutTemplate) + return Response(layout(**cdict)) + return Response(self.layout(**cdict)) + + def get_skin(self, request=None): + return request.annotations.get('__skin__') + + +@implementer(IAJAXForm) +class AJAXForm(BaseForm): + """AJAX form base class""" + + ajax_handler = FieldProperty(IAJAXForm['ajax_handler']) + form_options = FieldProperty(IAJAXForm['form_options']) + form_target = FieldProperty(IAJAXForm['form_target']) + ajax_callback = FieldProperty(IAJAXForm['ajax_callback']) + + def get_form_action(self): + return resource_url(self.context, self.request, self.request.view_name) + + def get_form_options(self): + return json.dumps(self.form_options) if self.form_options else None + + def get_ajax_handler(self): + return resource_url(self.context, self.request, self.ajax_handler) + + def get_ajax_errors(self): + """Extract form errors in AJAX format""" + translate = self.request.localizer.translate + errors = {'status': u'error', + 'error_message': translate(self.status)} + registry = self.request.registry + for error in self.errors: + if isinstance(error, Exception): + error = registry.getMultiAdapter((error, self.request, None, None, self, self.request), + IErrorViewSnippet) + + error.update() + if hasattr(error, 'widget'): + widget = error.widget + if widget is not None: + errors.setdefault('widgets', []).append({'name': widget.name, + 'label': translate(widget.label), + 'message': translate(error.message)}) + else: + errors.setdefault('messages', []).append({'message': translate(error.message)}) + else: + errors.setdefault('messages', []).append(translate(error.message)) + return errors + + def get_ajax_output(self, changes): + """Extract AJAX POST output""" + raise NotImplementedError + + +# +# Add forms +# + +class AddForm(AJAXForm, BaseAddForm): + """Add form base class""" + + buttons = Buttons(IAddFormButtons) + + legend = _("Add form") + formErrorsMessage = _("There were some errors.") + + def updateActions(self): + BaseAddForm.updateActions(self) + if 'add' in self.actions: + self.actions['add'].addClass('btn-primary') + + def createAndAdd(self, data): + registry = self.request.registry + object = self.create(data) + registry.notify(ObjectCreatedEvent(object)) + self.update_content(object, data) + self.add(object) + registry.notify(FormObjectCreatedEvent(object, self)) + return object + + +class AJAXAddForm(AddForm): + """AJAX add form""" + + def __call__(self): + self.updateWidgets() + data, errors = self.extractData() + if errors or self.errors: + return self.get_ajax_errors() + result = self.createAndAdd(data) + return self.get_ajax_output(result) + + def get_ajax_output(self, changes): + return {'status': 'reload', + 'location': self.nextURL()} + + +@implementer(IDialog) +class DialogAddForm(AddForm): + """Modal dialog add form""" + + buttons = Buttons(IModalAddFormButtons) + dialog_class = 'modal-medium' + + +# +# Edit forms +# + +class EditForm(AJAXForm, BaseEditForm): + """Edit form base class""" + + buttons = Buttons(IEditFormButtons) + + legend = _("Edit form") + formErrorsMessage = _("There were some errors.") + successMessage = _("Data successfully updated.") + noChangesMessage = _("No changes were applied.") + + def updateActions(self): + BaseEditForm.updateActions(self) + if 'submit' in self.actions: + self.actions['submit'].addClass('btn-primary') + + def applyChanges(self, data): + content = self.getContent() + changes = self.update_content(content, data) + if changes: + descriptions = [] + for interface, names in changes.items(): + descriptions.append(Attributes(interface, *names)) + self.request.registry.notify(FormObjectModifiedEvent(content, self, *descriptions)) + return changes + + +class AJAXEditForm(EditForm): + """AJAX edit form""" + + def __call__(self): + self.updateWidgets() + data, errors = self.extractData() + if errors or self.errors: + return self.get_ajax_errors() + changes = self.applyChanges(data) + return self.get_ajax_output(changes) + + def get_ajax_output(self, changes): + if changes: + status = 'success' + message = self.successMessage + else: + status = 'info' + message = self.noChangesMessage + translate = self.request.localizer.translate + return {'status': status, + 'message': translate(message)} + + +@implementer(IDialog) +class DialogEditForm(EditForm): + """Modal dialog edit form""" + + buttons = Buttons(IModalEditFormButtons) + dialog_class = 'modal-medium' + + +# +# Display forms +# + +class DisplayForm(BaseForm, BaseDisplayForm): + """Display form base class""" + + +@implementer(IDialog) +class DialogDisplayForm(DisplayForm): + """Modal dialog display form""" + + buttons = Buttons(IModalDisplayFormButtons) + dialog_class = 'modal-medium' + + +# +# Form events subscribers +# + +@subscriber(IFormCreatedEvent, context_selector=ISkinnable) +def handle_form_skin(event): + request = event.object.request + if isinstance(request, PyramidPublisherRequest): + request = request._request + skin = ISkinnable(event.object).get_skin(request) + if skin is not None: + apply_skin(request, skin) + + +class FormSelector(object): + """Form event selector + + This selector can be used by subscriber to filter form events + """ + + def __init__(self, ifaces, config): + if not isinstance(ifaces, (list, tuple)): + ifaces = (ifaces,) + self.interfaces = ifaces + + def text(self): + return 'form_selector = %s' % str(self.interfaces) + + phash = text + + def __call__(self, event): + for intf in self.interfaces: + try: + if intf.providedBy(event.form): + return True + except (AttributeError, TypeError): + if isinstance(event.form, intf): + return True + return False diff -r 000000000000 -r 7a0b409fd4b8 src/pyams_form/group.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_form/group.py Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,147 @@ +# +# 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_form.interfaces.form import IFormWidgetsGroup, IGroupsBasedForm + +# import packages +from pyramid.decorator import reify +from zope.interface import implementer +from zope.schema.fieldproperty import FieldProperty + + +@implementer(IFormWidgetsGroup) +class FormWidgetsGroup(object): + """Form widgets group""" + + id = FieldProperty(IFormWidgetsGroup['id']) + widgets = FieldProperty(IFormWidgetsGroup['widgets']) + legend = FieldProperty(IFormWidgetsGroup['legend']) + help = FieldProperty(IFormWidgetsGroup['help']) + _css_class = FieldProperty(IFormWidgetsGroup['css_class']) + switch = FieldProperty(IFormWidgetsGroup['switch']) + checkbox_switch = FieldProperty(IFormWidgetsGroup['checkbox_switch']) + checkbox_field = FieldProperty(IFormWidgetsGroup['checkbox_field']) + checkbox_widget = FieldProperty(IFormWidgetsGroup['checkbox_widget']) + hide_if_empty = FieldProperty(IFormWidgetsGroup['hide_if_empty']) + + def __init__(self, id, widgets=None, legend=None, help=None, css_class='', switch=False, + checkbox_switch=False, checkbox_field=None, hide_if_empty=False): + assert (not checkbox_switch) or checkbox_field, "You must define checkbox field when using checkbox switch" + self.id = id + self.widgets = widgets or [] + self.legend = id if legend is None else legend + self.help = help + self._css_class = css_class + self.switch = switch + self.checkbox_switch = checkbox_switch + self.checkbox_field = checkbox_field + self.hide_if_empty = hide_if_empty + + @property + def css_class(self): + css_class = self._css_class + if self.switch: + if self.checkbox_switch: + css_class += ' checker' + else: + css_class += ' switcher' + return css_class + + @property + def checkbox_widget(self): + if self.checkbox_field is None: + return None + for widget in self.widgets: + if widget.field is self.checkbox_field.field: + return widget + + @reify + def visible(self): + if self.checkbox_switch: + widget = self.checkbox_widget + context = widget.context + name = widget.field.getName() + value = getattr(context, name, None) + return bool(value) + else: + if not (self.switch and self.hide_if_empty): + return True + for widget in self.widgets: + if not widget.ignoreContext: + field = widget.field + context = widget.context + name = field.getName() + value = getattr(context, name, None) + if value and (value != field.default): + return True + return False + + @property + def visible_widgets(self): + for widget in self.widgets: + if (self.checkbox_field is None) or (widget.field is not self.checkbox_field.field): + yield widget + + @property + def switchable(self): + return self.switch or self.checkbox_switch + + @property + def switcher_state(self): + return 'on' if self.visible else 'off' + + @property + def checker_state(self): + return 'on' if self.visible else 'off' + + +def NamedWidgetsGroup(id, widgets, names=(), legend=None, help=None, css_class='', switch=False, + checkbox_switch=False, checkbox_field=None, hide_if_empty=False): + """Create a widgets group based on widgets names""" + return FormWidgetsGroup(id, [widgets.get(name) for name in names], legend, help, css_class, switch, + checkbox_switch, checkbox_field, hide_if_empty) + + +@implementer(IGroupsBasedForm) +class GroupsBasedForm(object): + """Groups based form + + Should be used as a base class for forms also implementing IForm + """ + + def __init__(self): + self._groups = [] + + def add_group(self, group): + self._groups.append(group) + + @property + def groups(self): + result = self._groups[:] + others = [] + for widget in self.widgets.values(): + found = False + for group in result: + if widget in group.widgets: + found = True + break + if not found: + others.append(widget) + if others: + result.insert(0, FormWidgetsGroup(None, others)) + return result diff -r 000000000000 -r 7a0b409fd4b8 src/pyams_form/include.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_form/include.py Thu Feb 19 10:55:46 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 + +# import packages +from pyams_form.form import FormSelector + + +def include_package(config): + """Pyramid include""" + + # add translations + config.add_translation_dirs('pyams_form:locales') + + # custom subscriber predicate + config.add_subscriber_predicate('form_selector', FormSelector) + + # load registry components + config.scan() + + if hasattr(config, 'load_zcml'): + config.load_zcml('configure.zcml') diff -r 000000000000 -r 7a0b409fd4b8 src/pyams_form/interfaces/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_form/interfaces/__init__.py Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,20 @@ +# +# 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 .form import IForm, IFormLayer + +# import packages diff -r 000000000000 -r 7a0b409fd4b8 src/pyams_form/interfaces/form.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_form/interfaces/form.py Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,425 @@ +# +# 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_template.template import template_config + +__docformat__ = 'restructuredtext' + +# import standard library + +# import interfaces +from pyams_viewlet.interfaces import IViewletManager +from pyramid.interfaces import IView +from z3c.form.interfaces import INPUT_MODE, ISubForm, IWidget, IFormLayer as IBaseFormLayer, ISubmitWidget +from zope.interface.interfaces import IObjectEvent, ObjectEvent +from zope.lifecycleevent.interfaces import IObjectCreatedEvent, IObjectModifiedEvent + +# import packages +from pyams_form.schema import ResetButton, CloseButton +from z3c.form import button +from zope.interface import implementer, Interface, Attribute +from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent +from zope.schema import TextLine, List, Object, Bool, Choice, Dict + +from pyams_form import _ + + +# +# Form interfaces +# + +class IFormLayer(IBaseFormLayer): + """Base PyAMS form layer""" + + +class IBaseForm(IView): + """Base form interface""" + + +@template_config(template='../templates/form.pt', layer=IFormLayer) +class IForm(IBaseForm): + """Default form interface""" + + skin = TextLine(title="Skin name", + default="PyAMS default skin", + required=False) + + edit_permission = TextLine(title="Required edit permission", + required=False, + default='manage') + + def get_skin(self): + """Get skin associated with this form""" + + title = TextLine(title="Form title") + + legend = TextLine(title="Form legend", + required=False) + + css_class = TextLine(title="CSS class", + default='ams-form form-horizontal') + + icon_css_class = TextLine(title="Legend CSS class", + required=False) + + autocomplete = Choice(title="Auto-complete", + values=('on', 'off'), + default='on') + + warn_on_change = Choice(title="Warn on unsaved change?", + values=('default', True, False), + default='default') + + label_css_class = TextLine(title="Labels CSS class", + required=False, + default='control-label col-md-3') + + input_css_class = TextLine(title="Inputs CSS class", + required=False, + default='col-md-9') + + display_hints_on_widgets = Bool(title="Display hints on input widgets?", + required=True, + default=False) + + handle_upload = Bool(title="Handle uploads in form?", + description="Set to true when form handle uploads to get progress bar", + required=True, + default=False) + + callbacks = Dict(title="Widgets validation callbacks", + key_type=TextLine(), + value_type=TextLine(), + required=False) + + subforms = List(title="Sub-forms", + value_type=Object(schema=ISubForm), + required=False, + readonly=True) + + subforms_legend = TextLine(title="Sub-forms legend", + required=False) + + tabforms = List(title="Tab-forms", + value_type=Object(schema=ISubForm), + required=False, + readonly=True) + + is_dialog = Attribute("Check to know if current form is in a modal dialog") + + def get_form_action(self): + """Get form action URL""" + + def get_widget_callback(self, widget): + """Get submit callback associated with a given widget""" + + def update_content(self, object, data): + """Update given object with form data""" + + def get_submit_output(self, writer, changes): + """Get submit output""" + + +class IAJAXForm(Interface): + """AJAX form attributes""" + + ajax_handler = TextLine(title="AJAX handler", + description="Name of a JSON view handling AJAX requests", + required=False) + + form_options = Dict(title="AJAX data options", + required=False) + + form_target = TextLine(title="Form submit target", + description="Form content target for HTML and text content types", + required=False, + default='#content') + + ajax_callback = TextLine(title="AJAX submit callback", + description="Name of a custom form submit callback", + required=False) + + def get_form_options(self): + """Get form options in JSON format""" + + def get_ajax_handler(self): + """Get absolute URL of AJAX handler""" + + def get_ajax_errors(self): + """Get AJAX errors""" + + def get_ajax_output(self, changes): + """Get AJAX POST output""" + + +class IFormWidgetsGroup(Interface): + """Form widgets group interface""" + + id = TextLine(title="Group ID", + required=False) + + widgets = List(title="Group's widgets list", + value_type=Object(schema=IWidget)) + + legend = TextLine(title="Group legend", + required=False) + + help = TextLine(title="Group help", + required=False) + + css_class = TextLine(title="CSS class", + required=False, + readonly=True) + + switch = Bool(title="Switchable group?", + required=True, + default=False) + + checkbox_switch = Bool(title="Group switched via checkbox?", + required=True, + default=False) + + checkbox_field = TextLine(title="Field name matching switch checkbox?", + required=False) + + checkbox_widget = Object(schema=IWidget, + required=False, + readonly=True) + + hide_if_empty = Bool(title="Hide group if empty?", + description="""If 'Yes', a switchable group containing only """ + """widgets with default values is hidden""", + required=True, + default=False) + + visible = Attribute("Visible group?") + + visible_widgets = Attribute("Visible widgets") + + switchable = Attribute("Switchable group?") + + switcher_state = Attribute("Switcher state") + + checker_state = Attribute("Checker state") + + +class IGroupsBasedForm(IBaseForm): + """Groups based form""" + + groups = List(title="Form widgets groups", + value_type=Object(IFormWidgetsGroup)) + + def add_group(self, group): + """Add given group to form groups""" + + +class IViewletsBasedForm(IBaseForm): + """Viewlets based form""" + + providers = List(title="List of content providers names included in this form", + value_type=TextLine(), + required=True) + + +class IInnerSubForm(ISubForm): + """Inner sub-form interface""" + + +class IInnerTabForm(ISubForm): + """Inner tab-form interface""" + + +class IViewletSubForm(ISubForm): + """Inner viewlet form interface""" + + legend = Attribute("Sub-form legend") + + switchable = Attribute("Can the sub-form be hidden ?") + + visible = Attribute("Is the sub-form initially visible ?") + + callbacks = Dict(title="Widgets callbacks", + key_type=TextLine(), + value_type=TextLine()) + + def get_widget_callback(self, widget): + """Get submit callback associated with a given widget""" + + +class ICustomExtractSubForm(ISubForm): + """SubForm interface with custom extract method""" + + def extract(self): + """Custom data and errors extraction method""" + + +class ICustomUpdateSubForm(ISubForm): + """SubForm interface with custom update method""" + + def update_content(self, object, data): + """Custom content update method""" + + +class ISearchForm(Interface): + """Default search form interface""" + + def get_search_results(self): + """Get search results""" + + +# +# Form viewlets +# + +class IFormViewletsManager(IViewletManager): + """Base forms viewlets manager interface""" + + +class IFormPrefixViewletsManager(IFormViewletsManager): + """Form prefix viewlets manager interface""" + + +class IWidgetsPrefixViewletsManager(IFormViewletsManager): + """Form widgets prefix viewlets manager interface""" + + +class IWidgetsSuffixViewletsManager(IFormViewletsManager): + """Form widgets suffix viewlets manager interface""" + + +class IFormSuffixViewletsManager(IFormViewletsManager): + """Form suffix viewlets manager interface""" + + +# +# Form buttons +# + +def check_submit_button(form): + """Check form and widgets mode before displaying submit button""" + if form.mode != INPUT_MODE: + return False + for widget in form.widgets.values(): + if widget.mode == INPUT_MODE: + return True + if IForm.providedBy(form): + for subform in form.subforms: + for widget in subform.widgets.values(): + if widget.mode == INPUT_MODE: + return True + + +class IAddFormButtons(Interface): + """Default add form buttons""" + + reset = ResetButton(name='reset', title=_("Reset")) + add = button.Button(name='add', title=_("Add"), condition=check_submit_button) + + +class IModalAddFormButtons(Interface): + """Modal add form buttons""" + + close = CloseButton(name='close', title=_("Close")) + add = button.Button(name='add', title=_("Add"), condition=check_submit_button) + + +class IEditFormButtons(Interface): + """Default edit form buttons""" + + reset = ResetButton(name='reset', title=_("Reset")) + submit = button.Button(name='submit', title=_("Submit"), condition=check_submit_button) + + +class IModalEditFormButtons(Interface): + """Modal edit form buttons""" + + close = CloseButton(name='close', title=_("Close")) + submit = button.Button(name='submit', title=_("Submit"), condition=check_submit_button) + + +class IModalDisplayFormButtons(Interface): + """Modal display form buttons""" + + close = CloseButton(name='close', title=_("Close")) + + +# +# Inner form interface +# + +@template_config(template='../templates/inner-form.pt', layer=IFormLayer) +class IInnerForm(Interface): + """Inner form marker interface""" + + +@template_config(template='../templates/widget-form.pt', layer=IFormLayer) +class IWidgetForm(Interface): + """Widget form marker interface""" + + widget_icon_class = TextLine(title="Widget icon class", + default="fa fa-edit") + + +# +# Form buttons widgets +# + +class IResetWidget(ISubmitWidget): + """Reset button widget interface""" + + +class ICloseWidget(ISubmitWidget): + """Close button widget interface""" + + +# +# Form events +# + +class IFormCreatedEvent(IObjectEvent): + """Form created event interface""" + + +@implementer(IFormCreatedEvent) +class FormCreatedEvent(ObjectEvent): + """Form created event""" + + +class IViewObjectEvent(IObjectEvent): + """View object event interface""" + + view = Attribute("View in which event was fired") + + +class IFormObjectCreatedEvent(IObjectCreatedEvent, IViewObjectEvent): + """Event fired after final object creation""" + + +@implementer(IFormObjectCreatedEvent) +class FormObjectCreatedEvent(ObjectCreatedEvent): + """Form object created event""" + + def __init__(self, object, view): + self.object = object + self.view = view + + +class IFormObjectModifiedEvent(IObjectModifiedEvent, IViewObjectEvent): + """Event fired after object modification""" + + +@implementer(IFormObjectModifiedEvent) +class FormObjectModifiedEvent(ObjectModifiedEvent): + """Form object modified event""" + + def __init__(self, object, view, *descriptions): + ObjectModifiedEvent.__init__(self, object, *descriptions) + self.view = view diff -r 000000000000 -r 7a0b409fd4b8 src/pyams_form/locales/fr/LC_MESSAGES/pyams_form.mo Binary file src/pyams_form/locales/fr/LC_MESSAGES/pyams_form.mo has changed diff -r 000000000000 -r 7a0b409fd4b8 src/pyams_form/locales/fr/LC_MESSAGES/pyams_form.po --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_form/locales/fr/LC_MESSAGES/pyams_form.po Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,78 @@ +# +# French translations for PACKAGE package +# This file is distributed under the same license as the PACKAGE package. +# Thierry Florac , 2015. +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE 1.0\n" +"POT-Creation-Date: 2015-01-29 22:41+0100\n" +"PO-Revision-Date: 2015-01-29 17:17+0100\n" +"Last-Translator: Thierry Florac \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" +"Generated-By: Lingua 3.8\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: src/pyams_form/terms.py:36 +msgid "yes" +msgstr "oui" + +#: src/pyams_form/terms.py:37 +msgid "no" +msgstr "non" + +#: src/pyams_form/form.py:212 +msgid "Add form" +msgstr "" + +#: src/pyams_form/form.py:213 src/pyams_form/form.py:264 +msgid "There were some errors." +msgstr "Des erreurs se sont produites." + +#: src/pyams_form/form.py:263 +msgid "Edit form" +msgstr "" + +#: src/pyams_form/form.py:265 +msgid "Data successfully updated." +msgstr "Les modifications ont été enregistrées." + +#: src/pyams_form/form.py:266 +msgid "No changes were applied." +msgstr "Aucune modification effectuée." + +#: src/pyams_form/form.py:353 +msgid "My legend" +msgstr "Ma légende" + +#: src/pyams_form/templates/radio-input.pt:7 +msgid "Label" +msgstr "" + +#: src/pyams_form/templates/inner-form.pt:89 +msgid "legend" +msgstr "" + +#: src/pyams_form/templates/inner-form.pt:108 +msgid "Tab label" +msgstr "" + +#: src/pyams_form/interfaces/form.py:295 src/pyams_form/interfaces/form.py:309 +msgid "Reset" +msgstr "Annuler" + +#: src/pyams_form/interfaces/form.py:296 src/pyams_form/interfaces/form.py:303 +msgid "Add" +msgstr "Ajouter" + +#: src/pyams_form/interfaces/form.py:302 src/pyams_form/interfaces/form.py:316 +#: src/pyams_form/interfaces/form.py:323 +msgid "Close" +msgstr "Fermer" + +#: src/pyams_form/interfaces/form.py:310 src/pyams_form/interfaces/form.py:317 +msgid "Submit" +msgstr "Enregistrer" diff -r 000000000000 -r 7a0b409fd4b8 src/pyams_form/locales/fr/LC_MESSAGES/pyams_form.po~ --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_form/locales/fr/LC_MESSAGES/pyams_form.po~ Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,74 @@ +# +# French translations for PACKAGE package +# This file is distributed under the same license as the PACKAGE package. +# Thierry Florac , 2015. +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE 1.0\n" +"POT-Creation-Date: 2015-01-29 17:29+0100\n" +"PO-Revision-Date: 2015-01-29 17:17+0100\n" +"Last-Translator: Thierry Florac \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" +"Generated-By: Lingua 3.8\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: src/pyams_form/terms.py:36 +msgid "yes" +msgstr "oui" + +#: src/pyams_form/terms.py:37 +msgid "no" +msgstr "non" + +#: src/pyams_form/form.py:205 +msgid "Add form" +msgstr "" + +#: src/pyams_form/form.py:206 src/pyams_form/form.py:257 +msgid "There were some errors." +msgstr "" + +#: src/pyams_form/form.py:256 +msgid "Edit form" +msgstr "" + +#: src/pyams_form/form.py:258 +msgid "Data successfully updated." +msgstr "" + +#: src/pyams_form/form.py:259 +msgid "No changes were applied." +msgstr "" + +#: src/pyams_form/form.py:340 +msgid "My legend" +msgstr "Ma légende" + +#: src/pyams_form/templates/inner-form.pt:88 +msgid "legend" +msgstr "" + +#: src/pyams_form/templates/inner-form.pt:107 +msgid "Tab label" +msgstr "" + +#: src/pyams_form/interfaces/form.py:292 src/pyams_form/interfaces/form.py:306 +msgid "Reset" +msgstr "" + +#: src/pyams_form/interfaces/form.py:293 src/pyams_form/interfaces/form.py:300 +msgid "Add" +msgstr "Ajouter" + +#: src/pyams_form/interfaces/form.py:299 src/pyams_form/interfaces/form.py:313 +#: src/pyams_form/interfaces/form.py:320 +msgid "Close" +msgstr "" + +#: src/pyams_form/interfaces/form.py:307 src/pyams_form/interfaces/form.py:314 +msgid "Submit" +msgstr "" diff -r 000000000000 -r 7a0b409fd4b8 src/pyams_form/locales/pyams_form.pot --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_form/locales/pyams_form.pot Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,82 @@ +# +# SOME DESCRIPTIVE TITLE +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , 2015. +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE 1.0\n" +"POT-Creation-Date: 2015-01-29 22:41+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \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_form/terms.py:36 +msgid "yes" +msgstr "" + +#: ./src/pyams_form/terms.py:37 +msgid "no" +msgstr "" + +#: ./src/pyams_form/form.py:212 +msgid "Add form" +msgstr "" + +#: ./src/pyams_form/form.py:213 ./src/pyams_form/form.py:264 +msgid "There were some errors." +msgstr "" + +#: ./src/pyams_form/form.py:263 +msgid "Edit form" +msgstr "" + +#: ./src/pyams_form/form.py:265 +msgid "Data successfully updated." +msgstr "" + +#: ./src/pyams_form/form.py:266 +msgid "No changes were applied." +msgstr "" + +#: ./src/pyams_form/form.py:353 +msgid "My legend" +msgstr "" + +#: ./src/pyams_form/templates/radio-input.pt:7 +msgid "Label" +msgstr "" + +#: ./src/pyams_form/templates/inner-form.pt:89 +msgid "legend" +msgstr "" + +#: ./src/pyams_form/templates/inner-form.pt:108 +msgid "Tab label" +msgstr "" + +#: ./src/pyams_form/interfaces/form.py:295 +#: ./src/pyams_form/interfaces/form.py:309 +msgid "Reset" +msgstr "" + +#: ./src/pyams_form/interfaces/form.py:296 +#: ./src/pyams_form/interfaces/form.py:303 +msgid "Add" +msgstr "" + +#: ./src/pyams_form/interfaces/form.py:302 +#: ./src/pyams_form/interfaces/form.py:316 +#: ./src/pyams_form/interfaces/form.py:323 +msgid "Close" +msgstr "" + +#: ./src/pyams_form/interfaces/form.py:310 +#: ./src/pyams_form/interfaces/form.py:317 +msgid "Submit" +msgstr "" diff -r 000000000000 -r 7a0b409fd4b8 src/pyams_form/schema.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_form/schema.py Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,41 @@ +# +# 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 IButton + +# import packages +from z3c.form.button import Button +from zope.interface import implementer + + +class IResetButton(IButton): + """Reset button interface""" + + +@implementer(IResetButton) +class ResetButton(Button): + """Reset button""" + + +class ICloseButton(IButton): + """Close button interface""" + + +@implementer(ICloseButton) +class CloseButton(Button): + """Close button""" diff -r 000000000000 -r 7a0b409fd4b8 src/pyams_form/search.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_form/search.py Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,106 @@ +# +# 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_form.interfaces.form import IWidgetForm, ISearchForm +from pyams_skin.interfaces import IContentSearch, IInnerPage, ISearchPage +from pyams_skin.layer import IPyAMSLayer +from z3c.table.interfaces import IValues + +# import packages +from pyams_form.form import AddForm +from pyams_form.schema import ResetButton +from pyams_skin.table import BaseTable +from pyams_template.template import template_config +from pyams_utils.adapter import ContextRequestViewAdapter, adapter_config +from pyramid.response import Response +from z3c.form import field, button +from zope.interface import implementer, Interface +from zope.schema import TextLine + +from pyams_form import _ + + +class ISearchFields(Interface): + """Default search form interface""" + + query = TextLine(title=_("Search query")) + + +class ISearchButtons(Interface): + """Search form buttons interface""" + + reset = ResetButton(name='reset', title=_("Reset")) + search = button.Button(name='search', title=_("Search")) + + +@implementer(IWidgetForm, ISearchForm) +class SearchForm(AddForm): + """Base search form""" + + legend = _("Search") + widget_icon_class = 'fa fa-fw fa-search' + + fields = field.Fields(ISearchFields) + buttons = button.Buttons(ISearchButtons) + + ajax_handler = 'search-results.html' + form_target = '#search-results' + + def updateActions(self): + super(SearchForm, self).updateActions() + if 'search' in self.actions: + self.actions['search'].addClass('btn-primary') + + def get_search_results(self): + search = IContentSearch(self.context, None) + if search is not None: + self.updateWidgets() + data, errors = self.extractData() + return search.get_search_results(data) + + +@template_config(template='templates/search.pt', layer=IPyAMSLayer) +@implementer(IInnerPage, ISearchPage) +class SearchView(object): + """Base search view""" + + search_form_factory = SearchForm + + def update(self): + self.search_form = self.search_form_factory(self.context, self.request) + self.search_form.update() + + +class SearchResultsView(BaseTable): + """Search results view""" + + search_form_factory = SearchForm + + def __call__(self): + self.update() + return Response(self.renderTable()) + + +@adapter_config(context=(Interface, IPyAMSLayer, SearchResultsView), provides=IValues) +class SearchResultsViewValuesAdapter(ContextRequestViewAdapter): + """Search results view values adapter""" + + @property + def values(self): + form = self.view.search_form_factory(self.context, self.request) + return form.get_search_results() diff -r 000000000000 -r 7a0b409fd4b8 src/pyams_form/templates/form.pt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_form/templates/form.pt Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,141 @@ + diff -r 000000000000 -r 7a0b409fd4b8 src/pyams_form/templates/inner-form.pt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_form/templates/inner-form.pt Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,125 @@ +
+
Form prefix
+
+ +
+ +
+
+
Form prefix
+
diff -r 000000000000 -r 7a0b409fd4b8 src/pyams_form/templates/search.pt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_form/templates/search.pt Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,13 @@ +
+ + +
diff -r 000000000000 -r 7a0b409fd4b8 src/pyams_form/templates/widget-form.pt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_form/templates/widget-form.pt Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,129 @@ +
+
+ + +

+ +
+
+
Form prefix
+ +
+ +
+ +
+
+
Form suffix
+
+
diff -r 000000000000 -r 7a0b409fd4b8 src/pyams_form/terms.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_form/terms.py Thu Feb 19 10:55:46 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. +# +from pyams_utils.adapter import adapter_config + +__docformat__ = 'restructuredtext' + + +# import standard library + +# import interfaces +from pyams_form.interfaces.form import IFormLayer +from z3c.form.interfaces import IWidget, IBoolTerms +from zope.schema.interfaces import IBool + +# import packages +from z3c.form.term import BoolTerms as BaseBoolTerms +from zope.interface import implementer_only, Interface + +from pyams_utils import _ + + +@adapter_config(context=(Interface, IFormLayer, Interface, IBool, IWidget), provides=IBoolTerms) +@implementer_only(IBoolTerms) +class BoolTerms(BaseBoolTerms): + """Default yes and no terms are used by default for IBool fields.""" + + trueLabel = _('yes') + falseLabel = _('no') diff -r 000000000000 -r 7a0b409fd4b8 src/pyams_form/tests/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_form/tests/__init__.py Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,1 @@ + diff -r 000000000000 -r 7a0b409fd4b8 src/pyams_form/tests/test_utilsdocs.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_form/tests/test_utilsdocs.py Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,59 @@ +# +# Copyright (c) 2008-2015 Thierry Florac +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# + +""" +Generic Test case for pyams_form doctest +""" +__docformat__ = 'restructuredtext' + +import unittest +import doctest +import sys +import os + + +current_dir = os.path.dirname(__file__) + +def doc_suite(test_dir, setUp=None, tearDown=None, globs=None): + """Returns a test suite, based on doctests found in /doctest.""" + suite = [] + if globs is None: + globs = globals() + + flags = (doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE | + doctest.REPORT_ONLY_FIRST_FAILURE) + + package_dir = os.path.split(test_dir)[0] + if package_dir not in sys.path: + sys.path.append(package_dir) + + doctest_dir = os.path.join(package_dir, 'doctests') + + # filtering files on extension + docs = [os.path.join(doctest_dir, doc) for doc in + os.listdir(doctest_dir) if doc.endswith('.txt')] + + for test in docs: + suite.append(doctest.DocFileSuite(test, optionflags=flags, + globs=globs, setUp=setUp, + tearDown=tearDown, + module_relative=False)) + + return unittest.TestSuite(suite) + +def test_suite(): + """returns the test suite""" + return doc_suite(current_dir) + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') + diff -r 000000000000 -r 7a0b409fd4b8 src/pyams_form/tests/test_utilsdocstrings.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_form/tests/test_utilsdocstrings.py Thu Feb 19 10:55:46 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. +# + +""" +Generic Test case for pyams_form 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_form.%s' % test + suite.append(doctest.DocTestSuite(location, optionflags=flags, + globs=globs)) + + return unittest.TestSuite(suite) + +def test_suite(): + """returns the test suite""" + return doc_suite(current_dir) + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') diff -r 000000000000 -r 7a0b409fd4b8 src/pyams_form/viewlet.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_form/viewlet.py Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,53 @@ +# +# 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_form.interfaces.form import IFormViewletsManager, IFormPrefixViewletsManager, IWidgetsPrefixViewletsManager, \ + IWidgetsSuffixViewletsManager, IFormSuffixViewletsManager, IFormLayer + +# import packages +from pyams_viewlet.manager import WeightOrderedViewletManager, viewletmanager_config +from zope.interface import implementer + + +@implementer(IFormViewletsManager) +class FormViewletManager(WeightOrderedViewletManager): + """Base form viewlet manager""" + + +@viewletmanager_config(name='form_prefix', layer=IFormLayer) +@implementer(IFormPrefixViewletsManager) +class FormPrefixViewletManager(FormViewletManager): + """Form prefix viewlet manager, displayed before form""" + + +@viewletmanager_config(name='widgets_prefix', layer=IFormLayer) +@implementer(IWidgetsPrefixViewletsManager) +class WidgetsPrefixViewletManager(FormViewletManager): + """Form widgets prefix display manager, displayed before widgets""" + + +@viewletmanager_config(name='widgets_suffix', layer=IFormLayer) +@implementer(IWidgetsSuffixViewletsManager) +class WidgetsSuffixViewletManager(FormViewletManager): + """Form widgets suffix viewlet manager, displayed after widgets""" + + +@viewletmanager_config(name='form_suffix', layer=IFormLayer) +@implementer(IFormSuffixViewletsManager) +class FormSuffixViewletManager(FormViewletManager): + """Form suffix viewlet manager, displayed after form""" diff -r 000000000000 -r 7a0b409fd4b8 src/pyams_form/widget/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_form/widget/__init__.py Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,197 @@ +# +# 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 inspect +import os +import venusian + +# import interfaces +from pyams_form.interfaces.form import IFormLayer, IResetWidget, ICloseWidget +from pyams_form.schema import IResetButton, ICloseButton +from pyramid.interfaces import IRequest +from z3c.form.interfaces import INPUT_MODE, IFieldWidget, IButtonAction, IWidgetLayoutTemplate +from zope.pagetemplate.interfaces import IPageTemplate + +# import packages +from pyams_utils.adapter import adapter_config +from pyramid.exceptions import ConfigurationError +from z3c.form.action import Action +from z3c.form.browser.submit import SubmitWidget +from z3c.form.button import ButtonAction +from z3c.form.widget import FieldWidget, WidgetTemplateFactory, WidgetLayoutFactory +from zope.interface import implementer_only, directlyProvides, Interface + + +# +# Widget template configuration +# + +class widgettemplate_config(object): + """Class decorator used to declare a widget template""" + + venusian = venusian # for testing injection + + def __init__(self, **settings): + if 'for_' in settings: + if settings.get('context') is None: + settings['context'] = settings['for_'] + self.__dict__.update(settings) + + def __call__(self, wrapped): + settings = self.__dict__.copy() + + def callback(context, name, ob): + template = os.path.join(os.path.dirname(inspect.getfile(inspect.getmodule(ob))), + settings.get('template')) + if not os.path.isfile(template): + raise ConfigurationError("No such file", template) + + contentType = settings.get('contentType', 'text/html') + factory = WidgetTemplateFactory(template, contentType) + provides = settings.get('provides', IPageTemplate) + directlyProvides(factory, provides) + + config = context.config.with_package(info.module) + config.registry.registerAdapter(factory, + (settings.get('context', Interface), + settings.get('layer', IRequest), + settings.get('view', None), + settings.get('field', None), + settings.get('widget', ob)), + provides, + settings.get('mode', INPUT_MODE)) + + info = self.venusian.attach(wrapped, callback, category='pyams_form') + + if info.scope == 'class': + # if the decorator was attached to a method in a class, or + # otherwise executed at class scope, we need to set an + # 'attr' into the settings if one isn't already in there + if settings.get('attr') is None: + settings['attr'] = wrapped.__name__ + + settings['_info'] = info.codeinfo # fbo "action_method" + return wrapped + + +class widgetlayout_config(object): + """Class decorator used to declare a widget layout""" + + venusian = venusian # for testing injection + + def __init__(self, **settings): + if 'for_' in settings: + if settings.get('context') is None: + settings['context'] = settings['for_'] + self.__dict__.update(settings) + + def __call__(self, wrapped): + settings = self.__dict__.copy() + + def callback(context, name, ob): + template = os.path.join(os.path.dirname(inspect.getfile(inspect.getmodule(ob))), + settings.get('template')) + if not os.path.isfile(template): + raise ConfigurationError("No such file", template) + + contentType = settings.get('contentType', 'text/html') + factory = WidgetLayoutFactory(template, contentType) + provides = settings.get('provides', IWidgetLayoutTemplate) + directlyProvides(factory, provides) + + config = context.config.with_package(info.module) + config.registry.registerAdapter(factory, + (settings.get('context', Interface), + settings.get('layer', IRequest), + settings.get('view', None), + settings.get('field', None), + settings.get('widget', ob)), + provides, + settings.get('mode', INPUT_MODE)) + + info = self.venusian.attach(wrapped, callback, category='pyams_form') + + 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 + + +# +# Reset button widget and action +# + +@widgettemplate_config(mode='input', template='templates/reset-input.pt', layer=IFormLayer) +@widgettemplate_config(mode='display', template='templates/reset-display.pt', layer=IFormLayer) +@implementer_only(IResetWidget) +class ResetWidget(SubmitWidget): + """A reset button of a form.""" + + klass = u'reset-widget' + css = u'reset' + + +@adapter_config(context=(IResetButton, IFormLayer), provides=IFieldWidget) +def ResetFieldWidget(field, request): + reset = FieldWidget(field, ResetWidget(request)) + reset.value = field.title + return reset + + +@adapter_config(context=(IFormLayer, IResetButton), provides=IButtonAction) +class ResetButtonAction(ResetWidget, ButtonAction): + """Reset button action""" + + def __init__(self, request, field): + Action.__init__(self, request, field.title) + ResetWidget.__init__(self, request) + self.field = field + + +# +# Close button widget and action +# + +@widgettemplate_config(mode='input', template='templates/close-input.pt', layer=IFormLayer) +@widgettemplate_config(mode='display', template='templates/close-display.pt', layer=IFormLayer) +@implementer_only(ICloseWidget) +class CloseWidget(SubmitWidget): + """A dialog close button""" + + klass = u'close-widget' + css = u'close' + + +@adapter_config(context=(ICloseButton, IFormLayer), provides=IFieldWidget) +def CloseFieldWidget(field, request): + close = FieldWidget(field, CloseWidget(request)) + close.value = field.title + return close + + +@adapter_config(context=(IFormLayer, ICloseButton), provides=IButtonAction) +class CloseButtonAction(CloseWidget, ButtonAction): + """Close button action""" + + def __init__(self, request, field): + Action.__init__(self, request, field.title) + CloseWidget.__init__(self, request) + self.field = field diff -r 000000000000 -r 7a0b409fd4b8 src/pyams_form/widget/configure.zcml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_form/widget/configure.zcml Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r 000000000000 -r 7a0b409fd4b8 src/pyams_form/widget/templates/button-display.pt diff -r 000000000000 -r 7a0b409fd4b8 src/pyams_form/widget/templates/button-input.pt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_form/widget/templates/button-input.pt Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,35 @@ + diff -r 000000000000 -r 7a0b409fd4b8 src/pyams_form/widget/templates/close-display.pt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_form/widget/templates/close-display.pt Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,34 @@ + diff -r 000000000000 -r 7a0b409fd4b8 src/pyams_form/widget/templates/close-input.pt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_form/widget/templates/close-input.pt Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,35 @@ + diff -r 000000000000 -r 7a0b409fd4b8 src/pyams_form/widget/templates/radio-display.pt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_form/widget/templates/radio-display.pt Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,17 @@ + diff -r 000000000000 -r 7a0b409fd4b8 src/pyams_form/widget/templates/radio-input.pt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_form/widget/templates/radio-input.pt Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,36 @@ +
+ +
diff -r 000000000000 -r 7a0b409fd4b8 src/pyams_form/widget/templates/reset-display.pt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_form/widget/templates/reset-display.pt Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,33 @@ + diff -r 000000000000 -r 7a0b409fd4b8 src/pyams_form/widget/templates/reset-input.pt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_form/widget/templates/reset-input.pt Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,34 @@ + diff -r 000000000000 -r 7a0b409fd4b8 src/pyams_form/widget/templates/select-input.pt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_form/widget/templates/select-input.pt Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,29 @@ + diff -r 000000000000 -r 7a0b409fd4b8 src/pyams_form/widget/templates/submit-display.pt diff -r 000000000000 -r 7a0b409fd4b8 src/pyams_form/widget/templates/submit-input.pt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_form/widget/templates/submit-input.pt Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,35 @@ + diff -r 000000000000 -r 7a0b409fd4b8 src/pyams_form/widget/templates/text-display.pt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_form/widget/templates/text-display.pt Thu Feb 19 10:55:46 2015 +0100 @@ -0,0 +1,17 @@ +