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.
return initial
def _has_changed(self, initial, data):
def has_changed(self, initial, data):
return False

View File

@ -533,4 +533,4 @@ class ReadOnlyPasswordHashTest(TestCase):
def test_readonly_field_has_changed(self):
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
def _has_changed(self, initial, data):
def has_changed(self, initial, data):
""" Compare geographic value of data with its initial value. """
try:

View File

@ -44,7 +44,7 @@ class GeoAdminTest(TestCase):
"""
geoadmin = admin.site._registry[City]
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)
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.encoding import smart_text, force_str, force_text
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.six.moves.urllib.parse import urlsplit, urlunsplit
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.
hidden_widget = HiddenInput # Default widget to use when rendering this as "hidden".
default_validators = [] # Default set of validators
@ -185,7 +191,7 @@ class Field(object):
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.
"""
@ -629,7 +635,7 @@ class FileField(Field):
return initial
return data
def _has_changed(self, initial, data):
def has_changed(self, initial, data):
if data is None:
return False
return True
@ -744,7 +750,7 @@ class BooleanField(Field):
if not value and self.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
# same thing as False.
if initial == 'False':
@ -779,7 +785,7 @@ class NullBooleanField(BooleanField):
def validate(self, value):
pass
def _has_changed(self, initial, data):
def has_changed(self, initial, data):
# None (unknown) and False (No) are not the same
if initial is not None:
initial = bool(initial)
@ -906,7 +912,7 @@ class MultipleChoiceField(ChoiceField):
params={'value': val},
)
def _has_changed(self, initial, data):
def has_changed(self, initial, data):
if initial is None:
initial = []
if data is None:
@ -1084,14 +1090,14 @@ class MultiValueField(Field):
"""
raise NotImplementedError('Subclasses must implement this method.')
def _has_changed(self, initial, data):
def has_changed(self, initial, data):
if initial is None:
initial = ['' for x in range(0, len(data))]
else:
if not isinstance(initial, list):
initial = self.widget.decompress(initial)
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 False

View File

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

View File

@ -1058,7 +1058,7 @@ class InlineForeignKeyField(Field):
raise ValidationError(self.error_messages['invalid_choice'], code='invalid_choice')
return self.parent_instance
def _has_changed(self, initial, data):
def has_changed(self, initial, data):
return False
@ -1186,7 +1186,7 @@ class ModelChoiceField(ChoiceField):
def 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 ''
data_value = data if data is not None else ''
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(value)
def _has_changed(self, initial, data):
def has_changed(self, initial, data):
if initial is None:
initial = []
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.
* The backward compatible shim to rename ``django.forms.Form._has_changed()``
to ``has_changed()`` will be removed.
.. _deprecation-removed-in-1.9:
1.9

View File

@ -279,7 +279,9 @@ so that the comparison can be done:
>>> f.has_changed()
``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
----------------------------------

View File

@ -299,6 +299,24 @@ as the rendered output.
See the :ref:`format localization <format-localization>` documentation for
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 ``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::
``['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']])
# 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
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']]))
# 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']]))
# test when the last widget's data has changed. this ensures that it is not
# 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']]))
class ComplexFieldForm(Form):
@ -803,7 +803,7 @@ class FormsExtraL10NTestCase(TestCase):
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.
Refs #17165.
"""

View File

