zope.formlib-4.3.0a2/ 0000775 0000000 0000000 00000000000 12355055056 013055 5 ustar root root zope.formlib-4.3.0a2/bootstrap.py 0000664 0000000 0000000 00000024435 12243456432 015453 0 ustar root root ##############################################################################
#
# 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, shutil, sys, tempfile, urllib, urllib2, subprocess
from optparse import OptionParser
if sys.platform == 'win32':
def quote(c):
if ' ' in c:
return '"%s"' % c # work around spawn lamosity on windows
else:
return c
else:
quote = str
# See zc.buildout.easy_install._has_broken_dash_S for motivation and comments.
stdout, stderr = subprocess.Popen(
[sys.executable, '-Sc',
'try:\n'
' import ConfigParser\n'
'except ImportError:\n'
' print 1\n'
'else:\n'
' print 0\n'],
stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
has_broken_dash_S = bool(int(stdout.strip()))
# In order to be more robust in the face of system Pythons, we want to
# run without site-packages loaded. This is somewhat tricky, in
# particular because Python 2.6's distutils imports site, so starting
# with the -S flag is not sufficient. However, we'll start with that:
if not has_broken_dash_S and 'site' in sys.modules:
# We will restart with python -S.
args = sys.argv[:]
args[0:0] = [sys.executable, '-S']
args = map(quote, args)
os.execv(sys.executable, args)
# Now we are running with -S. We'll get the clean sys.path, import site
# because distutils will do it later, and then reset the path and clean
# out any namespace packages from site-packages that might have been
# loaded by .pth files.
clean_path = sys.path[:]
import site # imported because of its side effects
sys.path[:] = clean_path
for k, v in sys.modules.items():
if k in ('setuptools', 'pkg_resources') or (
hasattr(v, '__path__') and
len(v.__path__) == 1 and
not os.path.exists(os.path.join(v.__path__[0], '__init__.py'))):
# This is a namespace package. Remove it.
sys.modules.pop(k)
is_jython = sys.platform.startswith('java')
setuptools_source = 'http://peak.telecommunity.com/dist/ez_setup.py'
distribute_source = 'http://python-distribute.org/distribute_setup.py'
# parsing arguments
def normalize_to_url(option, opt_str, value, parser):
if value:
if '://' not in value: # It doesn't smell like a URL.
value = 'file://%s' % (
urllib.pathname2url(
os.path.abspath(os.path.expanduser(value))),)
if opt_str == '--download-base' and not value.endswith('/'):
# Download base needs a trailing slash to make the world happy.
value += '/'
else:
value = None
name = opt_str[2:].replace('-', '_')
setattr(parser.values, name, value)
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 --setup-source and --download-base to point to
local resources, you can keep this script from going over the network.
'''
parser = OptionParser(usage=usage)
parser.add_option("-v", "--version", dest="version",
help="use a specific zc.buildout version")
parser.add_option("-d", "--distribute",
action="store_true", dest="use_distribute", default=False,
help="Use Distribute rather than Setuptools.")
parser.add_option("--setup-source", action="callback", dest="setup_source",
callback=normalize_to_url, nargs=1, type="string",
help=("Specify a URL or file location for the setup file. "
"If you use Setuptools, this will default to " +
setuptools_source + "; if you use Distribute, this "
"will default to " + distribute_source + "."))
parser.add_option("--download-base", action="callback", dest="download_base",
callback=normalize_to_url, nargs=1, type="string",
help=("Specify a URL or directory for downloading "
"zc.buildout and either Setuptools or Distribute. "
"Defaults to PyPI."))
parser.add_option("--eggs",
help=("Specify a directory for storing eggs. Defaults to "
"a temporary directory that is deleted when the "
"bootstrap script completes."))
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", None, action="store", dest="config_file",
help=("Specify the path to the buildout configuration "
"file to be used."))
options, args = parser.parse_args()
if options.eggs:
eggs_dir = os.path.abspath(os.path.expanduser(options.eggs))
else:
eggs_dir = tempfile.mkdtemp()
if options.setup_source is None:
if options.use_distribute:
options.setup_source = distribute_source
else:
options.setup_source = setuptools_source
if options.accept_buildout_test_releases:
args.insert(0, 'buildout:accept-buildout-test-releases=true')
try:
import pkg_resources
import setuptools # A flag. Sometimes pkg_resources is installed alone.
if not hasattr(pkg_resources, '_distribute'):
raise ImportError
except ImportError:
ez_code = urllib2.urlopen(
options.setup_source).read().replace('\r\n', '\n')
ez = {}
exec ez_code in ez
setup_args = dict(to_dir=eggs_dir, download_delay=0)
if options.download_base:
setup_args['download_base'] = options.download_base
if options.use_distribute:
setup_args['no_fake'] = True
if sys.version_info[:2] == (2, 4):
setup_args['version'] = '0.6.32'
ez['use_setuptools'](**setup_args)
if 'pkg_resources' in sys.modules:
reload(sys.modules['pkg_resources'])
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)
cmd = [quote(sys.executable),
'-c',
quote('from setuptools.command.easy_install import main; main()'),
'-mqNxd',
quote(eggs_dir)]
if not has_broken_dash_S:
cmd.insert(1, '-S')
find_links = options.download_base
if not find_links:
find_links = os.environ.get('bootstrap-testing-find-links')
if not find_links and options.accept_buildout_test_releases:
find_links = 'http://downloads.buildout.org/'
if find_links:
cmd.extend(['-f', quote(find_links)])
if options.use_distribute:
setup_requirement = 'distribute'
else:
setup_requirement = 'setuptools'
ws = pkg_resources.working_set
setup_requirement_path = ws.find(
pkg_resources.Requirement.parse(setup_requirement)).location
env = dict(
os.environ,
PYTHONPATH=setup_requirement_path)
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=[setup_requirement_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 distv >= pkg_resources.parse_version('2dev'):
continue
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 += '=='+version
else:
requirement += '<2dev'
cmd.append(requirement)
if is_jython:
import subprocess
exitcode = subprocess.Popen(cmd, env=env).wait()
else: # Windows prefers this, apparently; otherwise we would prefer subprocess
exitcode = os.spawnle(*([os.P_WAIT, sys.executable] + cmd + [env]))
if exitcode != 0:
sys.stdout.flush()
sys.stderr.flush()
print ("An error occurred when trying to install zc.buildout. "
"Look above this message for any errors that "
"were output by easy_install.")
sys.exit(exitcode)
ws.add_entry(eggs_dir)
ws.require(requirement)
import zc.buildout.buildout
# If there isn't already a command in the args, add bootstrap
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's main function
if options.config_file is not None:
args[0:0] = ['-c', options.config_file]
zc.buildout.buildout.main(args)
if not options.eggs: # clean up temporary egg directory
shutil.rmtree(eggs_dir)
zope.formlib-4.3.0a2/CHANGES.txt 0000664 0000000 0000000 00000011572 12243456432 014673 0 ustar root root =======
Changes
=======
4.3.0a2 (2013-11-21)
====================
- Support for CSRF protection.
- Added support for restricting the acceptable request method for the
form submit.
4.3.0a1 (2013-02-27)
====================
- Added support for Python 3.3.
4.2.1 (2013-02-22)
==================
- Moved default values for the `BooleanDisplayWidget` from module to class
definition to make them changeable in instance.
4.2.0 (2012-11-27)
==================
- LP #1017884: Add redirect status codes (303, 307) to the set which prevent
form rendering.
- Replaced deprecated ``zope.component.adapts`` usage with equivalent
``zope.component.adapter`` decorator.
- Replaced deprecated ``zope.interface.implements`` usage with equivalent
``zope.interface.implementer`` decorator.
- Dropped support for Python 2.5.
- Make separator of ``SourceSequenceDisplayWidget`` configurable.
4.1.1 (2012-03-16)
==================
- Added `ignoreContext` attribute to form classes to control whether
`checkInvariants` takes the context of the form into account when
checking interface invariants.
By default `ignoreContext` is set to ``False``. On the `AddForm` it is
``True`` by default because the context of this form is naturally not
suitable as context for the interface invariant.
4.1.0 (2012-03-15)
==================
- `checkInvariants` now takes the context of the form into account when
checking interface invariants.
- Tests are no longer compatible with Python 2.4.
4.0.6 (2011-08-20)
==================
- Fixed bug in ``orderedSelectionList.pt`` template.
4.0.5 (2010-09-16)
==================
- Fixed Action name parameter handling, since 4.0.3 all passed names were
lowercased.
4.0.4 (2010-07-06)
==================
- Fixed tests to pass under Python 2.7.
- Fix validation of "multiple" attributes in orderedSelectionList.pt.
4.0.3 (2010-05-06)
==================
- Keep Actions from raising exceptions when passed Unicode lables [LP:528468].
- Improve display of the "nothing selected" case for optional Choice fields
[LP:269782].
- Improve truth testing for ItemDisplayWidget [LP:159232].
- Don't blow up if TypeError raised during token conversion [LP:98491].
4.0.2 (2010-03-07)
==================
- Adapted tests for Python 2.4 (enforce sorting for short pprint output)
4.0.1 (2010-02-21)
==================
- Documentation uploaded to PyPI now contains widget documentation.
- Escape MultiCheckBoxWidget content [LP:302427].
4.0 (2010-01-08)
================
- Widget implementation and all widgets from zope.app.form have been
moved into zope.formlib, breaking zope.formlib's dependency on
zope.app.form (instead zope.app.form now depends on zope.formlib).
Widgets can all be imported from ``zope.formlib.widgets``.
Widget base classes and render functionality is in
``zope.formlib.widget``.
All relevant widget interfaces are now in ``zope.formlib.interfaces``.
3.10.0 (2009-12-22)
===================
- Use named template from zope.browserpage in favor of zope.app.pagetemplate.
3.9.0 (2009-12-22)
==================
- Use ViewPageTemplateFile from zope.browserpage.
3.8.0 (2009-12-22)
==================
- Adjusted test output to new zope.schema release.
3.7.0 (2009-12-18)
==================
- Rid ourselves from zope.app test dependencies.
- Fix: Button label needs escaping
3.6.0 (2009-05-18)
==================
- Remove deprecated imports.
- Remove dependency on zope.app.container (use ``IAdding`` from
``zope.browser.interfaces``) instead. Depend on
``zope.browser>=1.1`` (the version with ``IAdding``).
- Moved ``namedtemplate`` to ``zope.app.pagetemplate``, to cut some
dependencies on ``zope.formlib`` when using this feature. Left BBB
imports here.
3.5.2 (2009-02-21)
==================
- Adapt tests for Python 2.5 output.
3.5.1 (2009-01-31)
==================
- Adapt tests to upcoming zope.schema release 3.5.1.
3.5.0 (2009-01-26)
==================
New Features
------------
- Test dependencies are declared in a `test` extra now.
- Introduced ``zope.formlib.form.applyData`` which works like
``applyChanges`` but returns a dictionary with information about
which attribute of which schema changed. This information is then
sent along with the ``IObjectModifiedEvent``.
This fixes https://bugs.launchpad.net/zope3/+bug/98483.
Bugs Fixed
----------
- Actions that cause a redirect (301, 302) do not cause the `render` method to
be called anymore.
- The zope.formlib.form.Action class didn't fully implement
zope.formlib.interfaces.IAction.
- zope.formlib.form.setupWidgets and zope.formlib.form.setupEditWidgets did
not check for write access on the adapter but on context. This fixes
https://bugs.launchpad.net/zope3/+bug/219948
3.4.0 (2007-09-28)
==================
No further changes since 3.4.0a1.
3.4.0a1 (2007-04-22)
====================
Initial release as a separate project, corresponds to zope.formlib
from Zope 3.4.0a1
zope.formlib-4.3.0a2/buildout.cfg 0000664 0000000 0000000 00000001175 12243456432 015370 0 ustar root root [buildout]
develop = .
parts = test python coverage-test coverage-report
versions = versions
[test]
recipe = zc.recipe.testrunner
eggs = zope.formlib [test]
[python]
recipe = zc.recipe.egg
eggs = zope.formlib
interpreter = python
[coverage-test]
recipe = zc.recipe.testrunner
eggs = ${test:eggs}
defaults = ['--coverage', '../../coverage']
[coverage-report]
recipe = zc.recipe.egg
eggs = z3c.coverage
scripts = coverage=coverage-report
arguments = ('coverage', 'coverage/report')
[versions]
zope.browserpage = 4.1.0a1
zope.i18n = 4.0.0a4
zope.publisher = 4.0.0a2
zope.security = 4.0.0a3
zope.traversing = 4.0.0a2
zope.tal = 4.0.0a1
zope.formlib-4.3.0a2/tox.ini 0000664 0000000 0000000 00000000624 12243456432 014371 0 ustar root root [tox]
envlist =
py26,py27,py33
[testenv]
commands =
python setup.py test -q
deps =
pytz
zope.browser
zope.browserpage
zope.component
zope.event
zope.i18n
zope.i18nmessageid
zope.interface
zope.lifecycleevent
zope.publisher
zope.schema
zope.security
zope.traversing
zope.datetime
zope.configuration
zope.testing
zope.testrunner
zope.formlib-4.3.0a2/src/ 0000775 0000000 0000000 00000000000 12355055056 013644 5 ustar root root zope.formlib-4.3.0a2/src/zope/ 0000775 0000000 0000000 00000000000 12355055056 014621 5 ustar root root zope.formlib-4.3.0a2/src/zope/formlib/ 0000775 0000000 0000000 00000000000 12355055056 016253 5 ustar root root zope.formlib-4.3.0a2/src/zope/formlib/configure.zcml 0000664 0000000 0000000 00000047152 12243456432 021133 0 ustar root root
zope.formlib-4.3.0a2/src/zope/formlib/errors.txt 0000664 0000000 0000000 00000005145 12243456432 020334 0 ustar root root ==============
Error handling
==============
These are a couple of functional tests that were written on-the-go ... In the
future this might become more extensive ...
Displaying invalidation errors
==============================
Validation errors, e.g. cause by invariants, are converted into readable text
by adapting them to IWidgetInputErrorView:
>>> from zope.publisher.browser import TestRequest
>>> from zope.interface.exceptions import Invalid
>>> from zope.component import getMultiAdapter
>>> from zope.formlib.interfaces import IWidgetInputErrorView
>>> error = Invalid("You are wrong!")
>>> message = getMultiAdapter((error, TestRequest()),
... IWidgetInputErrorView).snippet()
>>> message
u'You are wrong!'
Interface invariant methods raise zope.interface.Invalid exception. Test if
this exception gets handled by the error_views.
>>> myError = Invalid('My error message')
>>> import zope.formlib.form
>>> mybase = zope.formlib.form.FormBase(None, TestRequest())
>>> mybase.errors = (myError,)
>>> save = mybase.error_views()
>>> next(save)
u'My error message'
Now we need to set up the translation framework:
>>> from zope import component, interface
>>> from zope.i18n.interfaces import INegotiator
>>> @interface.implementer(INegotiator)
... class Negotiator:
... def getLanguage(*ignored): return 'test'
>>> component.provideUtility(Negotiator())
>>> from zope.i18n.testmessagecatalog import TestMessageFallbackDomain
>>> component.provideUtility(TestMessageFallbackDomain)
And yes, we can even handle an i18n message in an Invalid exception:
>>> from zope.i18nmessageid import MessageFactory
>>> _ = MessageFactory('my.domain')
>>> myError = Invalid(_('My i18n error message'))
>>> mybase = zope.formlib.form.FormBase(None, TestRequest())
>>> mybase.errors = (myError,)
>>> save = mybase.error_views()
>>> next(save)
u'[[my.domain][My i18n error message]]'
Displaying widget input errors
==============================
WidgetInputError exceptions also work with i18n messages:
>>> from zope.formlib.interfaces import WidgetInputError
>>> myError = WidgetInputError(
... field_name='summary',
... widget_title=_(u'Summary'),
... errors=_(u'Foo'))
>>> mybase = zope.formlib.form.FormBase(None, TestRequest())
>>> mybase.errors = (myError,)
>>> save = mybase.error_views()
>>> next(save)
u'[[my.domain][Summary]]: [[my.domain][Foo]]'
zope.formlib-4.3.0a2/src/zope/formlib/i18n.py 0000664 0000000 0000000 00000000225 12243456432 017402 0 ustar root root """\
I18N support for zope.formlib
"""
__docformat__ = "reStructuredText"
import zope.i18nmessageid
_ = zope.i18nmessageid.MessageFactory("zope")
zope.formlib-4.3.0a2/src/zope/formlib/orderedSelectionList.pt 0000664 0000000 0000000 00000014176 12243456432 022756 0 ustar root root
zope.formlib-4.3.0a2/src/zope/formlib/TODO.txt 0000664 0000000 0000000 00000000061 12243456432 017555 0 ustar root root - test coverage
- return error icon to templates
zope.formlib-4.3.0a2/src/zope/formlib/_compat.py 0000664 0000000 0000000 00000002365 12243456432 020254 0 ustar root root ##############################################################################
#
# Copyright (c) 2005 Zope Foundation and Contributors.
#
# 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.
#
##############################################################################
"""Compatibility between Python versions
"""
import base64
import sys
PY3 = sys.version_info[0] >= 3
if PY3:
from io import StringIO
unicode = str
imap = map
basestring = str
def toUnicode(obj):
return obj.decode() if isinstance(obj, bytes) else str(obj)
def safeBase64Encode(obj):
return base64.b64encode(
obj.encode()).strip().replace(b'=', b'_').decode()
else:
from StringIO import StringIO
from itertools import imap
unicode = toUnicode = unicode
basestring = basestring
def safeBase64Encode(obj):
return base64.b64encode(toUnicode(obj)).strip().replace('=', '_')
zope.formlib-4.3.0a2/src/zope/formlib/widgets.txt 0000664 0000000 0000000 00000016606 12243456432 020472 0 ustar root root ===============
Browser Widgets
===============
Formlib defines widgets: views on bound schema fields. Many of these
are straightforward. For instance, see the `TextWidget` in
textwidgets.py, which is a subclass of BrowserWidget in widget.py. It
is registered as an `IBrowserRequest` view of an `ITextLine` schema
field, providing the `IInputWidget` interface::
The widget then receives the field and the request as arguments to the factory
(i.e., the `TextWidget` class).
Some widgets in formlib extend this pattern. The widget registration
is extended for `Choice` fields and for the `collection` fields.
Default Choice Field Widget Registration and Lookup
===================================================
All field widgets are obtained by looking up a browser `IInputWidget`
or `IDisplayWidget` view for the field object. For `Choice` fields,
the default registered widget defers all of its behavior to the result
of another lookup: a browser widget view for the field *and* the
Choice field's vocabulary.
This allows registration of Choice widgets that differ on the basis of the
vocabulary type. For example, a widget for a vocabulary of images might have
a significantly different user interface than a widget for a vocabulary of
words. A dynamic vocabulary might implement `IIterableVocabulary` if its
contents are below a certain length, but not implement the marker "iterable"
interface if the number of possible values is above the threshhold.
This also means that choice widget factories are called with with an additional
argument. Rather than being called with the field and the request as
arguments, choice widgets receive the field, vocabulary, and request as
arguments.
Some `Choice` widgets may also need to provide a source interface,
particularly if the vocabulary is too big to iterate over.
Default Collection Field Widget Registration and Lookup
=======================================================
The default configured lookup for collection fields -- List, Tuple, and Set, for
instance -- begins with the usual lookup for a browser widget view for the
field object. This widget defers its display to the result of another lookup:
a browser widget view registered for the field and the field's `value_type`
(the type of the contained values). This allows registrations for collection
widgets that differ on the basis of the members -- a widget for entering a list
of text strings might differ significantly from a widget for entering a list of
dates...or even a list of choices, as discussed below.
This registration pattern has three implications that should be highlighted.
* First, collection fields that do not specify a `value_type` probably cannot
have a reasonable widget.
* Second, collection widgets that wish to be the default widget for a
collection with any `value_type` should be registered for the collection
field and a generic value_type: the `IField` interface. Do not register the
generic widget for the collection field only or you will break the lookup
behavior as described here.
* Third, like choice widget factories, sequence widget factories (classes or
functions) take three arguments. Typical sequence widgets receive the
field, the `value_type`, and the request as arguments.
Collections of Choices
----------------------
If a collection field's `value_type` is a `Choice` field, the second widget
again defers its behavior, this time to a third lookup based on the collection
field and the choice's vocabulary. This means that a widget for a list of
large image choices can be different than a widget for a list of small image
choices (with a different vocabulary interface), different from a widget for a
list of keyword choices, and different from a set of keyword choices.
Some advanced applications may wish to do a further lookup on the basis of the
unique attribute of the collection field--perhaps looking up a named view with
a "unique" or "lenient" token depending on the field's value, but this is not
enabled in the default Zope 3 configuration.
Registering Widgets for a New Collection Field Type
---------------------------------------------------
Because of this lookup pattern, basic widget registrations for new field types
must follow a recipe. For example, a developer may introduce a new Bag field
type for simple shopping cart functionality and wishes to add widgets for it
within the default Zope 3 collection widget registration. The bag widgets
should be registered something like this.
The only hard requirement is that the developer must register the bag + choice
widget: the widget is just the factory for the third dispatch as described
above, so the developer can use the already implemented widgets listed below::
Beyond this, the developer may also have a generic bag widget she wishes to
register. This might look something like this, assuming there's a
`BagSequenceWidget` available in this package::
Then any widgets for the bag and a vocabulary would be registered according to
this general pattern, in which `IIterableVocabulary` would be the interface of
any appropriate vocabulary and `BagWidget` is some appropriate widget::
Choice widgets and the missing value
====================================
Choice widgets for a non-required field include a "no value" item to allow for
not selecting any value at all. This value used to be omitted for required
fields on the assumption that the widget should avoid invalid input from the
start.
However, if the context object doesn't yet have a field value set and there's
no default value, a dropdown widget would have to select an arbitrary value
due to the way it is displayed in the browser. This way, the field would
always validate, but possibly with a value the user never chose consciously.
Starting with version zope.app.form 3.6.0, dropdown widgets for
required fields display a "no value" item even for required fields if
an arbitrary value would have to be selected by the widget otherwise.
To switch the old behaviour back on for backwards compatibility, do
zope.formlib.itemswidgets.EXPLICIT_EMPTY_SELECTION = False
during application start-up.
zope.formlib-4.3.0a2/src/zope/formlib/utility.py 0000664 0000000 0000000 00000014326 12243456432 020335 0 ustar root root ##############################################################################
#
# Copyright (c) 2002 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.
#
##############################################################################
"""Form utility functions
This is an implementation only used by zope.formlib.objectwidget, not
by the rest of the widgets in zope.formlib. We would like to keep it
this way.
This module is not directly tested: zope.app.form does have tests to
test this, and the objectwidget implementation tests this indirectly.
At some point we would like to rewrite zope.formlib.objectwidget so it
uses the infrastructure provided by zope.formlib itself.
"""
__docformat__ = 'restructuredtext'
from zope import component
from zope.schema import getFieldsInOrder
from zope.formlib.interfaces import IWidget
from zope.formlib.interfaces import WidgetsError
from zope.formlib.interfaces import InputErrors
from zope.formlib.interfaces import IInputWidget
from zope.formlib.interfaces import IWidgetFactory
# A marker that indicates 'no value' for any of the utility functions that
# accept a 'value' argument.
no_value = object()
def _fieldlist(names, schema):
if not names:
fields = getFieldsInOrder(schema)
else:
fields = [ (name, schema[name]) for name in names ]
return fields
def _createWidget(context, field, viewType, request):
"""Creates a widget given a `context`, `field`, and `viewType`."""
field = field.bind(context)
return component.getMultiAdapter((field, request), viewType)
def _widgetHasStickyValue(widget):
"""Returns ``True`` if the widget has a sticky value.
A sticky value is input from the user that should not be overridden
by an object's current field value. E.g. a user may enter an invalid
postal code, submit the form, and receive a validation error - the postal
code should be treated as 'sticky' until the user successfully updates
the object.
"""
return IInputWidget.providedBy(widget) and widget.hasInput()
def setUpWidget(view, name, field, viewType, value=no_value, prefix=None,
ignoreStickyValues=False, context=None):
"""Sets up a single view widget.
The widget will be an attribute of the `view`. If there is already
an attribute of the given name, it must be a widget and it will be
initialized with the given `value` if not ``no_value``.
If there isn't already a `view` attribute of the given name, then a
widget will be created and assigned to the attribute.
"""
if context is None:
context = view.context
widgetName = name + '_widget'
# check if widget already exists
widget = getattr(view, widgetName, None)
if widget is None:
# does not exist - create it
widget = _createWidget(context, field, viewType, view.request)
setattr(view, widgetName, widget)
elif IWidgetFactory.providedBy(widget):
# exists, but is actually a factory - use it to create the widget
widget = widget(field.bind(context), view.request)
setattr(view, widgetName, widget)
# widget must implement IWidget
if not IWidget.providedBy(widget):
raise TypeError(
"Unable to configure a widget for %s - attribute %s does not "
"implement IWidget" % (name, widgetName))
if prefix:
widget.setPrefix(prefix)
if value is not no_value and (
ignoreStickyValues or not _widgetHasStickyValue(widget)):
widget.setRenderedValue(value)
def setUpWidgets(view, schema, viewType, prefix=None, ignoreStickyValues=False,
initial={}, names=None, context=None):
"""Sets up widgets for the fields defined by a `schema`.
Appropriate for collecting input without a current object implementing
the schema (such as an add form).
`view` is the view that will be configured with widgets.
`viewType` is the type of widgets to create (e.g. IInputWidget or
IDisplayWidget).
`schema` is an interface containing the fields that widgets will be
created for.
`prefix` is a string that is prepended to the widget names in the generated
HTML. This can be used to differentiate widgets for different schemas.
`ignoreStickyValues` is a flag that, when ``True``, will cause widget
sticky values to be replaced with the context field value or a value
specified in initial.
`initial` is a mapping of field names to initial values.
`names` is an optional iterable that provides an ordered list of field
names to use. If names is ``None``, the list of fields will be defined by
the schema.
`context` provides an alternative context for acquisition.
"""
for (name, field) in _fieldlist(names, schema):
setUpWidget(view, name, field, viewType,
value=initial.get(name, no_value),
prefix=prefix,
ignoreStickyValues=ignoreStickyValues,
context=context)
def applyWidgetsChanges(view, schema, target=None, names=None):
"""Updates an object with values from a view's widgets.
`view` contained the widgets that perform the update. By default, the
widgets will update the view's context.
`target` can be specified as an alternative object to update.
`schema` contrains the values provided by the widgets.
`names` can be specified to update a subset of the schema constrained
values.
"""
errors = []
changed = False
if target is None:
target = view.context
for name, field in _fieldlist(names, schema):
widget = getattr(view, name + '_widget')
if IInputWidget.providedBy(widget) and widget.hasInput():
try:
changed = widget.applyChanges(target) or changed
except InputErrors as v:
errors.append(v)
if errors:
raise WidgetsError(errors)
return changed
zope.formlib-4.3.0a2/src/zope/formlib/pageform.pt 0000664 0000000 0000000 00000013043 12243456432 020420 0 ustar root root
zope.formlib-4.3.0a2/src/zope/formlib/boolwidgets.py 0000664 0000000 0000000 00000010266 12243456432 021153 0 ustar root root ##############################################################################
#
# Copyright (c) 2004 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.
#
##############################################################################
"""Browser widgets for items
"""
__docformat__ = 'restructuredtext'
from zope.schema.vocabulary import SimpleVocabulary
from zope.formlib.widget import SimpleInputWidget, renderElement
from zope.formlib.widget import DisplayWidget
from zope.formlib.i18n import _
from zope.formlib.itemswidgets import RadioWidget
from zope.formlib.itemswidgets import SelectWidget, DropdownWidget
class CheckBoxWidget(SimpleInputWidget):
"""A checkbox widget used to display Bool fields.
For more detailed documentation, including sample code, see
``tests/test_checkboxwidget.py``.
"""
type = 'checkbox'
default = 0
extra = ''
def __init__(self, context, request):
super(CheckBoxWidget, self).__init__(context, request)
self.required = False
def __call__(self):
"""Render the widget to HTML."""
value = self._getFormValue()
if value == 'on':
kw = {'checked': 'checked'}
else:
kw = {}
return "%s %s" % (
renderElement(self.tag,
type='hidden',
name=self.name+".used",
id=self.name+".used",
value=""
),
renderElement(self.tag,
type=self.type,
name=self.name,
id=self.name,
cssClass=self.cssClass,
extra=self.extra,
value="on",
**kw),
)
def _toFieldValue(self, input):
"""Convert from HTML presentation to Python bool."""
return input == 'on'
def _toFormValue(self, value):
"""Convert from Python bool to HTML representation."""
return value and 'on' or ''
def hasInput(self):
"""Check whether the field is represented in the form."""
return self.name + ".used" in self.request.form or \
super(CheckBoxWidget, self).hasInput()
def _getFormInput(self):
"""Returns the form input used by `_toFieldValue`.
Return values:
``'on'`` checkbox is checked
``''`` checkbox is not checked
``None`` form input was not provided
"""
if self.request.get(self.name) == 'on':
return 'on'
elif self.name + '.used' in self.request:
return ''
else:
return None
def BooleanRadioWidget(field, request, true=_('on'), false=_('off')):
vocabulary = SimpleVocabulary.fromItems( ((true, True), (false, False)) )
widget = RadioWidget(field, vocabulary, request)
widget.required = False
return widget
def BooleanSelectWidget(field, request, true=_('on'), false=_('off')):
vocabulary = SimpleVocabulary.fromItems( ((true, True), (false, False)) )
widget = SelectWidget(field, vocabulary, request)
widget.size = 2
widget.required = False
return widget
def BooleanDropdownWidget(field, request, true=_('on'), false=_('off')):
vocabulary = SimpleVocabulary.fromItems( ((true, True), (false, False)) )
widget = DropdownWidget(field, vocabulary, request)
widget.required = False
return widget
class BooleanDisplayWidget(DisplayWidget):
_msg_true = _("True")
_msg_false = _("False")
def __call__(self):
if self._renderedValueSet():
value = self._data
else:
value = self.context.default
if value:
return self._msg_true
else:
return self._msg_false
zope.formlib-4.3.0a2/src/zope/formlib/form.py 0000664 0000000 0000000 00000103155 12243456432 017574 0 ustar root root ##############################################################################
#
# Copyright (c) 2005 Zope Foundation and Contributors.
#
# 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.
#
##############################################################################
"""Forms
"""
import binascii
import datetime
import os
import re
import sys
import pytz
from cgi import escape
import zope.browser.interfaces
import zope.event
import zope.i18n
import zope.i18nmessageid
import zope.security
import zope.interface.interfaces
import zope.publisher.browser
import zope.publisher.interfaces.browser
from zope import component, interface, schema
from zope.browserpage import ViewPageTemplateFile
from zope.interface.common import idatetime
from zope.interface.interface import InterfaceClass
from zope.publisher.interfaces.http import MethodNotAllowed
from zope.schema.interfaces import IField
from zope.schema.interfaces import ValidationError
from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent
from zope.lifecycleevent import Attributes
from zope.browserpage import namedtemplate
from zope.formlib._compat import basestring, unicode
from zope.formlib.interfaces import IWidgetInputErrorView
from zope.formlib.interfaces import IInputWidget, IDisplayWidget
from zope.formlib.interfaces import WidgetsError, MissingInputError
from zope.formlib.interfaces import InputErrors, WidgetInputError
from zope.formlib.interfaces import InvalidFormError, InvalidCSRFTokenError
from zope.formlib import interfaces
from zope.i18nmessageid import MessageFactory
_ = MessageFactory("zope")
interface.moduleProvides(interfaces.IFormAPI)
def expandPrefix(prefix):
"""Expand prefix string by adding a trailing period if needed.
expandPrefix(p) should be used instead of p+'.' in most contexts.
"""
if prefix and not prefix.endswith('.'):
return prefix + '.'
return prefix
@interface.implementer(interfaces.IFormField)
class FormField:
def __init__(self, field, name=None, prefix='',
for_display=None, for_input=None, custom_widget=None,
render_context=False, get_rendered=None, interface=None
):
self.field = field
if name is None:
name = field.__name__
assert name
self.__name__ = expandPrefix(prefix) + name
self.prefix = prefix
if interface is None:
interface = field.interface
self.interface = interface
self.for_display = for_display
self.for_input = for_input
self.custom_widget = custom_widget
self.render_context = render_context
self.get_rendered = get_rendered
Field = FormField
def _initkw(keep_readonly=(), omit_readonly=False, **defaults):
return keep_readonly, omit_readonly, defaults
@interface.implementer(interfaces.IFormFields)
class FormFields(object):
def __init__(self, *args, **kw):
keep_readonly, omit_readonly, defaults = _initkw(**kw)
fields = []
for arg in args:
if isinstance(arg, InterfaceClass):
for name, field in schema.getFieldsInOrder(arg):
fields.append((name, field, arg))
elif IField.providedBy(arg):
name = arg.__name__
if not name:
raise ValueError(
"Field has no name")
fields.append((name, arg, arg.interface))
elif isinstance(arg, FormFields):
for form_field in arg:
fields.append(
(form_field.__name__,
form_field,
form_field.interface)
)
elif isinstance(arg, FormField):
fields.append((arg.__name__, arg, arg.interface))
else:
raise TypeError("Unrecognized argument type", arg)
seq = []
byname = {}
for name, field, iface in fields:
if isinstance(field, FormField):
form_field = field
else:
if field.readonly:
if omit_readonly and (name not in keep_readonly):
continue
form_field = FormField(field, interface=iface, **defaults)
name = form_field.__name__
if name in byname:
raise ValueError("Duplicate name", name)
seq.append(form_field)
byname[name] = form_field
self.__FormFields_seq__ = seq
self.__FormFields_byname__ = byname
def __len__(self):
return len(self.__FormFields_seq__)
def __iter__(self):
return iter(self.__FormFields_seq__)
def __getitem__(self, name):
return self.__FormFields_byname__[name]
def get(self, name, default=None):
return self.__FormFields_byname__.get(name, default)
def __add__(self, other):
if not isinstance(other, FormFields):
return NotImplemented
return self.__class__(self, other)
def select(self, *names):
"""Return a modified instance with an ordered subset of fields."""
return self.__class__(*[self[name] for name in names])
def omit(self, *names):
"""Return a modified instance omitting given fields."""
return self.__class__(*[ff for ff in self if ff.__name__ not in names])
Fields = FormFields
def fields_initkw(keep_all_readonly=False, **other):
return keep_all_readonly, other
# Backward compat
def fields(*args, **kw):
keep_all_readonly, other = fields_initkw(**kw)
other['omit_readonly'] = not keep_all_readonly
return FormFields(*args, **other)
@interface.implementer(interfaces.IWidgets)
class Widgets(object):
def __init__(self, widgets, prefix_length=None, prefix=None):
self.__Widgets_widgets_items__ = widgets
self.__Widgets_widgets_list__ = [w for (i, w) in widgets]
if prefix is None:
# BBB Allow old code using the prefix_length argument.
if prefix_length is None:
raise TypeError(
"One of 'prefix_length' and 'prefix' is required."
)
self.__Widgets_widgets_dict__ = dict(
[(w.name[prefix_length:], w) for (i, w) in widgets]
)
else:
prefix = expandPrefix(prefix)
self.__Widgets_widgets_dict__ = dict(
[(_widgetKey(w, prefix), w) for (i, w) in widgets]
)
def __iter__(self):
return iter(self.__Widgets_widgets_list__)
def __getitem__(self, name):
return self.__Widgets_widgets_dict__[name]
# TODO need test
def get(self, name):
return self.__Widgets_widgets_dict__.get(name)
def __iter_input_and_widget__(self):
return iter(self.__Widgets_widgets_items__)
# TODO need test
def __add__(self, other):
widgets = self.__class__([], 0)
widgets.__Widgets_widgets_items__ = (
self.__Widgets_widgets_items__ + other.__Widgets_widgets_items__)
widgets.__Widgets_widgets_list__ = (
self.__Widgets_widgets_list__ + other.__Widgets_widgets_list__)
widgets.__Widgets_widgets_dict__ = self.__Widgets_widgets_dict__.copy()
widgets.__Widgets_widgets_dict__.update(other.__Widgets_widgets_dict__)
return widgets
def canWrite(context, field):
writer = getattr(field, 'writer', None)
if writer is not None:
return zope.security.canAccess(context, writer.__name__)
return zope.security.canWrite(context, field.__name__)
def setUpWidgets(form_fields,
form_prefix=None, context=None, request=None, form=None,
data=(), adapters=None, ignore_request=False):
if request is None:
request = form.request
if context is None and form is not None:
context = form.context
if form_prefix is None:
form_prefix = form.prefix
widgets = []
adapter = None
for form_field in form_fields:
field = form_field.field
if form_field.render_context:
if adapters is None:
adapters = {}
# Adapt context, if necessary
interface = form_field.interface
adapter = adapters.get(interface)
if adapter is None:
if interface is None:
adapter = context
else:
adapter = interface(context)
adapters[interface] = adapter
if interface is not None:
adapters[interface.__name__] = adapter
field = field.bind(adapter)
else:
field = field.bind(context)
readonly = form_field.for_display
readonly = readonly or (field.readonly and not form_field.for_input)
readonly = readonly or (
(form_field.render_context & interfaces.DISPLAY_UNWRITEABLE)
and not canWrite(adapter, field)
)
if form_field.custom_widget is not None:
widget = form_field.custom_widget(field, request)
else:
if readonly:
widget = component.getMultiAdapter((field, request),
IDisplayWidget)
else:
widget = component.getMultiAdapter((field, request),
IInputWidget)
prefix = form_prefix
if form_field.prefix:
prefix = expandPrefix(prefix) + form_field.prefix
widget.setPrefix(prefix)
if ignore_request or readonly or not widget.hasInput():
# Get the value to render
if form_field.__name__ in data:
widget.setRenderedValue(data[form_field.__name__])
elif form_field.get_rendered is not None:
widget.setRenderedValue(form_field.get_rendered(form))
elif form_field.render_context:
widget.setRenderedValue(field.get(adapter))
else:
widget.setRenderedValue(field.default)
widgets.append((not readonly, widget))
return Widgets(widgets, prefix=form_prefix)
def setUpInputWidgets(form_fields, form_prefix, context, request,
form=None, ignore_request=False):
widgets = []
for form_field in form_fields:
field = form_field.field.bind(context)
widget = _createWidget(form_field, field, request, IInputWidget)
prefix = form_prefix
if form_field.prefix:
prefix = expandPrefix(prefix) + form_field.prefix
widget.setPrefix(prefix)
if ignore_request:
if form_field.get_rendered is not None:
value = form_field.get_rendered(form)
else:
value = field.default
widget.setRenderedValue(value)
widgets.append((True, widget))
return Widgets(widgets, prefix=form_prefix)
def _createWidget(form_field, field, request, iface):
if form_field.custom_widget is None:
return component.getMultiAdapter((field, request), iface)
else:
return form_field.custom_widget(field, request)
def getWidgetsData(widgets, form_prefix, data):
errors = []
form_prefix = expandPrefix(form_prefix)
for input, widget in widgets.__iter_input_and_widget__():
if input and IInputWidget.providedBy(widget):
name = _widgetKey(widget, form_prefix)
if not widget.hasInput():
continue
try:
data[name] = widget.getInputValue()
except ValidationError as error:
# convert field ValidationError to WidgetInputError
error = WidgetInputError(widget.name, widget.label, error)
errors.append(error)
except InputErrors as error:
errors.append(error)
return errors
def _widgetKey(widget, form_prefix):
name = widget.name
if name.startswith(form_prefix):
name = name[len(form_prefix):]
else:
raise ValueError("Name does not match prefix", name, form_prefix)
return name
def setUpEditWidgets(form_fields, form_prefix, context, request,
adapters=None, for_display=False,
ignore_request=False):
if adapters is None:
adapters = {}
widgets = []
for form_field in form_fields:
field = form_field.field
# Adapt context, if necessary
interface = form_field.interface
adapter = adapters.get(interface)
if adapter is None:
if interface is None:
adapter = context
else:
adapter = interface(context)
adapters[interface] = adapter
if interface is not None:
adapters[interface.__name__] = adapter
field = field.bind(adapter)
readonly = form_field.for_display
readonly = readonly or (field.readonly and not form_field.for_input)
readonly = readonly or (
(form_field.render_context & interfaces.DISPLAY_UNWRITEABLE)
and not canWrite(adapter, field)
)
readonly = readonly or for_display
if readonly:
iface = IDisplayWidget
else:
iface = IInputWidget
widget = _createWidget(form_field, field, request, iface)
prefix = form_prefix
if form_field.prefix:
prefix = expandPrefix(prefix) + form_field.prefix
widget.setPrefix(prefix)
if ignore_request or readonly or not widget.hasInput():
# Get the value to render
value = field.get(adapter)
widget.setRenderedValue(value)
widgets.append((not readonly, widget))
return Widgets(widgets, prefix=form_prefix)
def setUpDataWidgets(form_fields, form_prefix, context, request, data=(),
for_display=False, ignore_request=False):
widgets = []
for form_field in form_fields:
field = form_field.field.bind(context)
readonly = for_display or field.readonly or form_field.for_display
if readonly:
iface = IDisplayWidget
else:
iface = IInputWidget
widget = _createWidget(form_field, field, request, iface)
prefix = form_prefix
if form_field.prefix:
prefix = expandPrefix(prefix) + form_field.prefix
widget.setPrefix(prefix)
if ((form_field.__name__ in data)
and (ignore_request or readonly or not widget.hasInput())
):
widget.setRenderedValue(data[form_field.__name__])
widgets.append((not readonly, widget))
return Widgets(widgets, prefix=form_prefix)
class NoInputData(interface.Invalid):
"""There was no input data because:
- It wasn't asked for
- It wasn't entered by the user
- It was entered by the user, but the value entered was invalid
This exception is part of the internal implementation of checkInvariants.
"""
class FormData:
def __init__(self, schema, data, context):
self._FormData_data___ = data
self._FormData_schema___ = schema
self._FormData_context___ = context
def __getattr__(self, name):
schema = self._FormData_schema___
data = self._FormData_data___
context = self._FormData_context___
try:
field = schema[name]
except KeyError:
raise AttributeError(name)
else:
value = data.get(name, data)
if value is data:
if context is None:
raise NoInputData(name)
# The value is not in the form look it up on the context:
field = schema[name]
adapted_context = schema(context)
if IField.providedBy(field):
value = field.get(adapted_context)
elif (zope.interface.interfaces.IAttribute.providedBy(field)
and
not zope.interface.interfaces.IMethod.providedBy(field)):
# Fallback for non-field schema contents:
value = getattr(adapted_context, name)
else:
# Don't know how to extract value
raise NoInputData(name)
if zope.interface.interfaces.IMethod.providedBy(field):
if not IField.providedBy(field):
raise RuntimeError(
"Data value is not a schema field", name)
v = lambda: value
else:
v = value
setattr(self, name, v)
return v
raise AttributeError(name)
def checkInvariants(form_fields, form_data, context):
# First, collect the data for the various schemas
schema_data = {}
for form_field in form_fields:
schema = form_field.interface
if schema is None:
continue
data = schema_data.get(schema)
if data is None:
data = schema_data[schema] = {}
if form_field.__name__ in form_data:
data[form_field.field.__name__] = form_data[form_field.__name__]
# Now validate the individual schemas
errors = []
for schema, data in schema_data.items():
try:
schema.validateInvariants(FormData(schema, data, context), errors)
except interface.Invalid:
pass # Just collect the errors
return [error for error in errors if not isinstance(error, NoInputData)]
def applyData(context, form_fields, data, adapters=None):
if adapters is None:
adapters = {}
descriptions = {}
for form_field in form_fields:
field = form_field.field
# Adapt context, if necessary
interface = form_field.interface
adapter = adapters.get(interface)
if adapter is None:
if interface is None:
adapter = context
else:
adapter = interface(context)
adapters[interface] = adapter
name = form_field.__name__
newvalue = data.get(name, form_field) # using form_field as marker
if (newvalue is not form_field) \
and (field.get(adapter) != newvalue):
descriptions.setdefault(interface, []).append(field.__name__)
field.set(adapter, newvalue)
return descriptions
def applyChanges(context, form_fields, data, adapters=None):
return bool(applyData(context, form_fields, data, adapters))
def _callify(meth):
"""Return method if it is callable,
otherwise return the form's method of the name"""
if callable(meth):
return meth
elif isinstance(meth, str):
return lambda form, *args: getattr(form, meth)(*args)
@interface.implementer(interfaces.IAction)
class Action(object):
_identifier = re.compile('[A-Za-z][a-zA-Z0-9_]*$')
def __init__(self, label, success=None, failure=None,
condition=None, validator=None, prefix='actions',
name=None, data=None):
self.label = label
self.setPrefix(prefix)
self.setName(name)
self.bindMethods(success_handler=success,
failure_handler=failure,
condition=condition,
validator=validator)
if data is None:
data = {}
self.data = data
def bindMethods(self, **methods):
"""Bind methods to the action"""
for k, v in methods.items():
setattr(self, k, _callify(v))
def setName(self, name):
"""Make sure name is ASCIIfiable.
Use action label if name is None
"""
if name is None:
name = self.label
if self._identifier.match(name):
name = name.lower()
else:
if isinstance(name, unicode):
name = name.encode("utf-8")
name = binascii.hexlify(name).decode()
self.name = name
self.__name__ = self.prefix + name
def setPrefix(self, prefix):
"""Set prefix"""
self.prefix = expandPrefix(prefix)
def __get__(self, form, class_=None):
if form is None:
return self
result = self.__class__.__new__(self.__class__)
result.__dict__.update(self.__dict__)
result.form = form
result.__name__ = expandPrefix(form.prefix) + result.__name__
interface.alsoProvides(result, interfaces.IBoundAction)
return result
def available(self):
condition = self.condition
return (condition is None) or condition(self.form, self)
def validate(self, data):
if self.validator is not None:
return self.validator(self.form, self, data)
def success(self, data):
if self.success_handler is not None:
return self.success_handler(self.form, self, data)
def failure(self, data, errors):
if self.failure_handler is not None:
return self.failure_handler(self.form, self, data, errors)
def submitted(self):
return (self.__name__ in self.form.request.form) and self.available()
def update(self):
pass
render = namedtemplate.NamedTemplate('render')
@namedtemplate.implementation(interfaces.IAction)
def render_submit_button(self):
if not self.available():
return ''
label = self.label
if isinstance(label, zope.i18nmessageid.Message):
label = zope.i18n.translate(self.label, context=self.form.request)
return ('' %
(self.__name__, self.__name__, escape(label, quote=True))
)
class action:
def __init__(self, label, actions=None, **options):
caller_locals = sys._getframe(1).f_locals
if actions is None:
actions = caller_locals.get('actions')
if actions is None:
actions = caller_locals['actions'] = Actions()
self.actions = actions
self.label = label
self.options = options
def __call__(self, success):
action = Action(self.label, success=success, **self.options)
self.actions.append(action)
return action
@interface.implementer(interfaces.IActions)
class Actions(object):
def __init__(self, *actions):
self.actions = actions
self.byname = dict([(a.__name__, a) for a in actions])
def __iter__(self):
return iter(self.actions)
def __getitem__(self, name):
try:
return self.byname[name]
except TypeError:
if isinstance(name, slice):
return self.__class__(
*self.actions[name.start:name.stop:name.step]
)
def append(self, action):
self.actions += (action, )
self.byname[action.__name__] = action
# TODO need test
def __add__(self, other):
return self.__class__(*(self.actions + other.actions))
def copy(self):
return self.__class__(*self.actions)
def __get__(self, inst, class_):
if inst is None:
return self
return self.__class__(*[a.__get__(inst) for a in self.actions])
def handleSubmit(actions, data, default_validate=None):
for action in actions:
if action.submitted():
errors = action.validate(data)
if errors is None and default_validate is not None:
errors = default_validate(action, data)
return errors, action
return None, None
# TODO need test for this
def availableActions(form, actions):
result = []
for action in actions:
condition = action.condition
if (condition is None) or condition(form, action):
result.append(action)
return result
@interface.implementer(interfaces.IForm)
class FormBase(zope.publisher.browser.BrowserPage):
label = u''
prefix = 'form'
status = ''
errors = ()
ignoreContext = False
method = None
protected = False
csrftoken = None
def setPrefix(self, prefix):
self.prefix = prefix
def setUpToken(self):
self.csrftoken = self.request.getCookies().get('__csrftoken__')
if self.csrftoken is None:
# It is possible another form, that is rendered as part of
# this request, already set a csrftoken. In that case we
# should find it in the response cookie and use that.
setcookie = self.request.response.getCookie('__csrftoken__')
if setcookie is not None:
self.csrftoken = setcookie['value']
else:
# Ok, nothing found, we should generate one and set
# it in the cookie ourselves. Note how we ``str()``
# the hex value of the ``os.urandom`` call here, as
# Python-3 will return bytes and the cookie roundtrip
# of a bytes values gets messed up.
self.csrftoken = str(binascii.hexlify(os.urandom(32)))
self.request.response.setCookie(
'__csrftoken__',
self.csrftoken,
path='/',
expires=None, # equivalent to "remove on browser quit"
httpOnly=True, # no javascript access please.
)
def checkToken(self):
cookietoken = self.request.getCookies().get('__csrftoken__')
if cookietoken is None:
# CSRF is enabled, so we really should get a token from the
# cookie. We didn't get it, so this submit is invalid!
raise InvalidCSRFTokenError(_('Invalid CSRF token'))
if cookietoken != self.request.form.get('__csrftoken__', None):
# The token in the cookie is different from the one in the
# form data. This submit is invalid!
raise InvalidCSRFTokenError(_('Invalid CSRF token'))
def setUpWidgets(self, ignore_request=False):
self.adapters = {}
self.widgets = setUpWidgets(
self.form_fields, self.prefix, self.context, self.request,
form=self, adapters=self.adapters, ignore_request=ignore_request)
def validate(self, action, data):
if self.method is not None:
# Verify the correct request method was used.
if self.method.upper() != self.request.method.upper():
raise MethodNotAllowed(self.context, self.request)
if self.protected:
self.checkToken() # This form has CSRF protection enabled.
if self.ignoreContext:
context = None
else:
context = self.context
return (getWidgetsData(self.widgets, self.prefix, data)
+ checkInvariants(self.form_fields, data, context))
template = namedtemplate.NamedTemplate('default')
# TODO also need to be able to show disabled actions
def availableActions(self):
return availableActions(self, self.actions)
def resetForm(self):
self.setUpWidgets(ignore_request=True)
form_result = None
form_reset = True
def update(self):
if self.protected:
self.setUpToken() # This form has CSRF protection enabled.
self.setUpWidgets()
self.form_reset = False
data = {}
errors, action = handleSubmit(self.actions, data, self.validate)
# the following part will make sure that previous error not
# get overriden by new errors. This is usefull for subforms. (ri)
if self.errors is None:
self.errors = errors
else:
if errors is not None:
self.errors += tuple(errors)
if errors:
self.status = _('There were errors')
result = action.failure(data, errors)
elif errors is not None:
self.form_reset = True
result = action.success(data)
else:
result = None
self.form_result = result
def render(self):
# if the form has been updated, it will already have a result
if self.form_result is None:
if self.form_reset:
# we reset, in case data has changed in a way that
# causes the widgets to have different data
self.resetForm()
self.form_reset = False
self.form_result = self.template()
return self.form_result
def __call__(self):
self.update()
if self.request.response.getStatus() in [301, 302, 303, 307]:
# Avoid rendering if the action caused a redirect.
result = self.form_result or ''
else:
result = self.render()
return result
def error_views(self):
for error in self.errors:
if isinstance(error, basestring):
yield error
else:
view = component.getMultiAdapter(
(error, self.request),
IWidgetInputErrorView)
title = getattr(error, 'widget_title', None) # duck typing
if title:
if isinstance(title, zope.i18n.Message):
title = zope.i18n.translate(title, context=self.request)
yield '%s: %s' % (title, view.snippet())
else:
yield view.snippet()
def haveInputWidgets(form, action):
for input, widget in form.widgets.__iter_input_and_widget__():
if input:
return True
else:
return False
class EditFormBase(FormBase):
def setUpWidgets(self, ignore_request=False):
self.adapters = {}
self.widgets = setUpEditWidgets(
self.form_fields, self.prefix, self.context, self.request,
adapters=self.adapters, ignore_request=ignore_request
)
@action(_("Apply"), condition=haveInputWidgets)
def handle_edit_action(self, action, data):
descriptions = applyData(self.context, self.form_fields, data,
self.adapters)
if descriptions:
descriptions = [Attributes(interface, *tuple(keys))
for interface, keys in descriptions.items()]
zope.event.notify(ObjectModifiedEvent(self.context, *descriptions))
formatter = self.request.locale.dates.getFormatter(
'dateTime', 'medium')
try:
time_zone = idatetime.ITZInfo(self.request)
except TypeError:
time_zone = pytz.UTC
status = _("Updated on ${date_time}",
mapping={'date_time':
formatter.format(
datetime.datetime.now(time_zone)
)
}
)
self.status = status
else:
self.status = _('No changes')
class DisplayFormBase(FormBase):
def setUpWidgets(self, ignore_request=False):
self.adapters = {}
self.widgets = setUpEditWidgets(
self.form_fields, self.prefix, self.context, self.request,
adapters=self.adapters, for_display=True,
ignore_request=ignore_request
)
actions = ()
@interface.implementer(interfaces.IAddFormCustomization,
zope.component.interfaces.IFactory)
@component.adapter(zope.browser.interfaces.IAdding,
zope.publisher.interfaces.browser.IBrowserRequest)
class AddFormBase(FormBase):
ignoreContext = True
def __init__(self, context, request):
self.__parent__ = context
super(AddFormBase, self).__init__(context, request)
def setUpWidgets(self, ignore_request=False):
self.widgets = setUpInputWidgets(
self.form_fields, self.prefix, self.context, self.request,
ignore_request=ignore_request,
)
@action(_("Add"), condition=haveInputWidgets)
def handle_add(self, action, data):
self.createAndAdd(data)
# zope.formlib.interfaces.IAddFormCustomization
def createAndAdd(self, data):
ob = self.create(data)
zope.event.notify(ObjectCreatedEvent(ob))
return self.add(ob)
def create(self, data):
raise NotImplementedError(
"concrete classes must implement create() or createAndAdd()")
_finished_add = False
def add(self, object):
ob = self.context.add(object)
self._finished_add = True
return ob
def render(self):
if self._finished_add:
self.request.response.redirect(self.nextURL())
return ""
return super(AddFormBase, self).render()
def nextURL(self):
return self.context.nextURL()
default_page_template = namedtemplate.NamedTemplateImplementation(
ViewPageTemplateFile('pageform.pt'), interfaces.IPageForm)
default_subpage_template = namedtemplate.NamedTemplateImplementation(
ViewPageTemplateFile('subpageform.pt'), interfaces.ISubPageForm)
@interface.implementer(interfaces.IPageForm)
class PageForm(FormBase):
pass
Form = PageForm
@interface.implementer(interfaces.IPageForm)
class PageEditForm(EditFormBase):
pass
EditForm = PageEditForm
@interface.implementer(interfaces.IPageForm)
class PageDisplayForm(DisplayFormBase):
pass
DisplayForm = PageDisplayForm
@interface.implementer(interfaces.IPageForm)
class PageAddForm(AddFormBase):
pass
AddForm = PageAddForm
@interface.implementer(interfaces.ISubPageForm)
class SubPageForm(FormBase):
pass
@interface.implementer(interfaces.ISubPageForm)
class SubPageEditForm(EditFormBase):
pass
@interface.implementer(interfaces.ISubPageForm)
class SubPageDisplayForm(DisplayFormBase):
pass
zope.formlib-4.3.0a2/src/zope/formlib/source.py 0000664 0000000 0000000 00000051212 12243456432 020125 0 ustar root root ##############################################################################
#
# Copyright (c) 2004 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.
#
##############################################################################
"""Source widgets support
"""
import xml.sax.saxutils
from zope.component import adapter, getMultiAdapter
from zope.interface import implementer
import zope.browser.interfaces
import zope.schema.interfaces
from zope.schema.interfaces import \
ISourceQueriables, ValidationError, IVocabularyTokenized, IIterableSource
import zope.formlib.interfaces
import zope.formlib.widget
import zope.formlib.itemswidgets
from zope.formlib._compat import imap
from zope.formlib.interfaces import (
WidgetInputError,
MissingInputError,
ISourceQueryView,
IWidgetInputErrorView,
IDisplayWidget,
IInputWidget)
from zope.formlib.i18n import _
from zope.formlib.widgets import (
SelectWidget, RadioWidget, MultiSelectWidget, OrderedMultiSelectWidget,
MultiCheckBoxWidget, MultiSelectSetWidget, MultiSelectFrozenSetWidget)
from zope.formlib.widget import DisplayWidget, InputWidget
from zope.formlib._compat import safeBase64Encode
@implementer(IDisplayWidget)
class SourceDisplayWidget(DisplayWidget):
def __init__(self, field, source, request):
super(SourceDisplayWidget, self).__init__(field, request)
self.source = source
required = False
def hidden(self):
return ''
def error(self):
return ''
def __call__(self):
"""Render the current value
"""
if self._renderedValueSet():
value = self._data
else:
value = self.context.default
if value == self.context.missing_value:
value = self._translate(_("SourceDisplayWidget-missing",
default="Nothing"))
else:
terms = getMultiAdapter((self.source, self.request),
zope.browser.interfaces.ITerms)
try:
term = terms.getTerm(value)
except LookupError:
value = self._translate(_("SourceDisplayWidget-invalid",
default="Invalid value"))
else:
value = self.renderTermForDisplay(term)
return value
def renderTermForDisplay(self, term):
# Provide a rendering of `term` for display; this is not for
# use when generating a select list.
return xml.sax.saxutils.escape(self._translate(term.title))
class SourceSequenceDisplayWidget(SourceDisplayWidget):
separator = ' \n'
def __call__(self):
if self._renderedValueSet():
seq = self._data
else:
seq = self.context.default
terms = getMultiAdapter((self.source, self.request),
zope.browser.interfaces.ITerms)
result = []
for value in seq:
try:
term = terms.getTerm(value)
except LookupError:
value = self._translate(_("SourceDisplayWidget-invalid",
default="Invalid value"))
else:
value = self.renderTermForDisplay(term)
result.append(value)
return self.separator.join(result)
@implementer(IInputWidget)
class SourceInputWidget(InputWidget):
_error = None
def __init__(self, field, source, request):
super(SourceInputWidget, self).__init__(field, request)
self.source = source
self.terms = getMultiAdapter((source, self.request),
zope.browser.interfaces.ITerms)
def queryviews(self):
queriables = ISourceQueriables(self.source, None)
if queriables is None:
# treat the source itself as a queriable
queriables = ((self.name + '.query', self.source), )
else:
queriables = [
(self.name + '.' + safeBase64Encode(i), s)
for (i, s) in queriables.getQueriables()]
return [
(name, getMultiAdapter(
(source, self.request),
ISourceQueryView,
)
) for (name, source) in queriables]
queryviews = property(queryviews)
def _value(self):
if self._renderedValueSet():
value = self._data
else:
for name, queryview in self.queryviews:
if name+'.apply' in self.request:
token = self.request.form.get(name+'.selection')
if token is not None:
break
else:
token = self.request.form.get(self.name)
if token is not None:
try:
value = self.terms.getValue(str(token))
except LookupError:
value = self.context.missing_value
else:
value = self.context.missing_value
return value
def hidden(self):
value = self._value()
if value == self.context.missing_value:
return '' # Nothing to hide ;)
try:
term = self.terms.getTerm(value)
except LookupError:
# A value was set, but it's not valid. Treat
# it as if it was missing and return nothing.
return ''
return (''
% (self.name, xml.sax.saxutils.quoteattr(term.token))
)
def error(self):
if self._error:
# TODO This code path is untested.
return getMultiAdapter((self._error, self.request),
IWidgetInputErrorView).snippet()
return ""
def __call__(self):
result = ['
']
value = self._value()
field = self.context
term = None
if value == field.missing_value:
result.append('
')
return '\n'.join(result)
def _renderResults(self, results, name):
terms = []
for value in results:
term = self.terms.getTerm(value)
terms.append((self._translate(term.title), term.token))
terms.sort()
apply = self._translate(_("SourceInputWidget-apply", default="Apply"))
return (
'\n'
''
% (name,
'\n'.join(
[(''
% (token, title))
for (title, token) in terms]),
name,
apply)
)
def renderTermForDisplay(self, term):
# Provide a rendering of `term` for display; this is not for
# use when generating a select list.
return xml.sax.saxutils.escape(self._translate(term.title))
required = property(lambda self: self.context.required)
def getInputValue(self):
for name, queryview in self.queryviews:
if name+'.apply' in self.request:
token = self.request.form.get(name+'.selection')
if token is not None:
break
else:
token = self.request.get(self.name)
field = self.context
if token is None:
if field.required:
# TODO This code path is untested.
raise MissingInputError(
field.__name__, self.label,
)
return field.missing_value
try:
value = self.terms.getValue(str(token))
except LookupError:
# TODO This code path is untested.
err = zope.schema.interfaces.ValidationError(
"Invalid value id", token)
raise WidgetInputError(field.__name__, self.label, err)
# Remaining code copied from SimpleInputWidget
# value must be valid per the field constraints
try:
field.validate(value)
except ValidationError as err:
# TODO This code path is untested.
self._error = WidgetInputError(field.__name__, self.label, err)
raise self._error
return value
def hasInput(self):
if self.name in self.request or self.name+'.displayed' in self.request:
return True
for name, queryview in self.queryviews:
if name+'.apply' in self.request:
token = self.request.form.get(name+'.selection')
if token is not None:
return True
return False
class SourceListInputWidget(SourceInputWidget):
def _input_value(self):
tokens = self.request.form.get(self.name)
for name, queryview in self.queryviews:
if name+'.apply' in self.request:
newtokens = self.request.form.get(name+'.selection')
if newtokens:
if tokens:
tokens = tokens + newtokens
else:
tokens = newtokens
if tokens:
remove = self.request.form.get(self.name+'.checked')
if remove and (self.name+'.remove' in self.request):
tokens = [token
for token in tokens
if token not in remove
]
value = []
for token in tokens:
try:
v = self.terms.getValue(str(token))
except LookupError:
pass # skip invalid tokens (shrug)
else:
value.append(v)
else:
if self.name+'.displayed' in self.request:
value = []
else:
value = self.context.missing_value
if value:
r = []
seen = {}
for s in value:
if s not in seen:
r.append(s)
seen[s] = 1
value = r
return value
def _value(self):
if self._renderedValueSet():
value = self._data
else:
value = self._input_value()
return value
def hidden(self):
value = self._value()
if value == self.context.missing_value:
return '' # Nothing to hide ;)
result = []
for v in value:
try:
term = self.terms.getTerm(value)
except LookupError:
# A value was set, but it's not valid. Treat
# it as if it was missing and skip
continue
else:
result.append(
''
% (self.name, xml.sax.saxutils.quoteattr(term.token))
)
def __call__(self):
result = ['
']
value = self._value()
if value:
for v in value:
try:
term = self.terms.getTerm(v)
except LookupError:
continue # skip
else:
result.append(
' '
% (self.name, xml.sax.saxutils.quoteattr(term.token))
)
result.append(' ' + self.renderTermForDisplay(term))
result.append(
' '
% (self.name, xml.sax.saxutils.quoteattr(term.token)))
result.append(' ')
result.append(
' '
% (self.name,
self._translate(_("MultipleSourceInputWidget-remove",
default="Remove")))
)
result.append(' ')
result.append(' '
% self.name)
result.append('
')
for name, queryview in self.queryviews:
result.append('
')
return '\n'.join(result)
def _renderResults(self, results, name):
terms = []
apply = self._translate(_("SourceListInputWidget-apply",
default="Apply"))
for value in results:
term = self.terms.getTerm(value)
terms.append((self._translate(term.title), term.token))
terms.sort()
return (
'\n'
''
% (name,
'\n'.join([('' % (token, title))
for (title, token) in terms]),
name,
apply)
)
def getInputValue(self):
value = self._input_value()
# Remaining code copied from SimpleInputWidget
# value must be valid per the field constraints
field = self.context
try:
field.validate(value)
except ValidationError as err:
# TODO This code path is untested.
self._error = WidgetInputError(field.__name__, self.label, err)
raise self._error
return value
def hasInput(self):
return self.name+'.displayed' in self.request.form
# Input widgets for IIterableSource:
# These widgets reuse the old-style vocabulary widgets via the class
# IterableSourceVocabulary that adapts a source (and its ITerms object)
# into a vocabulary. When/if vocabularies go away, these classes
# should be updated into full implementations.
@implementer(IVocabularyTokenized)
@adapter(IIterableSource)
class IterableSourceVocabulary(object):
"""Adapts an iterable source into a legacy vocabulary.
This can be used to wrap sources to make them usable with widgets that
expect vocabularies. Note that there must be an ITerms implementation
registered to obtain the terms.
"""
def __init__(self, source, request):
self.source = source
self.terms = getMultiAdapter((source, request),
zope.browser.interfaces.ITerms)
def getTerm(self, value):
return self.terms.getTerm(value)
def getTermByToken(self, token):
value = self.terms.getValue(token)
return self.getTerm(value)
def __iter__(self):
return imap(
lambda value: self.getTerm(value), self.source.__iter__())
def __len__(self):
return self.source.__len__()
def __contains__(self, value):
return self.source.__contains__(value)
class SourceSelectWidget(SelectWidget):
"""Provide a selection list for the item."""
def __init__(self, field, source, request):
super(SourceSelectWidget, self).__init__(
field, IterableSourceVocabulary(source, request), request)
# BBB
if not zope.formlib.itemswidgets.EXPLICIT_EMPTY_SELECTION:
# Even if the field is required, no input is needed, so don't
# worry the user about it:
self.required = False
class SourceDropdownWidget(SourceSelectWidget):
"""Variation of the SourceSelectWidget that uses a drop-down list."""
size = 1
explicit_empty_selection = True
class SourceRadioWidget(RadioWidget):
"""Radio widget for single item choices."""
def __init__(self, field, source, request):
super(SourceRadioWidget, self).__init__(
field, IterableSourceVocabulary(source, request), request)
class SourceMultiSelectWidget(MultiSelectWidget):
"""A multi-selection widget with ordering support."""
def __init__(self, field, source, request):
super(SourceMultiSelectWidget, self).__init__(
field, IterableSourceVocabulary(source, request), request)
class SourceOrderedMultiSelectWidget(OrderedMultiSelectWidget):
"""A multi-selection widget with ordering support."""
def __init__(self, field, source, request):
super(SourceOrderedMultiSelectWidget, self).__init__(
field, IterableSourceVocabulary(source, request), request)
class SourceMultiSelectSetWidget(MultiSelectSetWidget):
"""Provide a selection list for the set to be selected."""
def __init__(self, field, source, request):
super(SourceMultiSelectSetWidget, self).__init__(
field, IterableSourceVocabulary(source, request), request)
class SourceMultiSelectFrozenSetWidget(MultiSelectFrozenSetWidget):
"""Provide a selection list for the frozenset to be selected."""
def __init__(self, field, source, request):
super(SourceMultiSelectFrozenSetWidget, self).__init__(
field, IterableSourceVocabulary(source, request), request)
class SourceMultiCheckBoxWidget(MultiCheckBoxWidget):
"""Provide a list of checkboxes that provide the choice for the list."""
def __init__(self, field, source, request):
super(SourceMultiCheckBoxWidget, self).__init__(
field, IterableSourceVocabulary(source, request), request)
zope.formlib-4.3.0a2/src/zope/formlib/ftesting.zcml 0000664 0000000 0000000 00000001230 12243456432 020760 0 ustar root root
zope.formlib-4.3.0a2/src/zope/formlib/source.txt 0000664 0000000 0000000 00000110056 12243456432 020316 0 ustar root root ==============
Source Widgets
==============
Sources are objects that represent sets of values from which one might choose
and are used with Choice schema fields. Source widgets currently fall into two
categories:
- widgets for iterable sources
- widgets for queryable sources
Sources (combined with the available adapters) may support both approaches, but
no widgets currently support both.
In both cases, the widgets need views that can be used to get tokens to
represent source values in forms, as well as textual representations of values.
We use the `zope.browser.interfaces.ITerms` views for that.
All of our examples will be using the component architecture::
>>> import zope.interface
>>> import zope.component
>>> import zope.schema
This `ITerms` implementation can be used for the sources involved in
our tests::
>>> import base64
>>> import binascii
>>> from zope.browser.interfaces import ITerms
>>> import zope.publisher.interfaces.browser
>>> from zope.schema.vocabulary import SimpleTerm
>>> from zope.formlib._compat import toUnicode
>>> @zope.interface.implementer(ITerms)
... class ListTerms:
...
... def __init__(self, source, request):
... pass # We don't actually need the source or the request :)
...
... def getTerm(self, value):
... title = toUnicode(value)
... try:
... # This convoluted mess makes it Py2 and Py3 friendly.
... token = str(base64.b64encode(title.encode()).strip().decode())
... except binascii.Error:
... raise LookupError(token)
... return SimpleTerm(value, token=token, title=title)
...
... def getValue(self, token):
... return base64.b64decode(token).decode()
This view just uses the unicode representations of values as titles and the
base-64 encoding of the titles as tokens. This is a very simple strategy
that's only approriate when the values have short and unique unicode
representations.
All of the source widgets are in a single module::
>>> import zope.formlib.source
We'll also need request objects::
>>> from zope.publisher.browser import TestRequest
Iterable Source Widgets
=======================
Iterable sources are expected to be simpler than queriable sources, so they
represent a good place to start. The most important aspect of iterable sources
for widgets is that it's actually possible to enumerate all the values from the
source. This allows each possible value to be listed in a