Skip to content
This repository has been archived by the owner on Sep 5, 2019. It is now read-only.

Commit

Permalink
Adds basic fieldset support
Browse files Browse the repository at this point in the history
  • Loading branch information
Denis Krienbühl committed May 19, 2015
1 parent 27901e3 commit 0b517f8
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 1 deletion.
82 changes: 82 additions & 0 deletions onegov/form/core.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,99 @@
import weakref

from collections import OrderedDict
from itertools import groupby
from operator import itemgetter
from wtforms import Form as BaseForm


class Form(BaseForm):
""" Extends wtforms.Form with useful methods and integrations needed in
OneGov applications.
This form supports fieldsets (which WTForms doesn't recognize). To put
fields into a fieldset, add a fieldset attribute to the field during
class definition::
class MyForm(Form):
first_name = StringField('First Name', fieldset='Name')
last_name = StringField('Last Name', fieldset='Name')
comment = StringField('Comment')
A form created like this will have two fieldsets, one visible fieldset
with the legend set to 'Name' and one invisible fieldset containing
'comment'.
Fieldsets with the same name are *not* automatically grouped together.
Instead, fields are taken in the order they are defined and put into the
same fieldset, if the previous fieldset has the same name.
That is to say, in this example, we get three fieldsets::
class MyForm(Form):
a = StringField('A', fieldset='1')
b = StringField('B', fieldset='2')
c = StringField('C', fieldset='1')
The first fieldset has the label '1' and it contains 'a'. The second
fieldset has the label '2' and it contains 'b'. The third fieldset has
the label '3' and it contains 'c'.
This ensures that all fields are in either a visible or an invisible
fieldset (see :meth:`Fieldset.is_visible`).
"""

def __init__(self, *args, **kwargs):

# consume the fieldset attribute of all unbound fields, as WTForms
# doesn't know it
fields_by_fieldset = [
(field.kwargs.pop('fieldset', None), field_id)
for field_id, field in self._unbound_fields
]

super(Form, self).__init__(*args, **kwargs)

# use the consumed fieldset attribute to build fieldsets
self.fieldsets = []

for label, fields in groupby(fields_by_fieldset, key=itemgetter(0)):
self.fieldsets.append(Fieldset(
label=label,
fields=(self._fields[f[1]] for f in fields)
))

def submitted(self, request):
""" Returns true if the given request is a successful post request. """
return request.POST and self.validate()


class Fieldset(object):
""" Defines a fieldset with a list of fields. """

def __init__(self, label, fields):
""" Initializes the Fieldset.
:label: Label of the fieldset (None if it's an invisible fieldset)
:fields: Iterator of bound fields. Fieldset creates a list of weak
references to these fields, as they are defined elsewhere and should
not be kept in memory just because a Fieldset references them.
"""
self.label = label
self.fields = OrderedDict((f.id, weakref.proxy(f)) for f in fields)

def __len__(self):
return len(self.fields)

def __getitem__(self, key):
return self.fields[key]

@property
def is_visible(self):
return self.label is not None


def with_options(widget_class, **render_options):
""" Takes a widget class and returns a child-instance of the widget class,
with the given options set on the render call.
Expand Down
7 changes: 6 additions & 1 deletion onegov/form/parser/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class (not an instance of it).
for block in (i[0] for i in doc.scanString(text)):

if block.type == 'fieldset':
raise NotImplementedError
builder.set_current_fieldset(block.label or None)

elif block.type == 'button':
raise NotImplementedError
Expand Down Expand Up @@ -77,6 +77,10 @@ class DynamicForm(form_class):
pass

self.form_class = DynamicForm
self.current_fieldset = None

def set_current_fieldset(self, label):
self.current_fieldset = label

def add_field(self, field_class, label, required, **kwargs):
validators = kwargs.pop('validators', [])
Expand All @@ -89,5 +93,6 @@ def add_field(self, field_class, label, required, **kwargs):
setattr(self.form_class, field_id, field_class(
label=label,
validators=validators,
fieldset=self.current_fieldset,
**kwargs
))
39 changes: 39 additions & 0 deletions onegov/form/tests/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,42 @@ def test_parse_text():
assert form.comment.label.text == 'Comment'
assert form.comment.widget(form.comment) == (
'<textarea id="comment" name="comment" rows="8"></textarea>')


def test_parse_fieldsets():
text = dedent("""
# Name
First name = ___
Last name = ___
# Address
Street = ___
# ...
Comment = ___
""")

form_class = parse_form(text)
form = form_class()

fields = form._fields.values()
assert len(fields) == 4

fieldsets = form.fieldsets
assert len(fieldsets) == 3

assert len(fieldsets[0]) == 2
assert fieldsets[0].label == 'Name'
assert fieldsets[0].is_visible
assert fieldsets[0]['first_name'].label.text == 'First name'
assert fieldsets[0]['last_name'].label.text == 'Last name'

assert len(fieldsets[1]) == 1
assert fieldsets[1].label == 'Address'
assert fieldsets[1].is_visible
assert fieldsets[1]['street'].label.text == 'Street'

assert len(fieldsets[2]) == 1
assert fieldsets[2].label is None
assert not fieldsets[2].is_visible
assert fieldsets[2]['comment'].label.text == 'Comment'

0 comments on commit 0b517f8

Please sign in to comment.