@ -315,12 +315,12 @@ class FieldsTests(SimpleTestCase):
def test_floatfield_changed(self):
f = FloatField()
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):
f = FloatField(localize=True)
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 ################################################################
@ -428,13 +428,13 @@ class FieldsTests(SimpleTestCase):
def test_decimalfield_changed(self):
f = DecimalField(max_digits=2, decimal_places=2)
d = Decimal("0.1")
self.assertFalse(f._has_changed(d, '0.10'))
self.assertTrue(f._has_changed(d, '0.101'))
self.assertFalse(f.has_changed(d, '0.10'))
self.assertTrue(f.has_changed(d, '0.101'))
with translation.override('fr'), self.settings(USE_L10N=True):
f = DecimalField(max_digits=2, decimal_places=2, localize=True)
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 ###################################################################
@ -493,7 +493,11 @@ class FieldsTests(SimpleTestCase):
format = '%d/%m/%Y'
f = DateField(input_formats=[format])
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):
"""Test that field.strptime doesn't raise an UnicodeEncodeError (#16123)"""
@ -535,9 +539,9 @@ class FieldsTests(SimpleTestCase):
t1 = datetime.time(12, 51, 34, 482548)
t2 = datetime.time(12, 51)
f = TimeField(input_formats=['%H:%M', '%H:%M %p'])
self.assertTrue(f._has_changed(t1, '12:51'))
self.assertFalse(f._has_changed(t2, '12:51'))
self.assertFalse(f._has_changed(t2, '12:51 PM'))
self.assertTrue(f.has_changed(t1, '12:51'))
self.assertFalse(f.has_changed(t2, '12:51'))
self.assertFalse(f.has_changed(t2, '12:51 PM'))
# DateTimeField ###############################################################
@ -602,7 +606,7 @@ class FieldsTests(SimpleTestCase):
format = '%Y %m %d %I:%M %p'
f = DateTimeField(input_formats=[format])
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 ##################################################################
@ -731,7 +735,7 @@ class FieldsTests(SimpleTestCase):
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
likely be a filename stored in the database. Since its value is of no use to
a FileField it is ignored.
@ -739,17 +743,17 @@ class FieldsTests(SimpleTestCase):
f = FileField()
# 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.
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
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
# 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 ##################################################################
@ -913,15 +917,15 @@ class FieldsTests(SimpleTestCase):
def test_booleanfield_changed(self):
f = BooleanField()
self.assertFalse(f._has_changed(None, None))
self.assertFalse(f._has_changed(None, ''))
self.assertFalse(f._has_changed('', None))
self.assertFalse(f._has_changed('', ''))
self.assertTrue(f._has_changed(False, 'on'))
self.assertFalse(f._has_changed(True, 'on'))
self.assertTrue(f._has_changed(True, ''))
self.assertFalse(f.has_changed(None, None))
self.assertFalse(f.has_changed(None, ''))
self.assertFalse(f.has_changed('', None))
self.assertFalse(f.has_changed('', ''))
self.assertTrue(f.has_changed(False, 'on'))
self.assertFalse(f.has_changed(True, 'on'))
self.assertTrue(f.has_changed(True, ''))
# 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 #################################################################
@ -996,8 +1000,8 @@ class FieldsTests(SimpleTestCase):
def test_typedchoicefield_has_changed(self):
# has_changed should not trigger required validation
f = TypedChoiceField(choices=[(1, "+1"), (-1, "-1")], coerce=int, required=True)
self.assertFalse(f._has_changed(None, ''))
self.assertFalse(f._has_changed(1, '1'))
self.assertFalse(f.has_changed(None, ''))
self.assertFalse(f.has_changed(1, '1'))
def test_typedchoicefield_special_coerce(self):
"""
@ -1065,13 +1069,13 @@ class FieldsTests(SimpleTestCase):
def test_nullbooleanfield_changed(self):
f = NullBooleanField()
self.assertTrue(f._has_changed(False, None))
self.assertTrue(f._has_changed(None, False))
self.assertFalse(f._has_changed(None, None))
self.assertFalse(f._has_changed(False, False))
self.assertTrue(f._has_changed(True, False))
self.assertTrue(f._has_changed(True, None))
self.assertTrue(f._has_changed(True, False))
self.assertTrue(f.has_changed(False, None))
self.assertTrue(f.has_changed(None, False))
self.assertFalse(f.has_changed(None, None))
self.assertFalse(f.has_changed(False, False))
self.assertTrue(f.has_changed(True, False))
self.assertTrue(f.has_changed(True, None))
self.assertTrue(f.has_changed(True, False))
# MultipleChoiceField #########################################################
@ -1116,13 +1120,13 @@ class FieldsTests(SimpleTestCase):
def test_multiplechoicefield_changed(self):
f = MultipleChoiceField(choices=[('1', 'One'), ('2', 'Two'), ('3', 'Three')])
self.assertFalse(f._has_changed(None, None))
self.assertFalse(f._has_changed([], None))
self.assertTrue(f._has_changed(None, ['1']))
self.assertFalse(f._has_changed([1, 2], ['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', '3']))
self.assertFalse(f.has_changed(None, None))
self.assertFalse(f.has_changed([], None))
self.assertTrue(f.has_changed(None, ['1']))
self.assertFalse(f.has_changed([1, 2], ['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', '3']))
# TypedMultipleChoiceField ############################################################
# TypedMultipleChoiceField is just like MultipleChoiceField, except that coerced types
@ -1169,7 +1173,7 @@ class FieldsTests(SimpleTestCase):
def test_typedmultiplechoicefield_has_changed(self):
# has_changed should not trigger required validation
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):
"""
@ -1334,7 +1338,7 @@ class FieldsTests(SimpleTestCase):
def test_splitdatetimefield_changed(self):
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.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.assertTrue(f._has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), ['06/05/2008', '12:41']))
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.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']))