Skip to content

Commit

Permalink
A better fix for #81: don't break with non-ascii strings as values.
Browse files Browse the repository at this point in the history
  • Loading branch information
dnouri committed Mar 31, 2012
1 parent 494db2b commit a22c0c4
Show file tree
Hide file tree
Showing 6 changed files with 65 additions and 18 deletions.
2 changes: 1 addition & 1 deletion deform/templates/checkbox_choice.pt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<input type="hidden" name="__start__" value="${field.name}:sequence"/>
<ul class="deformSet">
<tal:loop tal:repeat="choice field.widget.values">
<tal:loop tal:repeat="choice values">
<tal:def tal:define="(value, title) choice">
<li class="deformSet-item">
<input tal:attributes="checked value in cstruct;
Expand Down
2 changes: 1 addition & 1 deletion deform/templates/readonly/checkbox_choice.pt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<div>
<tal:loop tal:repeat="choice field.widget.values">
<tal:loop tal:repeat="choice values">
<tal:def tal:define="(value, description) choice">
<span>${description}</span>
<em id="${field.oid}-${repeat.choice.index}"
Expand Down
2 changes: 1 addition & 1 deletion deform/templates/readonly/select.pt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<p tal:repeat="(value, description) field.widget.values" i18n:domain="deform">
<p tal:repeat="(value, description) values" i18n:domain="deform">
<span>${description}</span>
<em tal:condition="value == cstruct" i18n:translate="">Selected</em>
<em tal:condition="value != cstruct" i18n:translate="">Not Selected</em>
Expand Down
4 changes: 2 additions & 2 deletions deform/templates/select.pt
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
id="${field.oid}"
tal:attributes="size field.widget.size"
tal:attributes="class field.widget.css_class">
<option tal:repeat="(value, description) field.widget.values"
tal:attributes="selected str(value) == cstruct and 'selected';
<option tal:repeat="(value, description) values"
tal:attributes="selected value == cstruct and 'selected';
class field.widget.css_class"
value="${value}">${description}</option>
</select>
36 changes: 36 additions & 0 deletions deform/tests/test_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,14 @@ def test_serialize_not_null_readonly(self):
self.assertEqual(renderer.kw['field'], field)
self.assertEqual(renderer.kw['cstruct'], cstruct)

def test_serialize_integer_values(self):
renderer = DummyRenderer()
schema = DummySchema()
field = DummyField(schema, renderer)
widget = self._makeOne(values=((1, 'one'),))
widget.serialize(field, None)
self.assertEqual(renderer.kw['values'], [('1', 'one')])

def test_deserialize_null(self):
from colander import null
widget = self._makeOne()
Expand Down Expand Up @@ -656,6 +664,14 @@ def test_serialize_not_null_readonly(self):
self.assertEqual(renderer.kw['field'], field)
self.assertEqual(renderer.kw['cstruct'], cstruct)

def test_serialize_integer_values(self):
renderer = DummyRenderer()
schema = DummySchema()
field = DummyField(schema, renderer)
widget = self._makeOne(values=((1, 'one'),))
widget.serialize(field, None)
self.assertEqual(renderer.kw['values'], [('1', 'one')])

def test_deserialize_null(self):
from colander import null
widget = self._makeOne()
Expand Down Expand Up @@ -1639,6 +1655,26 @@ def test___call___leaf_isnt_iterable(self):
result = reg([('abc', '123')])
self.assertEqual(result, {'js':['123'], 'css':['2']})

class TestNormalizeChoices(unittest.TestCase):
def _call(self, values):
from deform.widget import _normalize_choices
return _normalize_choices(values)

def test_empty(self):
self.assertEqual(self._call(()), [])

def test_string(self):
self.assertEqual(self._call((('value', 'description'),)),
[('value', 'description')])

def test_text_type(self):
self.assertEqual(self._call(((text_type('value'), 'description'),)),
[('value', 'description')])

def test_integer(self):
self.assertEqual(self._call(((1, 'description'),)),
[('1', 'description')])

class DummyRenderer(object):
def __init__(self, result=''):
self.result = result
Expand Down
37 changes: 24 additions & 13 deletions deform/widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@
uppercase,
)

def _normalize_choices(values):
result = []
for value, description in values:
if not isinstance(value, string_types):
value = str(value)
result.append((value, description))
return result

class Widget(object):
"""
A widget is the building block for rendering logic. The
Expand Down Expand Up @@ -613,7 +621,6 @@ class SelectWidget(Widget):
**Attributes/Arguments**
values
A sequence of two-tuples (the first value must be of type
string, unicode or integer, the second value must be string or
unicode) indicating allowable, displayed values, e.g. ``(
Expand Down Expand Up @@ -650,7 +657,8 @@ def serialize(self, field, cstruct, readonly=False):
if cstruct in (null, None):
cstruct = self.null_value
template = readonly and self.readonly_template or self.template
return field.renderer(template, field=field, cstruct=cstruct)
return field.renderer(template, field=field, cstruct=cstruct,
values=_normalize_choices(self.values))

def deserialize(self, field, pstruct):
if pstruct in (null, self.null_value):
Expand All @@ -665,11 +673,12 @@ class RadioChoiceWidget(SelectWidget):
**Attributes/Arguments**
values
A sequence of two-tuples (both values must be **string** or
**unicode** values) indicating allowable, displayed values,
e.g. ``( ('true', 'True'), ('false', 'False') )``. The first
element in the tuple is the value that should be returned when
the form is posted. The second is the display value.
A sequence of two-tuples (the first value must be of type
string, unicode or integer, the second value must be string or
unicode) indicating allowable, displayed values, e.g. ``(
('true', 'True'), ('false', 'False') )``. The first element
in the tuple is the value that should be returned when the
form is posted. The second is the display value.
template
The template name used to render the widget. Default:
Expand All @@ -695,11 +704,12 @@ class CheckboxChoiceWidget(Widget):
**Attributes/Arguments**
values
A sequence of two-tuples (both values must be **string** or
**unicode** values) indicating allowable, displayed values,
e.g. ``( ('true', 'True'), ('false', 'False') )``. The first
element in the tuple is the value that should be returned when
the form is posted. The second is the display value.
A sequence of two-tuples (the first value must be of type
string, unicode or integer, the second value must be string or
unicode) indicating allowable, displayed values, e.g. ``(
('true', 'True'), ('false', 'False') )``. The first element
in the tuple is the value that should be returned when the
form is posted. The second is the display value.
template
The template name used to render the widget. Default:
Expand All @@ -722,7 +732,8 @@ def serialize(self, field, cstruct, readonly=False):
if cstruct in (null, None):
cstruct = ()
template = readonly and self.readonly_template or self.template
return field.renderer(template, field=field, cstruct=cstruct)
return field.renderer(template, field=field, cstruct=cstruct,
values=_normalize_choices(self.values))

def deserialize(self, field, pstruct):
if pstruct is null:
Expand Down

0 comments on commit a22c0c4

Please sign in to comment.