Fixed #23162 -- Renamed forms.Field._has_changed() to has_changed().

This commit is contained in:
Gabriel Muñumel 2014-08-06 22:26:23 -04:30 committed by Tim Graham
parent 99561eef26
commit deed00c0d8
13 changed files with 106 additions and 67 deletions

View File

@ -64,7 +64,7 @@ class ReadOnlyPasswordHashField(forms.Field):
# render an input field. # render an input field.
return initial return initial
def _has_changed(self, initial, data): def has_changed(self, initial, data):
return False return False

View File

@ -533,4 +533,4 @@ class ReadOnlyPasswordHashTest(TestCase):
def test_readonly_field_has_changed(self): def test_readonly_field_has_changed(self):
field = ReadOnlyPasswordHashField() field = ReadOnlyPasswordHashField()
self.assertFalse(field._has_changed('aaa', 'bbb')) self.assertFalse(field.has_changed('aaa', 'bbb'))

View File

@ -81,7 +81,7 @@ class GeometryField(forms.Field):
return geom return geom
def _has_changed(self, initial, data): def has_changed(self, initial, data):
""" Compare geographic value of data with its initial value. """ """ Compare geographic value of data with its initial value. """
try: try:

View File

@ -44,7 +44,7 @@ class GeoAdminTest(TestCase):
""" """
geoadmin = admin.site._registry[City] geoadmin = admin.site._registry[City]
form = geoadmin.get_changelist_form(None)() form = geoadmin.get_changelist_form(None)()
has_changed = form.fields['point']._has_changed has_changed = form.fields['point'].has_changed
initial = Point(13.4197458572965953, 52.5194108501149799, srid=4326) initial = Point(13.4197458572965953, 52.5194108501149799, srid=4326)
data_same = "SRID=3857;POINT(1493879.2754093995 6894592.019687599)" data_same = "SRID=3857;POINT(1493879.2754093995 6894592.019687599)"

View File

@ -25,7 +25,7 @@ from django.forms.widgets import (
from django.utils import formats from django.utils import formats
from django.utils.encoding import smart_text, force_str, force_text from django.utils.encoding import smart_text, force_str, force_text
from django.utils.ipv6 import clean_ipv6_address from django.utils.ipv6 import clean_ipv6_address
from django.utils.deprecation import RemovedInDjango19Warning, RemovedInDjango20Warning from django.utils.deprecation import RemovedInDjango19Warning, RemovedInDjango20Warning, RenameMethodsBase
from django.utils import six from django.utils import six
from django.utils.six.moves.urllib.parse import urlsplit, urlunsplit from django.utils.six.moves.urllib.parse import urlsplit, urlunsplit
from django.utils.translation import ugettext_lazy as _, ungettext_lazy from django.utils.translation import ugettext_lazy as _, ungettext_lazy
@ -45,7 +45,13 @@ __all__ = (
) )
class Field(object): class RenameFieldMethods(RenameMethodsBase):
renamed_methods = (
('_has_changed', 'has_changed', RemovedInDjango20Warning),
)
class Field(six.with_metaclass(RenameFieldMethods, object)):
widget = TextInput # Default widget to use when rendering this type of Field. widget = TextInput # Default widget to use when rendering this type of Field.
hidden_widget = HiddenInput # Default widget to use when rendering this as "hidden". hidden_widget = HiddenInput # Default widget to use when rendering this as "hidden".
default_validators = [] # Default set of validators default_validators = [] # Default set of validators
@ -185,7 +191,7 @@ class Field(object):
return self.limit_choices_to() return self.limit_choices_to()
return self.limit_choices_to return self.limit_choices_to
def _has_changed(self, initial, data): def has_changed(self, initial, data):
""" """
Return True if data differs from initial. Return True if data differs from initial.
""" """
@ -629,7 +635,7 @@ class FileField(Field):
return initial return initial
return data return data
def _has_changed(self, initial, data): def has_changed(self, initial, data):
if data is None: if data is None:
return False return False
return True return True
@ -744,7 +750,7 @@ class BooleanField(Field):
if not value and self.required: if not value and self.required:
raise ValidationError(self.error_messages['required'], code='required') raise ValidationError(self.error_messages['required'], code='required')
def _has_changed(self, initial, data): def has_changed(self, initial, data):
# Sometimes data or initial could be None or '' which should be the # Sometimes data or initial could be None or '' which should be the
# same thing as False. # same thing as False.
if initial == 'False': if initial == 'False':
@ -779,7 +785,7 @@ class NullBooleanField(BooleanField):
def validate(self, value): def validate(self, value):
pass pass
def _has_changed(self, initial, data): def has_changed(self, initial, data):
# None (unknown) and False (No) are not the same # None (unknown) and False (No) are not the same
if initial is not None: if initial is not None:
initial = bool(initial) initial = bool(initial)
@ -906,7 +912,7 @@ class MultipleChoiceField(ChoiceField):
params={'value': val}, params={'value': val},
) )
def _has_changed(self, initial, data): def has_changed(self, initial, data):
if initial is None: if initial is None:
initial = [] initial = []
if data is None: if data is None:
@ -1084,14 +1090,14 @@ class MultiValueField(Field):
""" """
raise NotImplementedError('Subclasses must implement this method.') raise NotImplementedError('Subclasses must implement this method.')
def _has_changed(self, initial, data): def has_changed(self, initial, data):
if initial is None: if initial is None:
initial = ['' for x in range(0, len(data))] initial = ['' for x in range(0, len(data))]
else: else:
if not isinstance(initial, list): if not isinstance(initial, list):
initial = self.widget.decompress(initial) initial = self.widget.decompress(initial)
for field, initial, data in zip(self.fields, initial, data): for field, initial, data in zip(self.fields, initial, data):
if field._has_changed(field.to_python(initial), data): if field.has_changed(field.to_python(initial), data):
return True return True
return False return False

