Commit 892bc91c authored by Claude Paroz's avatar Claude Paroz
Browse files

Fixed #16612 -- Improved has_changed detection for localized field values

Thanks Simon Charette for the review.
parent 0c82b1df
Loading
Loading
Loading
Loading
+6 −16
Original line number Diff line number Diff line
@@ -184,17 +184,13 @@ class Field(object):
        # For purposes of seeing whether something has changed, None is
        # the same as an empty string, if the data or inital value we get
        # is None, replace it w/ ''.
        if data is None:
            data_value = ''
        else:
            data_value = data
        if initial is None:
            initial_value = ''
        else:
            initial_value = initial
        if force_text(initial_value) != force_text(data_value):
        initial_value = initial if initial is not None else ''
        try:
            data = self.to_python(data)
        except ValidationError:
            return True
        return False
        data_value = data if data is not None else ''
        return initial_value != data_value

    def __deepcopy__(self, memo):
        result = copy.copy(self)
@@ -392,12 +388,6 @@ class BaseTemporalField(Field):
    def strptime(self, value, format):
        raise NotImplementedError('Subclasses must define this method.')

    def _has_changed(self, initial, data):
        try:
            data = self.to_python(data)
        except ValidationError:
            return True
        return self.to_python(initial) != data

class DateField(BaseTemporalField):
    widget = DateInput
+2 −2
Original line number Diff line number Diff line
@@ -345,8 +345,8 @@ class BaseForm(object):
                else:
                    initial_prefixed_name = self.add_initial_prefix(name)
                    hidden_widget = field.hidden_widget()
                    initial_value = hidden_widget.value_from_datadict(
                        self.data, self.files, initial_prefixed_name)
                    initial_value = field.to_python(hidden_widget.value_from_datadict(
                        self.data, self.files, initial_prefixed_name))
                if hasattr(field.widget, '_has_changed'):
                    warnings.warn("The _has_changed method on widgets is deprecated,"
                        " define it at field level instead.",
+16 −0
Original line number Diff line number Diff line
@@ -1012,6 +1012,11 @@ class ModelChoiceField(ChoiceField):
    def validate(self, value):
        return Field.validate(self, value)

    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)

class ModelMultipleChoiceField(ModelChoiceField):
    """A MultipleChoiceField whose choices are a model QuerySet."""
    widget = SelectMultiple
@@ -1059,3 +1064,14 @@ class ModelMultipleChoiceField(ModelChoiceField):
                not hasattr(value, '_meta')):
            return [super(ModelMultipleChoiceField, self).prepare_value(v) for v in value]
        return super(ModelMultipleChoiceField, self).prepare_value(value)

    def _has_changed(self, initial, data):
        if initial is None:
            initial = []
        if data is None:
            data = []
        if len(initial) != len(data):
            return True
        initial_set = set([force_text(value) for value in initial])
        data_set = set([force_text(value) for value in data])
        return data_set != initial_set
+27 −9
Original line number Diff line number Diff line
@@ -35,7 +35,9 @@ from decimal import Decimal
from django.core.files.uploadedfile import SimpleUploadedFile
from django.forms import *
from django.test import SimpleTestCase
from django.utils import formats
from django.utils import six
from django.utils import translation
from django.utils._os import upath


@@ -256,6 +258,17 @@ class FieldsTests(SimpleTestCase):
        f = FloatField(localize=True)
        self.assertWidgetRendersTo(f, '<input id="id_f" name="f" type="text" />')

    def test_floatfield_changed(self):
        f = FloatField()
        n = 4.35
        self.assertFalse(f._has_changed(n, '4.3500'))

        with translation.override('fr'):
            with 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))

    # DecimalField ################################################################

    def test_decimalfield_1(self):
@@ -346,6 +359,18 @@ class FieldsTests(SimpleTestCase):
        f = DecimalField(localize=True)
        self.assertWidgetRendersTo(f, '<input id="id_f" name="f" type="text" />')

    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'))

        with translation.override('fr'):
            with 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))

    # DateField ###################################################################

    def test_datefield_1(self):
@@ -404,7 +429,6 @@ class FieldsTests(SimpleTestCase):
        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.strftime(format), '17/09/2007'))

    def test_datefield_strptime(self):
        """Test that field.strptime doesn't raise an UnicodeEncodeError (#16123)"""
@@ -445,14 +469,10 @@ class FieldsTests(SimpleTestCase):
    def test_timefield_changed(self):
        t1 = datetime.time(12, 51, 34, 482548)
        t2 = datetime.time(12, 51)
        format = '%H:%M'
        f = TimeField(input_formats=[format])
        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'))

        format = '%I:%M %p'
        f = TimeField(input_formats=[format])
        self.assertFalse(f._has_changed(t2.strftime(format), '12:51 PM'))
        self.assertFalse(f._has_changed(t2, '12:51 PM'))

    # DateTimeField ###############################################################

@@ -518,8 +538,6 @@ class FieldsTests(SimpleTestCase):
        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'))
        # Initial value may be a string from a hidden input
        self.assertFalse(f._has_changed(d.strftime(format), '2006 09 17 2:30 PM'))

    # RegexField ##################################################################

+2 −1
Original line number Diff line number Diff line
@@ -15,7 +15,7 @@ from django.test.utils import override_settings
from django.utils import translation
from django.utils.formats import (get_format, date_format, time_format,
    localize, localize_input, iter_format_modules, get_format_modules,
    number_format, sanitize_separators)
    number_format, reset_format_cache, sanitize_separators)
from django.utils.importlib import import_module
from django.utils.numberformat import format as nformat
from django.utils._os import upath
@@ -463,6 +463,7 @@ class FormattingTests(TestCase):
        fr_formats.THOUSAND_SEPARATOR = ''
        fr_formats.FIRST_DAY_OF_WEEK = 0

        reset_format_cache()
        with translation.override('fr'):
            with self.settings(USE_THOUSAND_SEPARATOR=True, THOUSAND_SEPARATOR='!'):
                self.assertEqual('', get_format('THOUSAND_SEPARATOR'))