Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Fixed #19997 -- Added custom EMPTY_VALUES to form fields

Thanks Loic Bistuer for the report and the patch.
  • Loading branch information...
commit 4cccb85e292fea01b3459cd97d751ed35179a7b7 1 parent 25ce177
Claude Paroz claudep authored
1  AUTHORS
View
@@ -98,6 +98,7 @@ answer newbie questions, and generally made Django that much better:
Natalia Bidart <nataliabidart@gmail.com>
Mark Biggers <biggers@utsl.com>
Paul Bissex <http://e-scribe.com/>
+ Loic Bistuer <loic.bistuer@sixmedia.com>
Simon Blanchard
Craig Blaszczyk <masterjakul@gmail.com>
David Blewett <david@dawninglight.net>
45 django/forms/fields.py
View
@@ -53,6 +53,7 @@ class Field(object):
'required': _('This field is required.'),
'invalid': _('Enter a valid value.'),
}
+ empty_values = list(validators.EMPTY_VALUES)
# Tracks each time a Field instance is created. Used to retain order.
creation_counter = 0
@@ -125,11 +126,11 @@ def to_python(self, value):
return value
def validate(self, value):
- if value in validators.EMPTY_VALUES and self.required:
+ if value in self.empty_values and self.required:
raise ValidationError(self.error_messages['required'])
def run_validators(self, value):
- if value in validators.EMPTY_VALUES:
+ if value in self.empty_values:
return
errors = []
for v in self.validators:
@@ -210,7 +211,7 @@ def __init__(self, max_length=None, min_length=None, *args, **kwargs):
def to_python(self, value):
"Returns a Unicode object."
- if value in validators.EMPTY_VALUES:
+ if value in self.empty_values:
return ''
return smart_text(value)
@@ -244,7 +245,7 @@ def to_python(self, value):
of int(). Returns None for empty values.
"""
value = super(IntegerField, self).to_python(value)
- if value in validators.EMPTY_VALUES:
+ if value in self.empty_values:
return None
if self.localize:
value = formats.sanitize_separators(value)
@@ -275,7 +276,7 @@ def to_python(self, value):
of float(). Returns None for empty values.
"""
value = super(IntegerField, self).to_python(value)
- if value in validators.EMPTY_VALUES:
+ if value in self.empty_values:
return None
if self.localize:
value = formats.sanitize_separators(value)
@@ -311,7 +312,7 @@ def to_python(self, value):
than max_digits in the number, and no more than decimal_places digits
after the decimal point.
"""
- if value in validators.EMPTY_VALUES:
+ if value in self.empty_values:
return None
if self.localize:
value = formats.sanitize_separators(value)
@@ -324,7 +325,7 @@ def to_python(self, value):
def validate(self, value):
super(DecimalField, self).validate(value)
- if value in validators.EMPTY_VALUES:
+ if value in self.empty_values:
return
# Check for NaN, Inf and -Inf values. We can't compare directly for NaN,
# since it is never equal to itself. However, NaN is the only value that
@@ -401,7 +402,7 @@ def to_python(self, value):
Validates that the input can be converted to a date. Returns a Python
datetime.date object.
"""
- if value in validators.EMPTY_VALUES:
+ if value in self.empty_values:
return None
if isinstance(value, datetime.datetime):
return value.date()
@@ -425,7 +426,7 @@ def to_python(self, value):
Validates that the input can be converted to a time. Returns a Python
datetime.time object.
"""
- if value in validators.EMPTY_VALUES:
+ if value in self.empty_values:
return None
if isinstance(value, datetime.time):
return value
@@ -451,7 +452,7 @@ def to_python(self, value):
Validates that the input can be converted to a datetime. Returns a
Python datetime.datetime object.
"""
- if value in validators.EMPTY_VALUES:
+ if value in self.empty_values:
return None
if isinstance(value, datetime.datetime):
return from_current_timezone(value)
@@ -463,7 +464,7 @@ def to_python(self, value):
# components: date and time.
if len(value) != 2:
raise ValidationError(self.error_messages['invalid'])
- if value[0] in validators.EMPTY_VALUES and value[1] in validators.EMPTY_VALUES:
+ if value[0] in self.empty_values and value[1] in self.empty_values:
return None
value = '%s %s' % tuple(value)
result = super(DateTimeField, self).to_python(value)
@@ -531,7 +532,7 @@ def __init__(self, *args, **kwargs):
super(FileField, self).__init__(*args, **kwargs)
def to_python(self, data):
- if data in validators.EMPTY_VALUES:
+ if data in self.empty_values:
return None
# UploadedFile objects should have name and size attributes.
@@ -562,7 +563,7 @@ def clean(self, data, initial=None):
return False
# If the field is required, clearing is not possible (the widget
# shouldn't return False data in that case anyway). False is not
- # in validators.EMPTY_VALUES; if a False value makes it this far
+ # in self.empty_value; if a False value makes it this far
# it should be validated from here on out as None (so it will be
# caught by the required check).
data = None
@@ -763,7 +764,7 @@ def _set_choices(self, value):
def to_python(self, value):
"Returns a Unicode object."
- if value in validators.EMPTY_VALUES:
+ if value in self.empty_values:
return ''
return smart_text(value)
@@ -801,7 +802,7 @@ def to_python(self, value):
"""
value = super(TypedChoiceField, self).to_python(value)
super(TypedChoiceField, self).validate(value)
- if value == self.empty_value or value in validators.EMPTY_VALUES:
+ if value == self.empty_value or value in self.empty_values:
return self.empty_value
try:
value = self.coerce(value)
@@ -864,7 +865,7 @@ def to_python(self, value):
"""
value = super(TypedMultipleChoiceField, self).to_python(value)
super(TypedMultipleChoiceField, self).validate(value)
- if value == self.empty_value or value in validators.EMPTY_VALUES:
+ if value == self.empty_value or value in self.empty_values:
return self.empty_value
new_value = []
for choice in value:
@@ -945,7 +946,7 @@ def clean(self, value):
clean_data = []
errors = ErrorList()
if not value or isinstance(value, (list, tuple)):
- if not value or not [v for v in value if v not in validators.EMPTY_VALUES]:
+ if not value or not [v for v in value if v not in self.empty_values]:
if self.required:
raise ValidationError(self.error_messages['required'])
else:
@@ -957,7 +958,7 @@ def clean(self, value):
field_value = value[i]
except IndexError:
field_value = None
- if self.required and field_value in validators.EMPTY_VALUES:
+ if self.required and field_value in self.empty_values:
raise ValidationError(self.error_messages['required'])
try:
clean_data.append(field.clean(field_value))
@@ -1071,9 +1072,9 @@ def compress(self, data_list):
if data_list:
# Raise a validation error if time or date is empty
# (possible if SplitDateTimeField has required=False).
- if data_list[0] in validators.EMPTY_VALUES:
+ if data_list[0] in self.empty_values:
raise ValidationError(self.error_messages['invalid_date'])
- if data_list[1] in validators.EMPTY_VALUES:
+ if data_list[1] in self.empty_values:
raise ValidationError(self.error_messages['invalid_time'])
result = datetime.datetime.combine(*data_list)
return from_current_timezone(result)
@@ -1087,7 +1088,7 @@ class IPAddressField(CharField):
default_validators = [validators.validate_ipv4_address]
def to_python(self, value):
- if value in EMPTY_VALUES:
+ if value in self.empty_values:
return ''
return value.strip()
@@ -1103,7 +1104,7 @@ def __init__(self, protocol='both', unpack_ipv4=False, *args, **kwargs):
super(GenericIPAddressField, self).__init__(*args, **kwargs)
def to_python(self, value):
- if value in validators.EMPTY_VALUES:
+ if value in self.empty_values:
return ''
value = value.strip()
if value and ':' in value:
7 django/forms/models.py
View
@@ -6,7 +6,6 @@
from __future__ import absolute_import, unicode_literals
from django.core.exceptions import ValidationError, NON_FIELD_ERRORS, FieldError
-from django.core.validators import EMPTY_VALUES
from django.forms.fields import Field, ChoiceField
from django.forms.forms import BaseForm, get_declared_fields
from django.forms.formsets import BaseFormSet, formset_factory
@@ -301,7 +300,7 @@ def _get_validation_exclusions(self):
else:
form_field = self.fields[field]
field_value = self.cleaned_data.get(field, None)
- if not f.blank and not form_field.required and field_value in EMPTY_VALUES:
+ if not f.blank and not form_field.required and field_value in form_field.empty_values:
exclude.append(f.name)
return exclude
@@ -880,7 +879,7 @@ def __init__(self, parent_instance, *args, **kwargs):
super(InlineForeignKeyField, self).__init__(*args, **kwargs)
def clean(self, value):
- if value in EMPTY_VALUES:
+ if value in self.empty_values:
if self.pk_field:
return None
# if there is no value act as we did before.
@@ -1000,7 +999,7 @@ def prepare_value(self, value):
return super(ModelChoiceField, self).prepare_value(value)
def to_python(self, value):
- if value in EMPTY_VALUES:
+ if value in self.empty_values:
return None
try:
key = self.to_field_name or 'pk'
5 django/test/testcases.py
View
@@ -27,7 +27,6 @@
from django.core.servers.basehttp import (WSGIRequestHandler, WSGIServer,
WSGIServerException)
from django.core.urlresolvers import clear_url_caches
-from django.core.validators import EMPTY_VALUES
from django.db import connection, connections, DEFAULT_DB_ALIAS, transaction
from django.forms.fields import CharField
from django.http import QueryDict
@@ -322,7 +321,7 @@ def assertFieldOutput(self, fieldclass, valid, invalid, field_args=None,
raised error messages.
field_args: the args passed to instantiate the field
field_kwargs: the kwargs passed to instantiate the field
- empty_value: the expected clean output for inputs in EMPTY_VALUES
+ empty_value: the expected clean output for inputs in empty_values
"""
if field_args is None:
@@ -347,7 +346,7 @@ def assertFieldOutput(self, fieldclass, valid, invalid, field_args=None,
self.assertEqual(context_manager.exception.messages, errors)
# test required inputs
error_required = [force_text(required.error_messages['required'])]
- for e in EMPTY_VALUES:
+ for e in required.empty_values:
with self.assertRaises(ValidationError) as context_manager:
required.clean(e)
self.assertEqual(context_manager.exception.messages,
2  docs/topics/testing/overview.txt
View
@@ -1480,7 +1480,7 @@ your test suite.
error messages.
:param field_args: the args passed to instantiate the field.
:param field_kwargs: the kwargs passed to instantiate the field.
- :param empty_value: the expected clean output for inputs in ``EMPTY_VALUES``.
+ :param empty_value: the expected clean output for inputs in ``empty_values``.
For example, the following code tests that an ``EmailField`` accepts
"a@a.com" as a valid email address, but rejects "aaa" with a reasonable
20 tests/forms_tests/tests/forms.py
View
@@ -1797,3 +1797,23 @@ class NameForm(Form):
form = NameForm(data={'name' : ['fname', 'lname']})
self.assertTrue(form.is_valid())
self.assertEqual(form.cleaned_data, {'name' : 'fname lname'})
+
+ def test_custom_empty_values(self):
+ """
+ Test that form fields can customize what is considered as an empty value
+ for themselves (#19997).
+ """
+ class CustomJSONField(CharField):
+ empty_values = [None, '']
+ def to_python(self, value):
+ # Fake json.loads
+ if value == '{}':
+ return {}
+ return super(CustomJSONField, self).to_python(value)
+
+ class JSONForm(forms.Form):
+ json = CustomJSONField()
+
+ form = JSONForm(data={'json': '{}'});
+ form.full_clean()
+ self.assertEqual(form.cleaned_data, {'json' : {}})
Please sign in to comment.
Something went wrong with that request. Please try again.