Skip to content

Simple fields customizable #123

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

Closed
wants to merge 2 commits into from
Closed
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
91 changes: 69 additions & 22 deletions django_mysql/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,30 @@

class SimpleListField(forms.CharField):

# These bits can be overridden to change the way the field serializes and
# deserializes for the user, e.g. line-delimited, json, etc.

default_error_messages = {
'item_n_invalid': _('Item %(nth)s in the list did not validate: '),
'no_double_commas': _('No leading, trailing, or double commas.'),
'items_no_commas': _('No leading, trailing, or double commas.'),
# The 'empty' message is the same as 'no commas' by default, since the
# only reason empty strings could arise with the basic comma-splitting
# logic is with extra commas. This may not be true in custom subclasses
# however.
'items_no_empty': _('No leading, trailing, or double commas.'),
}

def prepare_value_serialize(self, values):
return ",".join(values)

def to_python_deserialize(self, value):
if not value:
return []
else:
return value.split(",")

# Internals

def __init__(self, base_field, max_length=None, min_length=None,
*args, **kwargs):
self.base_field = base_field
Expand All @@ -36,25 +55,29 @@ def __init__(self, base_field, max_length=None, min_length=None,

def prepare_value(self, value):
if isinstance(value, list):
return ",".join(
six.text_type(self.base_field.prepare_value(v))
for v in value
return self.prepare_value_serialize(
(six.text_type(self.base_field.prepare_value(v))
for v in value)
)
return value

def to_python(self, value):
if value and len(value):
items = value.split(",")
else:
items = []
items = self.to_python_deserialize(value)

errors = []
values = []
for i, item in enumerate(items, start=1):
if not len(item):
errors.append(ValidationError(
self.error_messages['no_double_commas'],
code='no_double_commas',
self.error_messages['items_no_empty'],
code='items_no_empty',
))
continue

if ',' in item:
errors.append(ValidationError(
self.error_messages['items_no_commas'],
code='items_no_commas',
))
continue

Expand Down Expand Up @@ -116,16 +139,36 @@ def run_validators(self, value):


class SimpleSetField(forms.CharField):
empty_values = list(validators.EMPTY_VALUES) + [set()]

# These bits can be overridden to change the way the field serializes and
# deserializes for the user, e.g. line-delimited, json, etc.

default_error_messages = {
'item_invalid': _('Item "%(item)s" in the set did not validate: '),
'item_n_invalid': _('Item %(nth)s in the set did not validate: '),
'no_double_commas': _('No leading, trailing, or double commas.'),
'no_duplicates': _("Duplicates are not supported. "
"'%(item)s' appears twice or more.")
"'%(item)s' appears twice or more."),
'items_no_commas': _('No leading, trailing, or double commas.'),
# The 'empty' message is the same as 'no commas' by default, since the
# only reason empty strings could arise with the basic comma-splitting
# logic is with extra commas. This may not be true in custom subclasses
# however.
'items_no_empty': _('No leading, trailing, or double commas.'),
}

def prepare_value_serialize(self, values):
return ",".join(values)

def to_python_deserialize(self, value):
if not value:
return []
else:
return value.split(",")

# Internals

empty_values = list(validators.EMPTY_VALUES) + [set()]

def __init__(self, base_field, max_length=None, min_length=None,
*args, **kwargs):
self.base_field = base_field
Expand All @@ -139,25 +182,29 @@ def __init__(self, base_field, max_length=None, min_length=None,

def prepare_value(self, value):
if isinstance(value, set):
return ",".join(
six.text_type(self.base_field.prepare_value(v))
for v in value
return self.prepare_value_serialize(
(six.text_type(self.base_field.prepare_value(v))
for v in value)
)
return value

def to_python(self, value):
if value and len(value):
items = value.split(",")
else:
items = []
items = self.to_python_deserialize(value)

errors = []
values = set()
for i, item in enumerate(items, start=1):
if not len(item):
errors.append(ValidationError(
self.error_messages['no_double_commas'],
code='no_double_commas',
self.error_messages['items_no_empty'],
code='items_no_empty',
))
continue

if ',' in item:
errors.append(ValidationError(
self.error_messages['items_no_commas'],
code='items_no_commas',
))
continue

Expand Down
126 changes: 126 additions & 0 deletions tests/django_mysql_tests/test_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,69 @@ def test_required(self):
assert excinfo.value.messages[0] == 'This field is required.'


class CustomListField(SimpleListField):

default_error_messages = dict(
SimpleListField.default_error_messages,
items_no_commas="Commas are not allowed.",
items_no_empty="Empty lines are not allowed."
)

def prepare_value_serialize(self, values):
return "\n".join(values)

def to_python_deserialize(self, value):
if not value:
return []
else:
return value.splitlines()


class CustomListFieldTests(TestCase):
"""
Check that we can subclass SimpleListField and replace how data is
converted back and forth from the form easily
"""
def test_valid(self):
field = CustomListField(forms.CharField())
value = field.clean('a\nb\nc')
assert value == ['a', 'b', 'c']

def test_to_python_no_empties(self):
field = CustomListField(forms.IntegerField())
with pytest.raises(exceptions.ValidationError) as excinfo:
field.clean('\n\n')
assert (
excinfo.value.messages[0] ==
'Empty lines are not allowed.'
)

def test_to_python_no_commas(self):
field = CustomListField(forms.IntegerField())
with pytest.raises(exceptions.ValidationError) as excinfo:
field.clean(',1')
assert (
excinfo.value.messages[0] ==
'Commas are not allowed.'
)

def test_to_python_base_field_does_not_validate(self):
field = CustomListField(forms.IntegerField())
with pytest.raises(exceptions.ValidationError) as excinfo:
field.clean('a\nb\n9')
assert (
excinfo.value.messages[0] ==
'Item 1 in the list did not validate: Enter a whole number.'
)

def test_prepare_value(self):
field = CustomListField(forms.CharField())
value = field.prepare_value(['a', 'b', 'c'])
assert value.split('\n') == ['a', 'b', 'c']

assert field.prepare_value('1\na') == '1\na'


class TestSimpleSetField(TestCase):

def test_valid(self):
Expand Down Expand Up @@ -276,3 +339,66 @@ def test_required(self):
with pytest.raises(exceptions.ValidationError) as excinfo:
field.clean('')
assert excinfo.value.messages[0] == 'This field is required.'


class CustomSetField(SimpleSetField):

default_error_messages = dict(
SimpleSetField.default_error_messages,
items_no_commas="Commas are not allowed.",
items_no_empty="Empty items are not allowed."
)

def prepare_value_serialize(self, values):
return "&".join(values)

def to_python_deserialize(self, value):
if not value:
return []
else:
return [v for v in value.split('&')]


class CustomSetFieldTests(TestCase):
"""
Check that we can subclass SimpleListField and replace how data is
converted back and forth from the form easily
"""
def test_valid(self):
field = CustomSetField(forms.CharField())
value = field.clean('a&b&c')
assert value == {'a', 'b', 'c'}

def test_to_python_no_empties(self):
field = CustomSetField(forms.IntegerField())
with pytest.raises(exceptions.ValidationError) as excinfo:
field.clean('1&')
assert (
excinfo.value.messages[0] ==
'Empty items are not allowed.'
)

def test_to_python_no_commas(self):
field = CustomSetField(forms.IntegerField())
with pytest.raises(exceptions.ValidationError) as excinfo:
field.clean(',1')
assert (
excinfo.value.messages[0] ==
'Commas are not allowed.'
)

def test_to_python_base_field_does_not_validate(self):
field = CustomSetField(forms.IntegerField())
with pytest.raises(exceptions.ValidationError) as excinfo:
field.clean('a&b&9')
assert (
excinfo.value.messages[0] ==
'Item 1 in the set did not validate: Enter a whole number.'
)

def test_prepare_value(self):
field = CustomSetField(forms.CharField())
value = field.prepare_value({'a', 'b', 'c'})
assert set(value.split('&')) == {'a', 'b', 'c'}

assert field.prepare_value('1&a') == '1&a'