View File

@ -443,7 +443,7 @@ class BaseForm(object):
# Always assume data has changed if validation fails. # Always assume data has changed if validation fails.
self._changed_data.append(name) self._changed_data.append(name)
continue continue
if field._has_changed(initial_value, data_value): if field.has_changed(initial_value, data_value):
self._changed_data.append(name) self._changed_data.append(name)
return self._changed_data return self._changed_data

View File

@ -1058,7 +1058,7 @@ class InlineForeignKeyField(Field):
raise ValidationError(self.error_messages['invalid_choice'], code='invalid_choice') raise ValidationError(self.error_messages['invalid_choice'], code='invalid_choice')
return self.parent_instance return self.parent_instance
def _has_changed(self, initial, data): def has_changed(self, initial, data):
return False return False
@ -1186,7 +1186,7 @@ class ModelChoiceField(ChoiceField):
def validate(self, value): def validate(self, value):
return Field.validate(self, value) return Field.validate(self, value)
def _has_changed(self, initial, data): def has_changed(self, initial, data):
initial_value = initial if initial is not None else '' initial_value = initial if initial is not None else ''
data_value = data if data is not None else '' data_value = data if data is not None else ''
return force_text(self.prepare_value(initial_value)) != force_text(data_value) return force_text(self.prepare_value(initial_value)) != force_text(data_value)
@ -1254,7 +1254,7 @@ class ModelMultipleChoiceField(ModelChoiceField):
return [super(ModelMultipleChoiceField, self).prepare_value(v) for v in value] return [super(ModelMultipleChoiceField, self).prepare_value(v) for v in value]
return super(ModelMultipleChoiceField, self).prepare_value(value) return super(ModelMultipleChoiceField, self).prepare_value(value)
def _has_changed(self, initial, data): def has_changed(self, initial, data):
if initial is None: if initial is None:
initial = [] initial = []
if data is None: if data is None:

View File

@ -46,6 +46,9 @@ about each item can often be found in the release notes of two versions prior.
* Support for string ``view`` arguments to ``url()`` will be removed. * Support for string ``view`` arguments to ``url()`` will be removed.
* The backward compatible shim to rename ``django.forms.Form._has_changed()``
to ``has_changed()`` will be removed.
.. _deprecation-removed-in-1.9: .. _deprecation-removed-in-1.9:
1.9 1.9

