Skip to content

Commit

Permalink
checkInvariants now takes the context of the form into account when…
Browse files Browse the repository at this point in the history
… checking interface invariants.
  • Loading branch information
Michael Howitz committed Mar 15, 2012
1 parent 3d9c9f4 commit 2231f3f
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 19 deletions.
5 changes: 4 additions & 1 deletion CHANGES.txt
Expand Up @@ -2,9 +2,12 @@
Changes
=======

4.0.7 (unreleased)
4.1.0 (unreleased)
==================

- `checkInvariants` now takes the context of the form into account when
checking interface invariants.


4.0.6 (2011-08-20)
==================
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Expand Up @@ -24,7 +24,7 @@
def read(*rnames):
return open(os.path.join(os.path.dirname(__file__), *rnames)).read()

version = '4.0.7dev'
version = '4.1.0dev'

setup(name='zope.formlib',
version=version,
Expand Down
26 changes: 21 additions & 5 deletions src/zope/formlib/form.py
Expand Up @@ -459,21 +459,37 @@ class NoInputData(interface.Invalid):

class FormData:

def __init__(self, schema, data):
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:
raise NoInputData(name)
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(
Expand All @@ -486,7 +502,7 @@ def __getattr__(self, name):
raise AttributeError(name)


def checkInvariants(form_fields, form_data):
def checkInvariants(form_fields, form_data, context):

# First, collect the data for the various schemas
schema_data = {}
Expand All @@ -506,7 +522,7 @@ def checkInvariants(form_fields, form_data):
errors = []
for schema, data in schema_data.items():
try:
schema.validateInvariants(FormData(schema, data), errors)
schema.validateInvariants(FormData(schema, data, context), errors)
except interface.Invalid:
pass # Just collect the errors

Expand Down Expand Up @@ -740,7 +756,7 @@ def setUpWidgets(self, ignore_request=False):

def validate(self, action, data):
return (getWidgetsData(self.widgets, self.prefix, data)
+ checkInvariants(self.form_fields, data))
+ checkInvariants(self.form_fields, data, self.context))

template = namedtemplate.NamedTemplate('default')

Expand Down
25 changes: 13 additions & 12 deletions src/zope/formlib/form.txt
Expand Up @@ -227,7 +227,7 @@ and max size. If we provide an invalid value, we'll get an error too:
<input class="textType" id="form.max_size" name="form.max_size" size="10"
type="text" value="" />
<span class="error">Required input is missing.</span>
<input class="textType" id="form.color" name="form.color" size="20"
<input class="textType" id="form.color" name="form.color" size="20"
type="text" value="" />
{'name': u'bob'}

Expand Down Expand Up @@ -288,8 +288,8 @@ We can update our form to check the invariant using 'checkInvariants':
... if 'submit' in self.request:
... data = {}
... errors = form.getWidgetsData(widgets, 'form', data)
... invariant_errors = form.checkInvariants(self.form_fields,
... data)
... invariant_errors = form.checkInvariants(
... self.form_fields, data, self.context)
... if errors:
... print 'There were field errors:'
... for error in errors:
Expand Down Expand Up @@ -504,8 +504,8 @@ object. We can use the `applyChanges` function for that:
... if 'submit' in self.request:
... data = {}
... errors = form.getWidgetsData(widgets, 'form', data)
... invariant_errors = form.checkInvariants(self.form_fields,
... data)
... invariant_errors = form.checkInvariants(
... self.form_fields, data, self.context)
... if errors:
... print 'There were field errors:'
... for error in errors:
Expand Down Expand Up @@ -698,7 +698,8 @@ Here's the new version:
...
... def validate(self, action, data):
... return (form.getWidgetsData(self.widgets, self.prefix, data) +
... form.checkInvariants(self.form_fields, data))
... form.checkInvariants(
... self.form_fields, data, self.context))
...
... def handle_edit_action(self, action, data):
... if form.applyChanges(self.context, self.form_fields, data):
Expand Down Expand Up @@ -1566,15 +1567,15 @@ Now try the same with the AddFormBase which uses a setUpInputWidget:
... super(MyAddForm, self).setUpWidgets(ignore_request)

