Skip to content

Commit 5528519

Browse files
authored
Merge pull request #21 from timheap/bug/18-validation-errors
Keep composite field values after a validation error
2 parents b66d379 + c175a07 commit 5528519

2 files changed

Lines changed: 60 additions & 6 deletions

File tree

postgres_composite_types/forms.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,16 @@ def __init__(self, *args, fields=None, model=None, **kwargs):
109109
self.widget.widgets.values()):
110110
widget.attrs['placeholder'] = field.label
111111

112+
def prepare_value(self, value):
113+
"""
114+
Prepare the field data for the CompositeTypeWidget, which expects data
115+
as a dict.
116+
"""
117+
if isinstance(value, CompositeType):
118+
return value.__to_dict__()
119+
120+
return value
121+
112122
def validate(self, value):
113123
pass
114124

@@ -155,7 +165,9 @@ def get_bound_field(self, form, field_name):
155165

156166
class CompositeTypeWidget(forms.Widget):
157167
"""
158-
Takes an ordered dict of widgets to produce a composite form widget
168+
Takes an ordered dict of widgets to produce a composite form widget. This
169+
widget knows nothing about CompositeTypes, and works only with dicts for
170+
initial and output data.
159171
"""
160172
template_name = \
161173
'postgres_composite_types/forms/widgets/composite_type.html'
@@ -170,7 +182,7 @@ def __init__(self, widgets, **kwargs):
170182

171183
@property
172184
def is_hidden(self):
173-
return all(w.is_hidden for w in self.widgets)
185+
return all(w.is_hidden for w in self.widgets.values())
174186

175187
def get_context(self, name, value, attrs):
176188
context = super().get_context(name, value, attrs)
@@ -187,12 +199,9 @@ def get_context(self, name, value, attrs):
187199
if id_:
188200
widget_attrs['id'] = '%s-%s' % (id_, subname)
189201

190-
subwidgets[subname] = widget.render('%s-%s' % (name, subname),
191-
getattr(value, subname, None),
192-
final_attrs)
193202
widget_context = widget.get_context(
194203
'%s-%s' % (name, subname),
195-
getattr(value, subname, None),
204+
value.get(subname),
196205
widget_attrs)
197206
subwidgets[subname] = widget_context['widget']
198207

tests/test_forms.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from django import forms
55
from django.test import SimpleTestCase
6+
from django.test.testcases import assert_and_parse_html
67

78
from postgres_composite_types.forms import CompositeTypeField
89

@@ -56,6 +57,14 @@ def test_validation(self):
5657
self.assertEqual(str(form.errors['simple_field'][0]),
5758
'A number: Enter a whole number.')
5859

60+
# Fields with validation errors should render with their invalid input
61+
self.assertHTMLContains(
62+
"""
63+
<input id="id_simple_field-a" name="simple_field-a"
64+
placeholder="A number" required type="number" value="one" />
65+
""",
66+
str(form['simple_field']))
67+
5968
def test_subfield_validation(self):
6069
"""Errors on subfields should be accessible"""
6170
form = self.SimpleForm(data={
@@ -85,6 +94,42 @@ def test_nested_prefix(self):
8594
self.assertEqual(a_bound_field.html_name,
8695
'step1-simple_field-a')
8796

97+
def test_initial_data(self):
98+
"""
99+
Check that forms with initial data render with the fields prepopulated.
100+
"""
101+
initial = SimpleType(
102+
a=1, b='foo', c=datetime.datetime(2016, 5, 24, 17, 38, 32))
103+
form = self.SimpleForm(initial={'simple_field': initial})
104+
105+
self.assertHTMLContains(
106+
"""
107+
<input id="id_simple_field-a" name="simple_field-a"
108+
placeholder="A number" required type="number" value="1" />
109+
""",
110+
str(form['simple_field']))
111+
112+
# pylint:disable=invalid-name
113+
def assertHTMLContains(self, text, content, count=None, msg=None):
114+
"""
115+
Assert that the HTML snippet ``text`` is found within the HTML snippet
116+
``content``. Like assertContains, but works with plain strings instead
117+
of Response instances.
118+
"""
119+
content = assert_and_parse_html(
120+
self, content, None, "HTML content to search in is not valid:")
121+
text = assert_and_parse_html(
122+
self, text, None, "HTML content to search for is not valid:")
123+
124+
matches = content.count(text)
125+
if count is None:
126+
self.assertTrue(
127+
matches > 0, msg=msg or 'Could not find HTML snippet')
128+
else:
129+
self.assertEqual(
130+
matches, count,
131+
msg=msg or 'Found %d matches, expecting %d' % (matches, count))
132+
88133

89134
class OptionalFieldTests(SimpleTestCase):
90135
"""

0 commit comments

Comments
 (0)