View File

@ -279,7 +279,9 @@ so that the comparison can be done:
>>> f.has_changed() >>> f.has_changed()
``has_changed()`` will be ``True`` if the data from ``request.POST`` differs ``has_changed()`` will be ``True`` if the data from ``request.POST`` differs
from what was provided in :attr:`~Form.initial` or ``False`` otherwise. from what was provided in :attr:`~Form.initial` or ``False`` otherwise. The
result is computed by calling :meth:`Field.has_changed` for each field in the
form.
Accessing the fields from the form Accessing the fields from the form
---------------------------------- ----------------------------------

View File

@ -299,6 +299,24 @@ as the rendered output.
See the :ref:`format localization <format-localization>` documentation for See the :ref:`format localization <format-localization>` documentation for
more information. more information.
Checking if the field data has changed
--------------------------------------
``has_changed()``
~~~~~~~~~~~~~~~~~~
.. method:: Field.has_changed()
.. versionchanged:: 1.8
This method was renamed from ``_has_changed()``.
The ``has_changed()`` method is used to determine if the field value has changed
from the initial value. Returns ``True`` or ``False``.
See the :class:`Form.has_changed()` documentation for more information.
.. _built-in-fields: .. _built-in-fields:
Built-in ``Field`` classes Built-in ``Field`` classes

View File

@ -676,3 +676,9 @@ An older (pre-1.0), more restrictive and verbose input format for the
Using the new syntax, this becomes:: Using the new syntax, this becomes::
``['States', ['Kansas', ['Lawrence', 'Topeka'], 'Illinois']]`` ``['States', ['Kansas', ['Lawrence', 'Topeka'], 'Illinois']]``
``django.forms.Field._has_changed()``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Rename this method to :meth:`~django.forms.Field.has_changed` by removing the
leading underscore. The old name will still work until Django 2.0.

View File

@ -443,19 +443,19 @@ class FormsExtraTestCase(TestCase, AssertFormErrorsMixin):
self.assertFormErrors(['This field is required.'], f.clean, ['some text', ['JP']]) self.assertFormErrors(['This field is required.'], f.clean, ['some text', ['JP']])
# test with no initial data # test with no initial data
self.assertTrue(f._has_changed(None, ['some text', ['J', 'P'], ['2007-04-25', '6:24:00']])) self.assertTrue(f.has_changed(None, ['some text', ['J', 'P'], ['2007-04-25', '6:24:00']]))
# test when the data is the same as initial # test when the data is the same as initial
self.assertFalse(f._has_changed('some text,JP,2007-04-25 06:24:00', self.assertFalse(f.has_changed('some text,JP,2007-04-25 06:24:00',
['some text', ['J', 'P'], ['2007-04-25', '6:24:00']])) ['some text', ['J', 'P'], ['2007-04-25', '6:24:00']]))
# test when the first widget's data has changed # test when the first widget's data has changed
self.assertTrue(f._has_changed('some text,JP,2007-04-25 06:24:00', self.assertTrue(f.has_changed('some text,JP,2007-04-25 06:24:00',
['other text', ['J', 'P'], ['2007-04-25', '6:24:00']])) ['other text', ['J', 'P'], ['2007-04-25', '6:24:00']]))
# test when the last widget's data has changed. this ensures that it is not # test when the last widget's data has changed. this ensures that it is not
# short circuiting while testing the widgets. # short circuiting while testing the widgets.
self.assertTrue(f._has_changed('some text,JP,2007-04-25 06:24:00', self.assertTrue(f.has_changed('some text,JP,2007-04-25 06:24:00',
['some text', ['J', 'P'], ['2009-04-25', '11:44:00']])) ['some text', ['J', 'P'], ['2009-04-25', '11:44:00']]))
class ComplexFieldForm(Form): class ComplexFieldForm(Form):
@ -803,7 +803,7 @@ class FormsExtraL10NTestCase(TestCase):
def test_l10n_date_changed(self): def test_l10n_date_changed(self):
""" """
Ensure that DateField._has_changed() with SelectDateWidget works Ensure that DateField.has_changed() with SelectDateWidget works
correctly with a localized date format. correctly with a localized date format.
Refs #17165. Refs #17165.
""" """