>>> print MyAddForm(None, request)() # doctest: +NORMALIZE_WHITESPACE
<input class="textType" id="form.identifier" name="form.identifier"
<input class="textType" id="form.identifier" name="form.identifier"
size="10" type="text" value="" />
<input class="textType" id="form.name" name="form.name" size="20"
<input class="textType" id="form.name" name="form.name" size="20"
type="text" value="" />
<input class="textType" id="form.min_size" name="form.min_size"
<input class="textType" id="form.min_size" name="form.min_size"
size="10" type="text" value="" />
<input class="textType" id="form.max_size" name="form.max_size"
<input class="textType" id="form.max_size" name="form.max_size"
size="10" type="text" value="" />
<input class="textType" id="form.now" name="form.now" size="20"
<input class="textType" id="form.now" name="form.now" size="20"
type="text" value="2002-12-02 12:30:00" />

Note that a EditForm can't make use of a get_rendered method. The get_rendered
Expand Down Expand Up @@ -1801,7 +1802,7 @@ Here are some more places where the behavior was incorrect:
<input class="textType" id="form.title" name="form.title"
size="20" type="text" value="" />

>>> form.checkInvariants(form_fields, {'title': 'new'})
>>> form.checkInvariants(form_fields, {'title': 'new'}, blah)
[]

>>> form.applyChanges(blah, form_fields, {'title': 'new'})
Expand Down
81 changes: 81 additions & 0 deletions src/zope/formlib/tests/test_formlib.py
Expand Up @@ -630,6 +630,87 @@ def check_action_name():
"""


def checkInvariants_falls_back_to_context_if_value_not_in_form():
"""
`checkInvariants` is able to access the values from the form and the context to
make sure invariants are not violated:
>>> class IFlexMaximum(zope.interface.Interface):
... max = zope.schema.Int(title=u"Maximum")
... value = zope.schema.Int(title=u"Value")
...
... @zope.interface.invariant
... def value_not_bigger_than_max(data):
... if data.value > data.max:
... raise zope.interface.Invalid('value bigger than max')
>>> class Content(object):
... zope.interface.implements(IFlexMaximum)
... max = 10
... value = 7
>>> class ValueEditForm(zope.formlib.form.EditForm):
... form_fields = zope.formlib.form.FormFields(
... IFlexMaximum).omit('max')
If the value entered in the example form is bigger than the maximum the
interface invariant triggers an error:
>>> from zope.publisher.browser import TestRequest
>>> request = TestRequest(
... form={'form.value': 42, 'form.actions.apply': '1'})
>>> form = ValueEditForm(Content(), request)
>>> form.update()
>>> form.errors
(Invalid('value bigger than max',),)
If the value is below the maximum no error occures:
>>> request = TestRequest(
... form={'form.value': 8, 'form.actions.apply': '1'})
>>> form = ValueEditForm(Content(), request)
>>> form.update()
>>> form.errors
()
"""


def FormData___getattr___handles_zope_interrface_attributes_correctly():
"""
`FormData.__getattr__` reads objects defined as zope.interface.Attribute in
interface correctly from context:
>>> class IStaticMaximum(zope.interface.Interface):
... max = zope.interface.Attribute("Predefined maximum")
>>> class Content(object):
... zope.interface.implements(IStaticMaximum)
... max = 10
>>> formdata = zope.formlib.form.FormData(IStaticMaximum, {}, Content())
>>> formdata.max
10
"""


def FormData___getattr___raises_exception_if_unknown_how_to_access_value():
"""
`FormData.__getattr__` raises an exception if it cannot determine how to
read the object from context:
>>> class IStaticMaximum(zope.interface.Interface):
... def max(): pass
>>> class Content(object):
... zope.interface.implements(IStaticMaximum)
>>> formdata = zope.formlib.form.FormData(IStaticMaximum, {}, Content())
>>> formdata.max
Traceback (most recent call last):
NoInputData: max
"""


def test_suite():
import doctest
checker = zope.testing.renormalizing.RENormalizing([
Expand Down

0 comments on commit 2231f3f

Please sign in to comment.