Ticket #16612: 16612-1.diff

File 16612-1.diff, 40.7 KB (added by Claude Paroz, 11 years ago)

Moved _has_changed from widget to field

  • django/contrib/admin/widgets.py

    commit b6605d3bfd2ef8783812f2013c437d6c2781f691
    Author: Claude Paroz <claude@2xlibre.net>
    Date:   Mon Dec 31 16:35:03 2012 +0100
    
        Moved has_changed logic from widget to form field.
        
        Refs #16612.
    
    diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py
    index 1e6277f..a388774 100644
    a b class ManyToManyRawIdWidget(ForeignKeyRawIdWidget):  
    213213        if value:
    214214            return value.split(',')
    215215
    216     def _has_changed(self, initial, data):
    217         if initial is None:
    218             initial = []
    219         if data is None:
    220             data = []
    221         if len(initial) != len(data):
    222             return True
    223         for pk1, pk2 in zip(initial, data):
    224             if force_text(pk1) != force_text(pk2):
    225                 return True
    226         return False
    227216
    228217class RelatedFieldWidgetWrapper(forms.Widget):
    229218    """
    class RelatedFieldWidgetWrapper(forms.Widget):  
    279268    def value_from_datadict(self, data, files, name):
    280269        return self.widget.value_from_datadict(data, files, name)
    281270
    282     def _has_changed(self, initial, data):
    283         return self.widget._has_changed(initial, data)
    284 
    285271    def id_for_label(self, id_):
    286272        return self.widget.id_for_label(id_)
    287273
  • django/contrib/gis/admin/widgets.py

    diff --git a/django/contrib/gis/admin/widgets.py b/django/contrib/gis/admin/widgets.py
    index f4379be..a069336 100644
    a b from django.utils import six  
    77from django.utils import translation
    88
    99from django.contrib.gis.gdal import OGRException
    10 from django.contrib.gis.geos import GEOSGeometry, GEOSException, fromstr
     10from django.contrib.gis.geos import GEOSGeometry, GEOSException
    1111
    1212# Creating a template context that contains Django settings
    1313# values needed by admin map templates.
    class OpenLayersWidget(Textarea):  
    117117                    raise TypeError
    118118                map_options[js_name] = value
    119119        return map_options
    120 
    121     def _has_changed(self, initial, data):
    122         """ Compare geographic value of data with its initial value. """
    123 
    124         # Ensure we are dealing with a geographic object
    125         if isinstance(initial, six.string_types):
    126             try:
    127                 initial = GEOSGeometry(initial)
    128             except (GEOSException, ValueError):
    129                 initial = None
    130 
    131         # Only do a geographic comparison if both values are available
    132         if initial and data:
    133             data = fromstr(data)
    134             data.transform(initial.srid)
    135             # If the initial value was not added by the browser, the geometry
    136             # provided may be slightly different, the first time it is saved.
    137             # The comparison is done with a very low tolerance.
    138             return not initial.equals_exact(data, tolerance=0.000001)
    139         else:
    140             # Check for change of state of existence
    141             return bool(initial) != bool(data)
  • django/contrib/gis/forms/fields.py

    diff --git a/django/contrib/gis/forms/fields.py b/django/contrib/gis/forms/fields.py
    index cefb683..ab2e37f 100644
    a b  
    11from __future__ import unicode_literals
    22
    33from django import forms
     4from django.utils import six
    45from django.utils.translation import ugettext_lazy as _
    56
    67# While this couples the geographic forms to the GEOS library,
    78# it decouples from database (by not importing SpatialBackend).
    8 from django.contrib.gis.geos import GEOSException, GEOSGeometry
     9from django.contrib.gis.geos import GEOSException, GEOSGeometry, fromstr
     10
    911
    1012class GeometryField(forms.Field):
    1113    """
    class GeometryField(forms.Field):  
    7375                    raise forms.ValidationError(self.error_messages['transform_error'])
    7476
    7577        return geom
     78
     79    def _has_changed(self, initial, data):
     80        """ Compare geographic value of data with its initial value. """
     81
     82        # Ensure we are dealing with a geographic object
     83        if isinstance(initial, six.string_types):
     84            try:
     85                initial = GEOSGeometry(initial)
     86            except (GEOSException, ValueError):
     87                initial = None
     88
     89        # Only do a geographic comparison if both values are available
     90        if initial and data:
     91            data = fromstr(data)
     92            data.transform(initial.srid)
     93            # If the initial value was not added by the browser, the geometry
     94            # provided may be slightly different, the first time it is saved.
     95            # The comparison is done with a very low tolerance.
     96            return not initial.equals_exact(data, tolerance=0.000001)
     97        else:
     98            # Check for change of state of existence
     99            return bool(initial) != bool(data)
  • django/contrib/gis/tests/geoadmin/tests.py

    diff --git a/django/contrib/gis/tests/geoadmin/tests.py b/django/contrib/gis/tests/geoadmin/tests.py
    index 6fadebd..669914b 100644
    a b class GeoAdminTest(TestCase):  
    3838        """ Check that changes are accurately noticed by OpenLayersWidget. """
    3939        geoadmin = admin.site._registry[City]
    4040        form = geoadmin.get_changelist_form(None)()
    41         has_changed = form.fields['point'].widget._has_changed
     41        has_changed = form.fields['point']._has_changed
    4242
    4343        initial = Point(13.4197458572965953, 52.5194108501149799, srid=4326)
    4444        data_same = "SRID=3857;POINT(1493879.2754093995 6894592.019687599)"
  • django/forms/extras/widgets.py

    diff --git a/django/forms/extras/widgets.py b/django/forms/extras/widgets.py
    index c5ca142..e939a8f 100644
    a b class SelectDateWidget(Widget):  
    135135        s = Select(choices=choices)
    136136        select_html = s.render(field % name, val, local_attrs)
    137137        return select_html
    138 
    139     def _has_changed(self, initial, data):
    140         try:
    141             input_format = get_format('DATE_INPUT_FORMATS')[0]
    142             data = datetime_safe.datetime.strptime(data, input_format).date()
    143         except (TypeError, ValueError):
    144             pass
    145         return super(SelectDateWidget, self)._has_changed(initial, data)
  • django/forms/fields.py

    diff --git a/django/forms/fields.py b/django/forms/fields.py
    index 4438812..1e9cbcb 100644
    a b class Field(object):  
    175175        """
    176176        return {}
    177177
     178    def _has_changed(self, initial, data):
     179        """
     180        Return True if data differs from initial.
     181        """
     182        # For purposes of seeing whether something has changed, None is
     183        # the same as an empty string, if the data or inital value we get
     184        # is None, replace it w/ ''.
     185        if data is None:
     186            data_value = ''
     187        else:
     188            data_value = data
     189        if initial is None:
     190            initial_value = ''
     191        else:
     192            initial_value = initial
     193        if force_text(initial_value) != force_text(data_value):
     194            return True
     195        return False
     196
    178197    def __deepcopy__(self, memo):
    179198        result = copy.copy(self)
    180199        memo[id(self)] = result
    class BaseTemporalField(Field):  
    348367    def strptime(self, value, format):
    349368        raise NotImplementedError('Subclasses must define this method.')
    350369
     370    def _has_changed(self, initial, data):
     371        try:
     372            data = self.to_python(data)
     373        except ValidationError:
     374            return True
     375        return self.to_python(initial) != data
     376
    351377class DateField(BaseTemporalField):
    352378    widget = DateInput
    353379    input_formats = formats.get_format_lazy('DATE_INPUT_FORMATS')
    class DateField(BaseTemporalField):  
    371397    def strptime(self, value, format):
    372398        return datetime.datetime.strptime(value, format).date()
    373399
     400
    374401class TimeField(BaseTemporalField):
    375402    widget = TimeInput
    376403    input_formats = formats.get_format_lazy('TIME_INPUT_FORMATS')
    class FileField(Field):  
    529556            return initial
    530557        return data
    531558
     559    def _has_changed(self, initial, data):
     560        if data is None:
     561            return False
     562        return True
     563
     564
    532565class ImageField(FileField):
    533566    default_error_messages = {
    534567        'invalid_image': _("Upload a valid image. The file you uploaded was either not an image or a corrupted image."),
    class URLField(CharField):  
    618651            value = urlunsplit(url_fields)
    619652        return value
    620653
     654
    621655class BooleanField(Field):
    622656    widget = CheckboxInput
    623657
    class BooleanField(Field):  
    636670            raise ValidationError(self.error_messages['required'])
    637671        return value
    638672
     673    def _has_changed(self, initial, data):
     674        # Sometimes data or initial could be None or '' which should be the
     675        # same thing as False.
     676        if initial == 'False':
     677            # show_hidden_initial may have transformed False to 'False'
     678            initial = False
     679        return bool(initial) != bool(data)
     680
     681
    639682class NullBooleanField(BooleanField):
    640683    """
    641684    A field whose valid values are None, True and False. Invalid values are
    class NullBooleanField(BooleanField):  
    660703    def validate(self, value):
    661704        pass
    662705
     706    def _has_changed(self, initial, data):
     707        # None (unknown) and False (No) are not the same
     708        if initial is not None:
     709            initial = bool(initial)
     710        if data is not None:
     711            data = bool(data)
     712        return initial != data
     713
     714
    663715class ChoiceField(Field):
    664716    widget = Select
    665717    default_error_messages = {
    class TypedChoiceField(ChoiceField):  
    739791    def validate(self, value):
    740792        pass
    741793
     794
    742795class MultipleChoiceField(ChoiceField):
    743796    hidden_widget = MultipleHiddenInput
    744797    widget = SelectMultiple
    class MultipleChoiceField(ChoiceField):  
    765818            if not self.valid_value(val):
    766819                raise ValidationError(self.error_messages['invalid_choice'] % {'value': val})
    767820
     821    def _has_changed(self, initial, data):
     822        if initial is None:
     823            initial = []
     824        if data is None:
     825            data = []
     826        if len(initial) != len(data):
     827            return True
     828        initial_set = set([force_text(value) for value in initial])
     829        data_set = set([force_text(value) for value in data])
     830        return data_set != initial_set
     831
     832
    768833class TypedMultipleChoiceField(MultipleChoiceField):
    769834    def __init__(self, *args, **kwargs):
    770835        self.coerce = kwargs.pop('coerce', lambda val: val)
    class MultiValueField(Field):  
    899964        """
    900965        raise NotImplementedError('Subclasses must implement this method.')
    901966
     967    def _has_changed(self, initial, data):
     968        if initial is None:
     969            initial = ['' for x in range(0, len(data))]
     970        else:
     971            if not isinstance(initial, list):
     972                initial = self.widget.decompress(initial)
     973        for field, initial, data in zip(self.fields, initial, data):
     974            if field._has_changed(initial, data):
     975                return True
     976        return False
     977
     978
    902979class FilePathField(ChoiceField):
    903980    def __init__(self, path, match=None, recursive=False, allow_files=True,
    904981                 allow_folders=False, required=True, widget=None, label=None,
  • django/forms/forms.py

    diff --git a/django/forms/forms.py b/django/forms/forms.py
    index 3299c2b..f532391 100644
    a b class BaseForm(object):  
    341341                    hidden_widget = field.hidden_widget()
    342342                    initial_value = hidden_widget.value_from_datadict(
    343343                        self.data, self.files, initial_prefixed_name)
    344                 if field.widget._has_changed(initial_value, data_value):
     344                if hasattr(field.widget, '_has_changed'):
     345                    warnings.warn("The _has_changed method on widgets is deprecated,"
     346                        " define it at field level instead.",
     347                        PendingDeprecationWarning, stacklevel=2)
     348                    if field.widget._has_changed(initial_value, data_value):
     349                        self._changed_data.append(name)
     350                elif field._has_changed(initial_value, data_value):
    345351                    self._changed_data.append(name)
    346352        return self._changed_data
    347353    changed_data = property(_get_changed_data)
  • django/forms/models.py

    diff --git a/django/forms/models.py b/django/forms/models.py
    index 27c246b..3a54970 100644
    a b def inlineformset_factory(parent_model, model, form=ModelForm,  
    839839
    840840# Fields #####################################################################
    841841
    842 class InlineForeignKeyHiddenInput(HiddenInput):
    843     def _has_changed(self, initial, data):
    844         return False
    845 
    846842class InlineForeignKeyField(Field):
    847843    """
    848844    A basic integer field that deals with validating the given value to a
    849845    given parent instance in an inline.
    850846    """
     847    widget = HiddenInput
    851848    default_error_messages = {
    852849        'invalid_choice': _('The inline foreign key did not match the parent instance primary key.'),
    853850    }
    class InlineForeignKeyField(Field):  
    862859            else:
    863860                kwargs["initial"] = self.parent_instance.pk
    864861        kwargs["required"] = False
    865         kwargs["widget"] = InlineForeignKeyHiddenInput
    866862        super(InlineForeignKeyField, self).__init__(*args, **kwargs)
    867863
    868864    def clean(self, value):
    class InlineForeignKeyField(Field):  
    880876            raise ValidationError(self.error_messages['invalid_choice'])
    881877        return self.parent_instance
    882878
     879    def _has_changed(self, initial, data):
     880        return False
     881
    883882class ModelChoiceIterator(object):
    884883    def __init__(self, field):
    885884        self.field = field
  • django/forms/widgets.py

    diff --git a/django/forms/widgets.py b/django/forms/widgets.py
    index 4782b99..1884efd 100644
    a b class Widget(six.with_metaclass(MediaDefiningClass)):  
    208208        """
    209209        return data.get(name, None)
    210210
    211     def _has_changed(self, initial, data):
    212         """
    213         Return True if data differs from initial.
    214         """
    215         # For purposes of seeing whether something has changed, None is
    216         # the same as an empty string, if the data or inital value we get
    217         # is None, replace it w/ ''.
    218         if data is None:
    219             data_value = ''
    220         else:
    221             data_value = data
    222         if initial is None:
    223             initial_value = ''
    224         else:
    225             initial_value = initial
    226         if force_text(initial_value) != force_text(data_value):
    227             return True
    228         return False
    229 
    230211    def id_for_label(self, id_):
    231212        """
    232213        Returns the HTML ID attribute of this Widget for use by a <label>,
    class FileInput(Input):  
    325306        "File widgets take data from FILES, not POST"
    326307        return files.get(name, None)
    327308
    328     def _has_changed(self, initial, data):
    329         if data is None:
    330             return False
    331         return True
    332309
    333310FILE_INPUT_CONTRADICTION = object()
    334311
    class DateInput(TextInput):  
    426403            return value.strftime(self.format)
    427404        return value
    428405
    429     def _has_changed(self, initial, data):
    430         # If our field has show_hidden_initial=True, initial will be a string
    431         # formatted by HiddenInput using formats.localize_input, which is not
    432         # necessarily the format used for this widget. Attempt to convert it.
    433         try:
    434             input_format = formats.get_format('DATE_INPUT_FORMATS')[0]
    435             initial = datetime.datetime.strptime(initial, input_format).date()
    436         except (TypeError, ValueError):
    437             pass
    438         return super(DateInput, self)._has_changed(self._format_value(initial), data)
    439 
    440406
    441407class DateTimeInput(TextInput):
    442408    def __init__(self, attrs=None, format=None):
    class DateTimeInput(TextInput):  
    456422            return value.strftime(self.format)
    457423        return value
    458424
    459     def _has_changed(self, initial, data):
    460         # If our field has show_hidden_initial=True, initial will be a string
    461         # formatted by HiddenInput using formats.localize_input, which is not
    462         # necessarily the format used for this widget. Attempt to convert it.
    463         try:
    464             input_format = formats.get_format('DATETIME_INPUT_FORMATS')[0]
    465             initial = datetime.datetime.strptime(initial, input_format)
    466         except (TypeError, ValueError):
    467             pass
    468         return super(DateTimeInput, self)._has_changed(self._format_value(initial), data)
    469 
    470425
    471426class TimeInput(TextInput):
    472427    def __init__(self, attrs=None, format=None):
    class TimeInput(TextInput):  
    485440            return value.strftime(self.format)
    486441        return value
    487442
    488     def _has_changed(self, initial, data):
    489         # If our field has show_hidden_initial=True, initial will be a string
    490         # formatted by HiddenInput using formats.localize_input, which is not
    491         # necessarily the format used for this  widget. Attempt to convert it.
    492         try:
    493             input_format = formats.get_format('TIME_INPUT_FORMATS')[0]
    494             initial = datetime.datetime.strptime(initial, input_format).time()
    495         except (TypeError, ValueError):
    496             pass
    497         return super(TimeInput, self)._has_changed(self._format_value(initial), data)
    498 
    499443
    500444# Defined at module level so that CheckboxInput is picklable (#17976)
    501445def boolean_check(v):
    class CheckboxInput(Widget):  
    530474            value = values.get(value.lower(), value)
    531475        return bool(value)
    532476
    533     def _has_changed(self, initial, data):
    534         # Sometimes data or initial could be None or '' which should be the
    535         # same thing as False.
    536         if initial == 'False':
    537             # show_hidden_initial may have transformed False to 'False'
    538             initial = False
    539         return bool(initial) != bool(data)
    540477
    541478class Select(Widget):
    542479    allow_multiple_selected = False
    class NullBooleanSelect(Select):  
    612549                'False': False,
    613550                False: False}.get(value, None)
    614551
    615     def _has_changed(self, initial, data):
    616         # For a NullBooleanSelect, None (unknown) and False (No)
    617         # are not the same
    618         if initial is not None:
    619             initial = bool(initial)
    620         if data is not None:
    621             data = bool(data)
    622         return initial != data
    623552
    624553class SelectMultiple(Select):
    625554    allow_multiple_selected = True
    class SelectMultiple(Select):  
    639568            return data.getlist(name)
    640569        return data.get(name, None)
    641570
    642     def _has_changed(self, initial, data):
    643         if initial is None:
    644             initial = []
    645         if data is None:
    646             data = []
    647         if len(initial) != len(data):
    648             return True
    649         initial_set = set([force_text(value) for value in initial])
    650         data_set = set([force_text(value) for value in data])
    651         return data_set != initial_set
    652571
    653572@python_2_unicode_compatible
    654573class RadioInput(SubWidget):
    class MultiWidget(Widget):  
    844763    def value_from_datadict(self, data, files, name):
    845764        return [widget.value_from_datadict(data, files, name + '_%s' % i) for i, widget in enumerate(self.widgets)]
    846765
    847     def _has_changed(self, initial, data):
    848         if initial is None:
    849             initial = ['' for x in range(0, len(data))]
    850         else:
    851             if not isinstance(initial, list):
    852                 initial = self.decompress(initial)
    853         for widget, initial, data in zip(self.widgets, initial, data):
    854             if widget._has_changed(initial, data):
    855                 return True
    856         return False
    857 
    858766    def format_output(self, rendered_widgets):
    859767        """
    860768        Given a list of rendered widgets (as strings), returns a Unicode string
  • docs/releases/1.6.txt

    diff --git a/docs/releases/1.6.txt b/docs/releases/1.6.txt
    index 1f57913..a232a42 100644
    a b Backwards incompatible changes in 1.6  
    4141
    4242Features deprecated in 1.6
    4343==========================
     44
     45``_has_changed`` method on widgets
     46~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     47
     48If you defined your own form widgets and defined the ``_has_changed`` method
     49on a widget, you should now define this method on the form field itself.
  • tests/regressiontests/admin_widgets/tests.py

    diff --git a/tests/regressiontests/admin_widgets/tests.py b/tests/regressiontests/admin_widgets/tests.py
    index 76aa9f4..4523870 100644
    a b class ManyToManyRawIdWidgetTest(DjangoTestCase):  
    425425            '<input type="text" name="test" value="%(m1pk)s" class="vManyToManyRawIdAdminField" /><a href="/widget_admin/admin_widgets/member/" class="related-lookup" id="lookup_id_test" onclick="return showRelatedObjectLookupPopup(this);"> <img src="%(ADMIN_STATIC_PREFIX)simg/selector-search.gif" width="16" height="16" alt="Lookup" /></a>' % dict(admin_static_prefix(), m1pk=m1.pk)
    426426        )
    427427
    428         self.assertEqual(w._has_changed(None, None), False)
    429         self.assertEqual(w._has_changed([], None), False)
    430         self.assertEqual(w._has_changed(None, ['1']), True)
    431         self.assertEqual(w._has_changed([1, 2], ['1', '2']), False)
    432         self.assertEqual(w._has_changed([1, 2], ['1']), True)
    433         self.assertEqual(w._has_changed([1, 2], ['1', '3']), True)
    434 
    435428    def test_m2m_related_model_not_in_admin(self):
    436429        # M2M relationship with model not registered with admin site. Raw ID
    437430        # widget should have no magnifying glass link. See #16542
  • tests/regressiontests/forms/tests/extra.py

    diff --git a/tests/regressiontests/forms/tests/extra.py b/tests/regressiontests/forms/tests/extra.py
    index 07acb29..762b774 100644
    a b class FormsExtraTestCase(TestCase, AssertFormErrorsMixin):  
    428428        # If insufficient data is provided, None is substituted
    429429        self.assertFormErrors(['This field is required.'], f.clean, ['some text',['JP']])
    430430
     431        # test with no initial data
     432        self.assertTrue(f._has_changed(None, ['some text', ['J','P'], ['2007-04-25','6:24:00']]))
     433
     434        # test when the data is the same as initial
     435        self.assertFalse(f._has_changed('some text,JP,2007-04-25 06:24:00',
     436            ['some text', ['J','P'], ['2007-04-25','6:24:00']]))
     437
     438        # test when the first widget's data has changed
     439        self.assertTrue(f._has_changed('some text,JP,2007-04-25 06:24:00',
     440            ['other text', ['J','P'], ['2007-04-25','6:24:00']]))
     441
     442        # test when the last widget's data has changed. this ensures that it is not
     443        # short circuiting while testing the widgets.
     444        self.assertTrue(f._has_changed('some text,JP,2007-04-25 06:24:00',
     445            ['some text', ['J','P'], ['2009-04-25','11:44:00']]))
     446
     447
    431448        class ComplexFieldForm(Form):
    432449            field1 = ComplexField(widget=w)
    433450
    class FormsExtraL10NTestCase(TestCase):  
    725742
    726743    def test_l10n_date_changed(self):
    727744        """
    728         Ensure that SelectDateWidget._has_changed() works correctly with a
    729         localized date format.
     745        Ensure that DateField._has_changed() with SelectDateWidget works
     746        correctly with a localized date format.
    730747        Refs #17165.
    731748        """
    732749        # With Field.show_hidden_initial=False -----------------------
  • tests/regressiontests/forms/tests/fields.py

    diff --git a/tests/regressiontests/forms/tests/fields.py b/tests/regressiontests/forms/tests/fields.py
    index e17d976..7deb345 100644
    a b from decimal import Decimal  
    3535from django.core.files.uploadedfile import SimpleUploadedFile
    3636from django.forms import *
    3737from django.test import SimpleTestCase
     38from django.utils import formats
    3839from django.utils import six
    3940from django.utils._os import upath
    4041
    class FieldsTests(SimpleTestCase):  
    362363        f = DateField()
    363364        self.assertRaisesMessage(ValidationError, "'Enter a valid date.'", f.clean, 'a\x00b')
    364365
     366    def test_datefield_changed(self):
     367        format = '%d/%m/%Y'
     368        f = DateField(input_formats=[format])
     369        d = datetime.date(2007, 9, 17)
     370        self.assertFalse(f._has_changed(d, '17/09/2007'))
     371        self.assertFalse(f._has_changed(d.strftime(format), '17/09/2007'))
     372
    365373    # TimeField ###################################################################
    366374
    367375    def test_timefield_1(self):
    class FieldsTests(SimpleTestCase):  
    388396        self.assertEqual(datetime.time(14, 25, 59), f.clean(' 14:25:59 '))
    389397        self.assertRaisesMessage(ValidationError, "'Enter a valid time.'", f.clean, '   ')
    390398
     399    def test_timefield_changed(self):
     400        t1 = datetime.time(12, 51, 34, 482548)
     401        t2 = datetime.time(12, 51)
     402        format = '%H:%M'
     403        f = TimeField(input_formats=[format])
     404        self.assertTrue(f._has_changed(t1, '12:51'))
     405        self.assertFalse(f._has_changed(t2, '12:51'))
     406
     407        format = '%I:%M %p'
     408        f = TimeField(input_formats=[format])
     409        self.assertFalse(f._has_changed(t2.strftime(format), '12:51 PM'))
     410
    391411    # DateTimeField ###############################################################
    392412
    393413    def test_datetimefield_1(self):
    class FieldsTests(SimpleTestCase):  
    446466    def test_datetimefield_5(self):
    447467        f = DateTimeField(input_formats=['%Y.%m.%d %H:%M:%S.%f'])
    448468        self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 45, 200), f.clean('2006.10.25 14:30:45.0002'))
     469
     470    def test_datetimefield_changed(self):
     471        format = '%Y %m %d %I:%M %p'
     472        f = DateTimeField(input_formats=[format])
     473        d = datetime.datetime(2006, 9, 17, 14, 30, 0)
     474        self.assertFalse(f._has_changed(d, '2006 09 17 2:30 PM'))
     475        # Initial value may be a string from a hidden input
     476        self.assertFalse(f._has_changed(d.strftime(format), '2006 09 17 2:30 PM'))
     477
    449478    # RegexField ##################################################################
    450479
    451480    def test_regexfield_1(self):
    class FieldsTests(SimpleTestCase):  
    566595        self.assertEqual(SimpleUploadedFile,
    567596                         type(f.clean(SimpleUploadedFile('name', b''))))
    568597
     598    def test_filefield_changed(self):
     599        '''
     600        Test for the behavior of _has_changed for FileField. The value of data will
     601        more than likely come from request.FILES. The value of initial data will
     602        likely be a filename stored in the database. Since its value is of no use to
     603        a FileField it is ignored.
     604        '''
     605        f = FileField()
     606
     607        # No file was uploaded and no initial data.
     608        self.assertFalse(f._has_changed('', None))
     609
     610        # A file was uploaded and no initial data.
     611        self.assertTrue(f._has_changed('', {'filename': 'resume.txt', 'content': 'My resume'}))
     612
     613        # A file was not uploaded, but there is initial data
     614        self.assertFalse(f._has_changed('resume.txt', None))
     615
     616        # A file was uploaded and there is initial data (file identity is not dealt
     617        # with here)
     618        self.assertTrue(f._has_changed('resume.txt', {'filename': 'resume.txt', 'content': 'My resume'}))
     619
     620
    569621    # URLField ##################################################################
    570622
    571623    def test_urlfield_1(self):
    class FieldsTests(SimpleTestCase):  
    709761    def test_boolean_picklable(self):
    710762        self.assertIsInstance(pickle.loads(pickle.dumps(BooleanField())), BooleanField)
    711763
     764    def test_booleanfield_changed(self):
     765        f = BooleanField()
     766        self.assertFalse(f._has_changed(None, None))
     767        self.assertFalse(f._has_changed(None, ''))
     768        self.assertFalse(f._has_changed('', None))
     769        self.assertFalse(f._has_changed('', ''))
     770        self.assertTrue(f._has_changed(False, 'on'))
     771        self.assertFalse(f._has_changed(True, 'on'))
     772        self.assertTrue(f._has_changed(True, ''))
     773        # Initial value may have mutated to a string due to show_hidden_initial (#19537)
     774        self.assertTrue(f._has_changed('False', 'on'))
     775
    712776    # ChoiceField #################################################################
    713777
    714778    def test_choicefield_1(self):
    class FieldsTests(SimpleTestCase):  
    825889        self.assertEqual(False, f.cleaned_data['nullbool1'])
    826890        self.assertEqual(None, f.cleaned_data['nullbool2'])
    827891
     892    def test_nullbooleanfield_changed(self):
     893        f = NullBooleanField()
     894        self.assertTrue(f._has_changed(False, None))
     895        self.assertTrue(f._has_changed(None, False))
     896        self.assertFalse(f._has_changed(None, None))
     897        self.assertFalse(f._has_changed(False, False))
     898        self.assertTrue(f._has_changed(True, False))
     899        self.assertTrue(f._has_changed(True, None))
     900        self.assertTrue(f._has_changed(True, False))
     901
    828902    # MultipleChoiceField #########################################################
    829903
    830904    def test_multiplechoicefield_1(self):
    class FieldsTests(SimpleTestCase):  
    866940        self.assertRaisesMessage(ValidationError, "'Select a valid choice. 6 is not one of the available choices.'", f.clean, ['6'])
    867941        self.assertRaisesMessage(ValidationError, "'Select a valid choice. 6 is not one of the available choices.'", f.clean, ['1','6'])
    868942
     943    def test_multiplechoicefield_changed(self):
     944        f = MultipleChoiceField(choices=[('1', 'One'), ('2', 'Two'), ('3', 'Three')])
     945        self.assertFalse(f._has_changed(None, None))
     946        self.assertFalse(f._has_changed([], None))
     947        self.assertTrue(f._has_changed(None, ['1']))
     948        self.assertFalse(f._has_changed([1, 2], ['1', '2']))
     949        self.assertFalse(f._has_changed([2, 1], ['1', '2']))
     950        self.assertTrue(f._has_changed([1, 2], ['1']))
     951        self.assertTrue(f._has_changed([1, 2], ['1', '3']))
     952
    869953    # TypedMultipleChoiceField ############################################################
    870954    # TypedMultipleChoiceField is just like MultipleChoiceField, except that coerced types
    871955    # will be returned:
    class FieldsTests(SimpleTestCase):  
    10481132        self.assertRaisesMessage(ValidationError, "'Enter a valid time.'", f.clean, ['2006-01-10', ''])
    10491133        self.assertRaisesMessage(ValidationError, "'Enter a valid time.'", f.clean, ['2006-01-10'])
    10501134        self.assertRaisesMessage(ValidationError, "'Enter a valid date.'", f.clean, ['', '07:30'])
     1135
     1136    def test_splitdatetimefield_changed(self):
     1137        f = SplitDateTimeField(input_date_formats=['%d/%m/%Y'])
     1138        self.assertTrue(f._has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), ['2008-05-06', '12:40:00']))
     1139        self.assertFalse(f._has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), ['06/05/2008', '12:40']))
     1140        self.assertTrue(f._has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), ['06/05/2008', '12:41']))
  • tests/regressiontests/forms/tests/widgets.py

    diff --git a/tests/regressiontests/forms/tests/widgets.py b/tests/regressiontests/forms/tests/widgets.py
    index f9dc4a7..393bfcc 100644
    a b class FormsWidgetTestCase(TestCase):  
    148148
    149149        self.assertHTMLEqual(w.render('email', 'ŠĐĆŽćžšđ', attrs={'class': 'fun'}), '<input type="file" class="fun" name="email" />')
    150150
    151         # Test for the behavior of _has_changed for FileInput. The value of data will
    152         # more than likely come from request.FILES. The value of initial data will
    153         # likely be a filename stored in the database. Since its value is of no use to
    154         # a FileInput it is ignored.
    155         w = FileInput()
    156 
    157         # No file was uploaded and no initial data.
    158         self.assertFalse(w._has_changed('', None))
    159 
    160         # A file was uploaded and no initial data.
    161         self.assertTrue(w._has_changed('', {'filename': 'resume.txt', 'content': 'My resume'}))
    162 
    163         # A file was not uploaded, but there is initial data
    164         self.assertFalse(w._has_changed('resume.txt', None))
    165 
    166         # A file was uploaded and there is initial data (file identity is not dealt
    167         # with here)
    168         self.assertTrue(w._has_changed('resume.txt', {'filename': 'resume.txt', 'content': 'My resume'}))
    169 
    170151    def test_textarea(self):
    171152        w = Textarea()
    172153        self.assertHTMLEqual(w.render('msg', ''), '<textarea rows="10" cols="40" name="msg"></textarea>')
    class FormsWidgetTestCase(TestCase):  
    233214        self.assertIsInstance(value, bool)
    234215        self.assertTrue(value)
    235216
    236         self.assertFalse(w._has_changed(None, None))
    237         self.assertFalse(w._has_changed(None, ''))
    238         self.assertFalse(w._has_changed('', None))
    239         self.assertFalse(w._has_changed('', ''))
    240         self.assertTrue(w._has_changed(False, 'on'))
    241         self.assertFalse(w._has_changed(True, 'on'))
    242         self.assertTrue(w._has_changed(True, ''))
    243         # Initial value may have mutated to a string due to show_hidden_initial (#19537)
    244         self.assertTrue(w._has_changed('False', 'on'))
    245 
    246217    def test_select(self):
    247218        w = Select()
    248219        self.assertHTMLEqual(w.render('beatle', 'J', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<select name="beatle">
    class FormsWidgetTestCase(TestCase):  
    415386<option value="2">Yes</option>
    416387<option value="3" selected="selected">No</option>
    417388</select>""")
    418         self.assertTrue(w._has_changed(False, None))
    419         self.assertTrue(w._has_changed(None, False))
    420         self.assertFalse(w._has_changed(None, None))
    421         self.assertFalse(w._has_changed(False, False))
    422         self.assertTrue(w._has_changed(True, False))
    423         self.assertTrue(w._has_changed(True, None))
    424         self.assertTrue(w._has_changed(True, False))
    425389
    426390    def test_selectmultiple(self):
    427391        w = SelectMultiple()
    class FormsWidgetTestCase(TestCase):  
    535499        # Unicode choices are correctly rendered as HTML
    536500        self.assertHTMLEqual(w.render('nums', ['ŠĐĆŽćžšđ'], choices=[('ŠĐĆŽćžšđ', 'ŠĐabcĆŽćžšđ'), ('ćžšđ', 'abcćžšđ')]), '<select multiple="multiple" name="nums">\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" selected="selected">\u0160\u0110abc\u0106\u017d\u0107\u017e\u0161\u0111</option>\n<option value="\u0107\u017e\u0161\u0111">abc\u0107\u017e\u0161\u0111</option>\n</select>')
    537501
    538         # Test the usage of _has_changed
    539         self.assertFalse(w._has_changed(None, None))
    540         self.assertFalse(w._has_changed([], None))
    541         self.assertTrue(w._has_changed(None, ['1']))
    542         self.assertFalse(w._has_changed([1, 2], ['1', '2']))
    543         self.assertTrue(w._has_changed([1, 2], ['1']))
    544         self.assertTrue(w._has_changed([1, 2], ['1', '3']))
    545 
    546502        # Choices can be nested one level in order to create HTML optgroups:
    547503        w.choices = (('outer1', 'Outer 1'), ('Group "1"', (('inner1', 'Inner 1'), ('inner2', 'Inner 2'))))
    548504        self.assertHTMLEqual(w.render('nestchoice', None), """<select multiple="multiple" name="nestchoice">
    beatle J R Ringo False""")  
    844800<li><label><input type="checkbox" name="escape" value="good" /> you &gt; me</label></li>
    845801</ul>""")
    846802
    847         # Test the usage of _has_changed
    848         self.assertFalse(w._has_changed(None, None))
    849         self.assertFalse(w._has_changed([], None))
    850         self.assertTrue(w._has_changed(None, ['1']))
    851         self.assertFalse(w._has_changed([1, 2], ['1', '2']))
    852         self.assertTrue(w._has_changed([1, 2], ['1']))
    853         self.assertTrue(w._has_changed([1, 2], ['1', '3']))
    854         self.assertFalse(w._has_changed([2, 1], ['1', '2']))
    855 
    856803        # Unicode choices are correctly rendered as HTML
    857804        self.assertHTMLEqual(w.render('nums', ['ŠĐĆŽćžšđ'], choices=[('ŠĐĆŽćžšđ', 'ŠĐabcĆŽćžšđ'), ('ćžšđ', 'abcćžšđ')]), '<ul>\n<li><label><input type="checkbox" name="nums" value="1" /> 1</label></li>\n<li><label><input type="checkbox" name="nums" value="2" /> 2</label></li>\n<li><label><input type="checkbox" name="nums" value="3" /> 3</label></li>\n<li><label><input checked="checked" type="checkbox" name="nums" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" /> \u0160\u0110abc\u0106\u017d\u0107\u017e\u0161\u0111</label></li>\n<li><label><input type="checkbox" name="nums" value="\u0107\u017e\u0161\u0111" /> abc\u0107\u017e\u0161\u0111</label></li>\n</ul>')
    858805
    beatle J R Ringo False""")  
    879826        w = MyMultiWidget(widgets=(TextInput(attrs={'class': 'big'}), TextInput(attrs={'class': 'small'})), attrs={'id': 'bar'})
    880827        self.assertHTMLEqual(w.render('name', ['john', 'lennon']), '<input id="bar_0" type="text" class="big" value="john" name="name_0" /><br /><input id="bar_1" type="text" class="small" value="lennon" name="name_1" />')
    881828
    882         w = MyMultiWidget(widgets=(TextInput(), TextInput()))
    883 
    884         # test with no initial data
    885         self.assertTrue(w._has_changed(None, ['john', 'lennon']))
    886 
    887         # test when the data is the same as initial
    888         self.assertFalse(w._has_changed('john__lennon', ['john', 'lennon']))
    889 
    890         # test when the first widget's data has changed
    891         self.assertTrue(w._has_changed('john__lennon', ['alfred', 'lennon']))
    892 
    893         # test when the last widget's data has changed. this ensures that it is not
    894         # short circuiting while testing the widgets.
    895         self.assertTrue(w._has_changed('john__lennon', ['john', 'denver']))
    896 
    897829    def test_splitdatetime(self):
    898830        w = SplitDateTimeWidget()
    899831        self.assertHTMLEqual(w.render('date', ''), '<input type="text" name="date_0" /><input type="text" name="date_1" />')
    beatle J R Ringo False""")  
    909841        w = SplitDateTimeWidget(date_format='%d/%m/%Y', time_format='%H:%M')
    910842        self.assertHTMLEqual(w.render('date', datetime.datetime(2006, 1, 10, 7, 30)), '<input type="text" name="date_0" value="10/01/2006" /><input type="text" name="date_1" value="07:30" />')
    911843
    912         self.assertTrue(w._has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), ['2008-05-06', '12:40:00']))
    913         self.assertFalse(w._has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), ['06/05/2008', '12:40']))
    914         self.assertTrue(w._has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), ['06/05/2008', '12:41']))
    915 
    916844    def test_datetimeinput(self):
    917845        w = DateTimeInput()
    918846        self.assertHTMLEqual(w.render('date', None), '<input type="text" name="date" />')
    beatle J R Ringo False""")  
    927855        # Use 'format' to change the way a value is displayed.
    928856        w = DateTimeInput(format='%d/%m/%Y %H:%M', attrs={'type': 'datetime'})
    929857        self.assertHTMLEqual(w.render('date', d), '<input type="datetime" name="date" value="17/09/2007 12:51" />')
    930         self.assertFalse(w._has_changed(d, '17/09/2007 12:51'))
    931 
    932         # Make sure a custom format works with _has_changed. The hidden input will use
    933         data = datetime.datetime(2010, 3, 6, 12, 0, 0)
    934         custom_format = '%d.%m.%Y %H:%M'
    935         w = DateTimeInput(format=custom_format)
    936         self.assertFalse(w._has_changed(formats.localize_input(data), data.strftime(custom_format)))
    937858
    938859    def test_dateinput(self):
    939860        w = DateInput()
    beatle J R Ringo False""")  
    950871        # Use 'format' to change the way a value is displayed.
    951872        w = DateInput(format='%d/%m/%Y', attrs={'type': 'date'})
    952873        self.assertHTMLEqual(w.render('date', d), '<input type="date" name="date" value="17/09/2007" />')
    953         self.assertFalse(w._has_changed(d, '17/09/2007'))
    954 
    955         # Make sure a custom format works with _has_changed. The hidden input will use
    956         data = datetime.date(2010, 3, 6)
    957         custom_format = '%d.%m.%Y'
    958         w = DateInput(format=custom_format)
    959         self.assertFalse(w._has_changed(formats.localize_input(data), data.strftime(custom_format)))
    960874
    961875    def test_timeinput(self):
    962876        w = TimeInput()
    beatle J R Ringo False""")  
    975889        # Use 'format' to change the way a value is displayed.
    976890        w = TimeInput(format='%H:%M', attrs={'type': 'time'})
    977891        self.assertHTMLEqual(w.render('time', t), '<input type="time" name="time" value="12:51" />')
    978         self.assertFalse(w._has_changed(t, '12:51'))
    979 
    980         # Make sure a custom format works with _has_changed. The hidden input will use
    981         data = datetime.time(13, 0)
    982         custom_format = '%I:%M %p'
    983         w = TimeInput(format=custom_format)
    984         self.assertFalse(w._has_changed(formats.localize_input(data), data.strftime(custom_format)))
    985892
    986893    def test_splithiddendatetime(self):
    987894        from django.forms.widgets import SplitHiddenDateTimeWidget
    class FormsI18NWidgetsTestCase(TestCase):  
    1009916        deactivate()
    1010917        super(FormsI18NWidgetsTestCase, self).tearDown()
    1011918
    1012     def test_splitdatetime(self):
    1013         w = SplitDateTimeWidget(date_format='%d/%m/%Y', time_format='%H:%M')
    1014         self.assertTrue(w._has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), ['06.05.2008', '12:41']))
    1015 
    1016919    def test_datetimeinput(self):
    1017920        w = DateTimeInput()
    1018921        d = datetime.datetime(2007, 9, 17, 12, 51, 34, 482548)
Back to Top