From 11e416e86fb0c140cc443adaaf076d9016a02bf2 Mon Sep 17 00:00:00 2001 From: Adam Chainz Date: Mon, 1 Jun 2015 18:49:18 +0100 Subject: [PATCH] Enable customization of SimpleSetField --- django_mysql/forms.py | 47 ++++++++++++++----- tests/django_mysql_tests/test_forms.py | 63 ++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 11 deletions(-) diff --git a/django_mysql/forms.py b/django_mysql/forms.py index 6e5b1c1a..89b92ea1 100644 --- a/django_mysql/forms.py +++ b/django_mysql/forms.py @@ -18,7 +18,8 @@ class SimpleListField(forms.CharField): - # These bits can be overridden simply to change the way the field works + # 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: '), @@ -138,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: '), - 'items_no_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 @@ -161,22 +182,26 @@ 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['items_no_empty'], + code='items_no_empty', + )) + continue + + if ',' in item: errors.append(ValidationError( self.error_messages['items_no_commas'], code='items_no_commas', diff --git a/tests/django_mysql_tests/test_forms.py b/tests/django_mysql_tests/test_forms.py index f523204d..346febd1 100644 --- a/tests/django_mysql_tests/test_forms.py +++ b/tests/django_mysql_tests/test_forms.py @@ -339,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'