#22406: Inconsistent way to parse boolean value from posted data
--------------------------+--------------------------------------
Reporter: xiaowl@… | Owner: nobody
Type: Bug | Status: closed
Component: Forms | Version: 1.6
Severity: Normal | Resolution: needsinfo
Keywords: | Triage Stage: Unreviewed
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
--------------------------+--------------------------------------
Comment (by Christian Kreuzberger):
A similar inconsistency still exists with BooleanFields and
NullBooleanFields in django.forms:
django.forms.fields.BooleanField has the following logic:
{{{
#!python
class BooleanField(Field):
widget = CheckboxInput
def to_python(self, value):
"""Returns a Python boolean object."""
# Explicitly check for the string 'False', which is what a hidden
field
# will submit for False. Also check for '0', since this is what
# RadioSelect will provide. Because bool("True") == bool('1') ==
True,
# we don't need to handle that explicitly.
if isinstance(value, six.string_types) and value.lower() in
('false', '0'):
value = False
else:
value = bool(value)
return super(BooleanField, self).to_python(value)
}}}
So 'false', 'False',false,'0' will be evaluated to False,
'true','True',true,'1' will be evaluated to True. I guess '2' and '3' will
not be converted and will be evaluated as None at this point.
django.forms.fields.NullBooleanField has a different implementation:
{{{
#!python
class NullBooleanField(BooleanField):
"""
A field whose valid values are None, True and False. Invalid values
are
cleaned to None.
"""
widget = NullBooleanSelect
def to_python(self, value):
"""
Explicitly checks for the string 'True' and 'False', which is what
a
hidden field will submit for True and False, for 'true' and
'false',
which are likely to be returned by JavaScript serializations of
forms,
and for '1' and '0', which is what a RadioField will submit.
Unlike
the Booleanfield we need to explicitly check for True, because we
are
not using the bool() function
"""
if value in (True, 'True', 'true', '1'):
return True
elif value in (False, 'False', 'false', '0'):
return False
else:
return None
}}}
Essentially, the to_python method will do the same as BooleanFields
to_python method, but now comes the funny part:
NullBooleanField uses the NullBooleanSelect widget, while BooleanField
uses the CheckboxInput widget:
BooleanField uses django.forms.widget.CheckboxInput which evaluates
'true','True',True,'1' to True, and 'false','False',False,'0' to False -
which is fine.
{{{
#!python
class CheckboxInput(Input):
def value_from_datadict(self, data, files, name):
if name not in data:
# A missing value means False because HTML form submission
does not
# send results for unselected checkboxes.
return False
value = data.get(name)
# Translate true and false strings to boolean values.
values = {'true': True, 'false': False}
if isinstance(value, six.string_types):
value = values.get(value.lower(), value)
return bool(value)
}}}
But NullBooleanField uses NullBooleanSelect, which as stated above, does
NOT evaluate 'false' to False, and not 'true' to True. Instead, it
evalutes '2' and 'True' to True, '3' and 'False' to False...
{{{
#!python
class NullBooleanSelect(Select):
"""
A Select Widget intended to be used with NullBooleanField.
"""
def __init__(self, attrs=None):
choices = (
('1', ugettext_lazy('Unknown')),
('2', ugettext_lazy('Yes')),
('3', ugettext_lazy('No')),
)
super(NullBooleanSelect, self).__init__(attrs, choices)
def format_value(self, value):
try:
return {True: '2', False: '3', '2': '2', '3': '3'}[value]
except KeyError:
return '1'
def value_from_datadict(self, data, files, name):
value = data.get(name)
return {
'2': True,
True: True,
'True': True,
'3': False,
'False': False,
False: False,
}.get(value)
}}}
Because of this inconsistency, we are experiencing a very funny behaviour
for NullBooleanField:
django.forms.forms.BaseForm has the _clean_fields method, which calls the
widgets `value_from_datadict` method:
`value = field.widget.value_from_datadict(self.data, self.files,
self.add_prefix(name))`
This is done for each field, and it is happening BEFORE the `to_python`
method of the field is called.
Essentially, this whole inconsistency leads to a funny behaviour:
If you use a BooleanField the following HTTP request will be evaluated
correctly by the form:
/my/site/?show_only_assigned_tasks=true
But if you use a NullBooleanField instead of a BooleanField, the above
request will not work, and the String 'true' will be evaluated as None.
TL;DR: NullBooleanSelect should be extended with a way to allow 'false'
and 'true' to be evaluated:
{{{
#!python
def value_from_datadict(self, data, files, name):
value = data.get(name)
return {
'2': True,
True: True,
'True': True,
'true': True,
'3': False,
'False': False,
'false': False,
False: False,
}.get(value)
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/22406#comment:3>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
--
You received this message because you are subscribed to the Google Groups
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/django-updates/075.a805b62bc2cca91074599793b7fb9571%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.