Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add separator to FieldList #694

Merged
merged 4 commits into from
Sep 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Unreleased
- Support for optgroups in :class:`~fields.SelectField` and
:class:`~fields.SelectMultipleField`. :issue:`656` :pr:`667`
- Minor documentation fix. :issue:`701`
- Custom separators for :class:`~fields.FieldList`. :issue:`681` :pr:`694`

Version 3.0.0a1
---------------
Expand Down
2 changes: 1 addition & 1 deletion docs/fields.rst
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@ complex data structures such as lists and nested objects can be represented.
:attr:`~wtforms.form.Form.data` dict of the enclosed form. Similarly, the
`errors` property encapsulate the forms' errors.

.. autoclass:: FieldList(unbound_field, default field arguments, min_entries=0, max_entries=None)
.. autoclass:: FieldList(unbound_field, default field arguments, min_entries=0, max_entries=None, separator='-')

**Note**: Due to a limitation in how HTML sends values, FieldList cannot enclose
:class:`BooleanField` or :class:`SubmitField` instances.
Expand Down
14 changes: 10 additions & 4 deletions src/wtforms/fields/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -1105,6 +1105,9 @@ class FieldList(Field):
:param max_entries:
accept no more than this many entries as input, even if more exist in
formdata.
:param separator:
A string which will be suffixed to this field's name to create the
prefix to enclosed list entries. The default is fine for most uses.
"""

widget = widgets.ListWidget()
Expand All @@ -1116,6 +1119,7 @@ def __init__(
validators=None,
min_entries=0,
max_entries=None,
separator="-",
default=(),
**kwargs,
):
Expand All @@ -1133,6 +1137,8 @@ def __init__(
self.max_entries = max_entries
self.last_index = -1
self._prefix = kwargs.get("_prefix", "")
self._separator = separator
self._field_separator = unbound_field.kwargs.get("separator", "-")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a time where _separator and _field_separator would diverge in value?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, see test_enclosed_subform_mixed_separators for example. When assigning a separator to the contained form field but not to the containing list these fields diverge.


def process(self, formdata, data=unset_value, extra_filters=None):
if extra_filters:
Expand Down Expand Up @@ -1175,12 +1181,12 @@ def _extract_indices(self, prefix, formdata):

formdata must be an object which will produce keys when iterated. For
example, if field 'foo' contains keys 'foo-0-bar', 'foo-1-baz', then
the numbers 0 and 1 will be yielded, but not neccesarily in order.
the numbers 0 and 1 will be yielded, but not necessarily in order.
"""
offset = len(prefix) + 1
for k in formdata:
if k.startswith(prefix):
k = k[offset:].split("-", 1)[0]
k = k[offset:].split(self._field_separator, 1)[0]
if k.isdigit():
yield int(k)

Expand Down Expand Up @@ -1232,8 +1238,8 @@ def _add_entry(self, formdata=None, data=unset_value, index=None):
if index is None:
index = self.last_index + 1
self.last_index = index
name = "%s-%d" % (self.short_name, index)
id = "%s-%d" % (self.id, index)
name = f"{self.short_name}{self._separator}{index}"
id = f"{self.id}{self._separator}{index}"
field = self.unbound_field.bind(
form=None,
name=name,
Expand Down
62 changes: 62 additions & 0 deletions tests/fields/test_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,68 @@ class Outside(Form):
assert o.subforms[0].foo.data == "default"


def test_custom_separator():
F = make_form(a=FieldList(t, separator="_"))

pdata = DummyPostData({"a_0": "0_a", "a_1": "1_a"})
f = F(pdata)
assert f.a[0].data == "0_a"
assert f.a[1].data == "1_a"


def test_enclosed_subform_list_separator():
class Inside(Form):
foo = StringField(default="default")

class Outside(Form):
subforms = FieldList(FormField(Inside), min_entries=1, separator="_")

o = Outside()
assert o.subforms[0].foo.data == "default"
assert o.subforms[0].foo.name == "subforms_0-foo"

pdata = DummyPostData({"subforms_0-foo": "0-foo", "subforms_1-foo": "1-foo"})
o = Outside(pdata)
assert o.subforms[0].foo.data == "0-foo"
assert o.subforms[1].foo.data == "1-foo"


def test_enclosed_subform_uniform_separators():
class Inside(Form):
foo = StringField(default="default")

class Outside(Form):
subforms = FieldList(
FormField(Inside, separator="_"), min_entries=1, separator="_"
)

o = Outside()
assert o.subforms[0].foo.data == "default"
assert o.subforms[0].foo.name == "subforms_0_foo"

pdata = DummyPostData({"subforms_0_foo": "0_foo", "subforms_1_foo": "1_foo"})
o = Outside(pdata)
assert o.subforms[0].foo.data == "0_foo"
assert o.subforms[1].foo.data == "1_foo"


def test_enclosed_subform_mixed_separators():
class Inside(Form):
foo = StringField(default="default")

class Outside(Form):
subforms = FieldList(FormField(Inside, separator="_"), min_entries=1)

o = Outside()
assert o.subforms[0].foo.data == "default"
assert o.subforms[0].foo.name == "subforms-0_foo"

pdata = DummyPostData({"subforms-0_foo": "0_foo", "subforms-1_foo": "1_foo"})
o = Outside(pdata)
assert o.subforms[0].foo.data == "0_foo"
assert o.subforms[1].foo.data == "1_foo"


def test_entry_management():
F = make_form(a=FieldList(t))
a = F(a=["hello", "bye"]).a
Expand Down