From deed00c0d803d324a3dfdeba52458b6b009c1a90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Mu=C3=B1umel?= Date: Wed, 6 Aug 2014 22:26:23 -0430 Subject: [PATCH] Fixed #23162 -- Renamed forms.Field._has_changed() to has_changed(). --- django/contrib/auth/forms.py | 2 +- django/contrib/auth/tests/test_forms.py | 2 +- django/contrib/gis/forms/fields.py | 2 +- django/contrib/gis/tests/geoadmin/tests.py | 2 +- django/forms/fields.py | 24 +++--- django/forms/forms.py | 2 +- django/forms/models.py | 6 +- docs/internals/deprecation.txt | 3 + docs/ref/forms/api.txt | 4 +- docs/ref/forms/fields.txt | 18 +++++ docs/releases/1.8.txt | 6 ++ tests/forms_tests/tests/test_extra.py | 10 +-- tests/forms_tests/tests/test_fields.py | 92 +++++++++++----------- 13 files changed, 106 insertions(+), 67 deletions(-) diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py index 0711a1d3d8..992abbaa0f 100644 --- a/django/contrib/auth/forms.py +++ b/django/contrib/auth/forms.py @@ -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 diff --git a/django/contrib/auth/tests/test_forms.py b/django/contrib/auth/tests/test_forms.py index 0ca3d9ccfa..460aaeb315 100644 --- a/django/contrib/auth/tests/test_forms.py +++ b/django/contrib/auth/tests/test_forms.py @@ -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')) diff --git a/django/contrib/gis/forms/fields.py b/django/contrib/gis/forms/fields.py index 29e423df28..af481b8be7 100644 --- a/django/contrib/gis/forms/fields.py +++ b/django/contrib/gis/forms/fields.py @@ -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: diff --git a/django/contrib/gis/tests/geoadmin/tests.py b/django/contrib/gis/tests/geoadmin/tests.py index faecea3164..e815b455d0 100644 --- a/django/contrib/gis/tests/geoadmin/tests.py +++ b/django/contrib/gis/tests/geoadmin/tests.py @@ -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)" diff --git a/django/forms/fields.py b/django/forms/fields.py index 36f13472eb..577e6b4023 100644 --- a/django/forms/fields.py +++ b/django/forms/fields.py @@ -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 diff --git a/django/forms/forms.py b/django/forms/forms.py index a4db99526e..e5df320673 100644 --- a/django/forms/forms.py +++ b/django/forms/forms.py @@ -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 diff --git a/django/forms/models.py b/django/forms/models.py index fb342b3316..be247a3940 100644 --- a/django/forms/models.py +++ b/django/forms/models.py @@ -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: diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index ee5e9d0f67..ed84626d3f 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -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 diff --git a/docs/ref/forms/api.txt b/docs/ref/forms/api.txt index da8e8a92f1..deb974ad5d 100644 --- a/docs/ref/forms/api.txt +++ b/docs/ref/forms/api.txt @@ -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 ---------------------------------- diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt index 3fbbac9624..1d8a1c9b5d 100644 --- a/docs/ref/forms/fields.txt +++ b/docs/ref/forms/fields.txt @@ -299,6 +299,24 @@ as the rendered output. See the :ref:`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 diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt index ae8c1e4d91..9b013c676c 100644 --- a/docs/releases/1.8.txt +++ b/docs/releases/1.8.txt @@ -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. diff --git a/tests/forms_tests/tests/test_extra.py b/tests/forms_tests/tests/test_extra.py index 3f6208fade..6ec1a62932 100644 --- a/tests/forms_tests/tests/test_extra.py +++ b/tests/forms_tests/tests/test_extra.py @@ -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. """ diff --git a/tests/forms_tests/tests/test_fields.py b/tests/forms_tests/tests/test_fields.py index 2414353d00..b64e55b066 100644 --- a/tests/forms_tests/tests/test_fields.py +++ b/tests/forms_tests/tests/test_fields.py @@ -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']))