View File

@ -315,12 +315,12 @@ class FieldsTests(SimpleTestCase):
def test_floatfield_changed(self): def test_floatfield_changed(self):
f = FloatField() f = FloatField()
n = 4.35 n = 4.35
self.assertFalse(f._has_changed(n, '4.3500')) self.assertFalse(f.has_changed(n, '4.3500'))
with translation.override('fr'), self.settings(USE_L10N=True): with translation.override('fr'), self.settings(USE_L10N=True):
f = FloatField(localize=True) f = FloatField(localize=True)
localized_n = formats.localize_input(n) # -> '4,35' in French localized_n = formats.localize_input(n) # -> '4,35' in French
self.assertFalse(f._has_changed(n, localized_n)) self.assertFalse(f.has_changed(n, localized_n))
# DecimalField ################################################################ # DecimalField ################################################################
@ -428,13 +428,13 @@ class FieldsTests(SimpleTestCase):
def test_decimalfield_changed(self): def test_decimalfield_changed(self):
f = DecimalField(max_digits=2, decimal_places=2) f = DecimalField(max_digits=2, decimal_places=2)
d = Decimal("0.1") d = Decimal("0.1")
self.assertFalse(f._has_changed(d, '0.10')) self.assertFalse(f.has_changed(d, '0.10'))
self.assertTrue(f._has_changed(d, '0.101')) self.assertTrue(f.has_changed(d, '0.101'))
with translation.override('fr'), self.settings(USE_L10N=True): with translation.override('fr'), self.settings(USE_L10N=True):
f = DecimalField(max_digits=2, decimal_places=2, localize=True) f = DecimalField(max_digits=2, decimal_places=2, localize=True)
localized_d = formats.localize_input(d) # -> '0,1' in French localized_d = formats.localize_input(d) # -> '0,1' in French
self.assertFalse(f._has_changed(d, localized_d)) self.assertFalse(f.has_changed(d, localized_d))
# DateField ################################################################### # DateField ###################################################################
@ -493,7 +493,11 @@ class FieldsTests(SimpleTestCase):
format = '%d/%m/%Y' format = '%d/%m/%Y'
f = DateField(input_formats=[format]) f = DateField(input_formats=[format])
d = datetime.date(2007, 9, 17) d = datetime.date(2007, 9, 17)
self.assertFalse(f._has_changed(d, '17/09/2007')) self.assertFalse(f.has_changed(d, '17/09/2007'))
# Test for deprecated behavior _has_changed
with warnings.catch_warnings(record=True):
warnings.simplefilter("always")
self.assertFalse(f._has_changed(d, '17/09/2007'))
def test_datefield_strptime(self): def test_datefield_strptime(self):
"""Test that field.strptime doesn't raise an UnicodeEncodeError (#16123)""" """Test that field.strptime doesn't raise an UnicodeEncodeError (#16123)"""
@ -535,9 +539,9 @@ class FieldsTests(SimpleTestCase):
t1 = datetime.time(12, 51, 34, 482548) t1 = datetime.time(12, 51, 34, 482548)
t2 = datetime.time(12, 51) t2 = datetime.time(12, 51)
f = TimeField(input_formats=['%H:%M', '%H:%M %p']) f = TimeField(input_formats=['%H:%M', '%H:%M %p'])
self.assertTrue(f._has_changed(t1, '12:51')) self.assertTrue(f.has_changed(t1, '12:51'))
self.assertFalse(f._has_changed(t2, '12:51')) self.assertFalse(f.has_changed(t2, '12:51'))
self.assertFalse(f._has_changed(t2, '12:51 PM')) self.assertFalse(f.has_changed(t2, '12:51 PM'))
# DateTimeField ############################################################### # DateTimeField ###############################################################
@ -602,7 +606,7 @@ class FieldsTests(SimpleTestCase):
format = '%Y %m %d %I:%M %p' format = '%Y %m %d %I:%M %p'
f = DateTimeField(input_formats=[format]) f = DateTimeField(input_formats=[format])
d = datetime.datetime(2006, 9, 17, 14, 30, 0) d = datetime.datetime(2006, 9, 17, 14, 30, 0)
self.assertFalse(f._has_changed(d, '2006 09 17 2:30 PM')) self.assertFalse(f.has_changed(d, '2006 09 17 2:30 PM'))
# RegexField ################################################################## # RegexField ##################################################################
@ -731,7 +735,7 @@ class FieldsTests(SimpleTestCase):
def test_filefield_changed(self): def test_filefield_changed(self):
''' '''
Test for the behavior of _has_changed for FileField. The value of data will Test for the behavior of has_changed for FileField. The value of data will
more than likely come from request.FILES. The value of initial data will more than likely come from request.FILES. The value of initial data will
likely be a filename stored in the database. Since its value is of no use to likely be a filename stored in the database. Since its value is of no use to
a FileField it is ignored. a FileField it is ignored.
@ -739,17 +743,17 @@ class FieldsTests(SimpleTestCase):
f = FileField() f = FileField()
# No file was uploaded and no initial data. # No file was uploaded and no initial data.
self.assertFalse(f._has_changed('', None)) self.assertFalse(f.has_changed('', None))
# A file was uploaded and no initial data. # A file was uploaded and no initial data.
self.assertTrue(f._has_changed('', {'filename': 'resume.txt', 'content': 'My resume'})) self.assertTrue(f.has_changed('', {'filename': 'resume.txt', 'content': 'My resume'}))
# A file was not uploaded, but there is initial data # A file was not uploaded, but there is initial data
self.assertFalse(f._has_changed('resume.txt', None)) self.assertFalse(f.has_changed('resume.txt', None))
# A file was uploaded and there is initial data (file identity is not dealt # A file was uploaded and there is initial data (file identity is not dealt
# with here) # with here)
self.assertTrue(f._has_changed('resume.txt', {'filename': 'resume.txt', 'content': 'My resume'})) self.assertTrue(f.has_changed('resume.txt', {'filename': 'resume.txt', 'content': 'My resume'}))
# ImageField ################################################################## # ImageField ##################################################################
@ -913,15 +917,15 @@ class FieldsTests(SimpleTestCase):
def test_booleanfield_changed(self): def test_booleanfield_changed(self):
f = BooleanField() f = BooleanField()
self.assertFalse(f._has_changed(None, None)) self.assertFalse(f.has_changed(None, None))
self.assertFalse(f._has_changed(None, '')) self.assertFalse(f.has_changed(None, ''))
self.assertFalse(f._has_changed('', None)) self.assertFalse(f.has_changed('', None))
self.assertFalse(f._has_changed('', '')) self.assertFalse(f.has_changed('', ''))
self.assertTrue(f._has_changed(False, 'on')) self.assertTrue(f.has_changed(False, 'on'))
self.assertFalse(f._has_changed(True, 'on')) self.assertFalse(f.has_changed(True, 'on'))
self.assertTrue(f._has_changed(True, '')) self.assertTrue(f.has_changed(True, ''))
# Initial value may have mutated to a string due to show_hidden_initial (#19537) # Initial value may have mutated to a string due to show_hidden_initial (#19537)
self.assertTrue(f._has_changed('False', 'on')) self.assertTrue(f.has_changed('False', 'on'))
# ChoiceField ################################################################# # ChoiceField #################################################################
@ -996,8 +1000,8 @@ class FieldsTests(SimpleTestCase):
def test_typedchoicefield_has_changed(self): def test_typedchoicefield_has_changed(self):
# has_changed should not trigger required validation # has_changed should not trigger required validation
f = TypedChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int, required=True) f = TypedChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int, required=True)
self.assertFalse(f._has_changed(None, '')) self.assertFalse(f.has_changed(None, ''))
self.assertFalse(f._has_changed(1, '1')) self.assertFalse(f.has_changed(1, '1'))
def test_typedchoicefield_special_coerce(self): def test_typedchoicefield_special_coerce(self):
""" """
@ -1065,13 +1069,13 @@ class FieldsTests(SimpleTestCase):
def test_nullbooleanfield_changed(self): def test_nullbooleanfield_changed(self):
f = NullBooleanField() f = NullBooleanField()
self.assertTrue(f._has_changed(False, None)) self.assertTrue(f.has_changed(False, None))
self.assertTrue(f._has_changed(None, False)) self.assertTrue(f.has_changed(None, False))
self.assertFalse(f._has_changed(None, None)) self.assertFalse(f.has_changed(None, None))
self.assertFalse(f._has_changed(False, False)) self.assertFalse(f.has_changed(False, False))
self.assertTrue(f._has_changed(True, False)) self.assertTrue(f.has_changed(True, False))
self.assertTrue(f._has_changed(True, None)) self.assertTrue(f.has_changed(True, None))
self.assertTrue(f._has_changed(True, False)) self.assertTrue(f.has_changed(True, False))
# MultipleChoiceField ######################################################### # MultipleChoiceField #########################################################
@ -1116,13 +1120,13 @@ class FieldsTests(SimpleTestCase):
def test_multiplechoicefield_changed(self): def test_multiplechoicefield_changed(self):
f = MultipleChoiceField(choices=[('1', 'One'), ('2', 'Two'), ('3', 'Three')]) f = MultipleChoiceField(choices=[('1', 'One'), ('2', 'Two'), ('3', 'Three')])
self.assertFalse(f._has_changed(None, None)) self.assertFalse(f.has_changed(None, None))
self.assertFalse(f._has_changed([], None)) self.assertFalse(f.has_changed([], None))
self.assertTrue(f._has_changed(None, ['1'])) self.assertTrue(f.has_changed(None, ['1']))
self.assertFalse(f._has_changed([1, 2], ['1', '2'])) self.assertFalse(f.has_changed([1, 2], ['1', '2']))
self.assertFalse(f._has_changed([2, 1], ['1', '2'])) self.assertFalse(f.has_changed([2, 1], ['1', '2']))
self.assertTrue(f._has_changed([1, 2], ['1'])) self.assertTrue(f.has_changed([1, 2], ['1']))
self.assertTrue(f._has_changed([1, 2], ['1', '3'])) self.assertTrue(f.has_changed([1, 2], ['1', '3']))
# TypedMultipleChoiceField ############################################################ # TypedMultipleChoiceField ############################################################
# TypedMultipleChoiceField is just like MultipleChoiceField, except that coerced types # TypedMultipleChoiceField is just like MultipleChoiceField, except that coerced types
@ -1169,7 +1173,7 @@ class FieldsTests(SimpleTestCase):
def test_typedmultiplechoicefield_has_changed(self): def test_typedmultiplechoicefield_has_changed(self):
# has_changed should not trigger required validation # has_changed should not trigger required validation
f = TypedMultipleChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int, required=True) f = TypedMultipleChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int, required=True)
self.assertFalse(f._has_changed(None, '')) self.assertFalse(f.has_changed(None, ''))
def test_typedmultiplechoicefield_special_coerce(self): def test_typedmultiplechoicefield_special_coerce(self):
""" """
@ -1334,7 +1338,7 @@ class FieldsTests(SimpleTestCase):
def test_splitdatetimefield_changed(self): def test_splitdatetimefield_changed(self):
f = SplitDateTimeField(input_date_formats=['%d/%m/%Y']) f = SplitDateTimeField(input_date_formats=['%d/%m/%Y'])
self.assertFalse(f._has_changed(['11/01/2012', '09:18:15'], ['11/01/2012', '09:18:15'])) self.assertFalse(f.has_changed(['11/01/2012', '09:18:15'], ['11/01/2012', '09:18:15']))
self.assertTrue(f._has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), ['2008-05-06', '12:40:00'])) self.assertTrue(f.has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), ['2008-05-06', '12:40:00']))
self.assertFalse(f._has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), ['06/05/2008', '12:40'])) self.assertFalse(f.has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), ['06/05/2008', '12:40']))
self.assertTrue(f._has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), ['06/05/2008', '12:41'])) self.assertTrue(f.has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), ['06/05/2008', '12:41']))