Ticket #16612: 16612-1.diff
File 16612-1.diff, 40.7 KB (added by , 12 years ago) |
---|
-
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): 213 213 if value: 214 214 return value.split(',') 215 215 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 True223 for pk1, pk2 in zip(initial, data):224 if force_text(pk1) != force_text(pk2):225 return True226 return False227 216 228 217 class RelatedFieldWidgetWrapper(forms.Widget): 229 218 """ … … class RelatedFieldWidgetWrapper(forms.Widget): 279 268 def value_from_datadict(self, data, files, name): 280 269 return self.widget.value_from_datadict(data, files, name) 281 270 282 def _has_changed(self, initial, data):283 return self.widget._has_changed(initial, data)284 285 271 def id_for_label(self, id_): 286 272 return self.widget.id_for_label(id_) 287 273 -
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 7 7 from django.utils import translation 8 8 9 9 from django.contrib.gis.gdal import OGRException 10 from django.contrib.gis.geos import GEOSGeometry, GEOSException , fromstr10 from django.contrib.gis.geos import GEOSGeometry, GEOSException 11 11 12 12 # Creating a template context that contains Django settings 13 13 # values needed by admin map templates. … … class OpenLayersWidget(Textarea): 117 117 raise TypeError 118 118 map_options[js_name] = value 119 119 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 object125 if isinstance(initial, six.string_types):126 try:127 initial = GEOSGeometry(initial)128 except (GEOSException, ValueError):129 initial = None130 131 # Only do a geographic comparison if both values are available132 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 geometry136 # 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 existence141 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 1 1 from __future__ import unicode_literals 2 2 3 3 from django import forms 4 from django.utils import six 4 5 from django.utils.translation import ugettext_lazy as _ 5 6 6 7 # While this couples the geographic forms to the GEOS library, 7 8 # it decouples from database (by not importing SpatialBackend). 8 from django.contrib.gis.geos import GEOSException, GEOSGeometry 9 from django.contrib.gis.geos import GEOSException, GEOSGeometry, fromstr 10 9 11 10 12 class GeometryField(forms.Field): 11 13 """ … … class GeometryField(forms.Field): 73 75 raise forms.ValidationError(self.error_messages['transform_error']) 74 76 75 77 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): 38 38 """ Check that changes are accurately noticed by OpenLayersWidget. """ 39 39 geoadmin = admin.site._registry[City] 40 40 form = geoadmin.get_changelist_form(None)() 41 has_changed = form.fields['point']. widget._has_changed41 has_changed = form.fields['point']._has_changed 42 42 43 43 initial = Point(13.4197458572965953, 52.5194108501149799, srid=4326) 44 44 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): 135 135 s = Select(choices=choices) 136 136 select_html = s.render(field % name, val, local_attrs) 137 137 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 pass145 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): 175 175 """ 176 176 return {} 177 177 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 178 197 def __deepcopy__(self, memo): 179 198 result = copy.copy(self) 180 199 memo[id(self)] = result … … class BaseTemporalField(Field): 348 367 def strptime(self, value, format): 349 368 raise NotImplementedError('Subclasses must define this method.') 350 369 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 351 377 class DateField(BaseTemporalField): 352 378 widget = DateInput 353 379 input_formats = formats.get_format_lazy('DATE_INPUT_FORMATS') … … class DateField(BaseTemporalField): 371 397 def strptime(self, value, format): 372 398 return datetime.datetime.strptime(value, format).date() 373 399 400 374 401 class TimeField(BaseTemporalField): 375 402 widget = TimeInput 376 403 input_formats = formats.get_format_lazy('TIME_INPUT_FORMATS') … … class FileField(Field): 529 556 return initial 530 557 return data 531 558 559 def _has_changed(self, initial, data): 560 if data is None: 561 return False 562 return True 563 564 532 565 class ImageField(FileField): 533 566 default_error_messages = { 534 567 'invalid_image': _("Upload a valid image. The file you uploaded was either not an image or a corrupted image."), … … class URLField(CharField): 618 651 value = urlunsplit(url_fields) 619 652 return value 620 653 654 621 655 class BooleanField(Field): 622 656 widget = CheckboxInput 623 657 … … class BooleanField(Field): 636 670 raise ValidationError(self.error_messages['required']) 637 671 return value 638 672 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 639 682 class NullBooleanField(BooleanField): 640 683 """ 641 684 A field whose valid values are None, True and False. Invalid values are … … class NullBooleanField(BooleanField): 660 703 def validate(self, value): 661 704 pass 662 705 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 663 715 class ChoiceField(Field): 664 716 widget = Select 665 717 default_error_messages = { … … class TypedChoiceField(ChoiceField): 739 791 def validate(self, value): 740 792 pass 741 793 794 742 795 class MultipleChoiceField(ChoiceField): 743 796 hidden_widget = MultipleHiddenInput 744 797 widget = SelectMultiple … … class MultipleChoiceField(ChoiceField): 765 818 if not self.valid_value(val): 766 819 raise ValidationError(self.error_messages['invalid_choice'] % {'value': val}) 767 820 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 768 833 class TypedMultipleChoiceField(MultipleChoiceField): 769 834 def __init__(self, *args, **kwargs): 770 835 self.coerce = kwargs.pop('coerce', lambda val: val) … … class MultiValueField(Field): 899 964 """ 900 965 raise NotImplementedError('Subclasses must implement this method.') 901 966 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 902 979 class FilePathField(ChoiceField): 903 980 def __init__(self, path, match=None, recursive=False, allow_files=True, 904 981 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): 341 341 hidden_widget = field.hidden_widget() 342 342 initial_value = hidden_widget.value_from_datadict( 343 343 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): 345 351 self._changed_data.append(name) 346 352 return self._changed_data 347 353 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, 839 839 840 840 # Fields ##################################################################### 841 841 842 class InlineForeignKeyHiddenInput(HiddenInput):843 def _has_changed(self, initial, data):844 return False845 846 842 class InlineForeignKeyField(Field): 847 843 """ 848 844 A basic integer field that deals with validating the given value to a 849 845 given parent instance in an inline. 850 846 """ 847 widget = HiddenInput 851 848 default_error_messages = { 852 849 'invalid_choice': _('The inline foreign key did not match the parent instance primary key.'), 853 850 } … … class InlineForeignKeyField(Field): 862 859 else: 863 860 kwargs["initial"] = self.parent_instance.pk 864 861 kwargs["required"] = False 865 kwargs["widget"] = InlineForeignKeyHiddenInput866 862 super(InlineForeignKeyField, self).__init__(*args, **kwargs) 867 863 868 864 def clean(self, value): … … class InlineForeignKeyField(Field): 880 876 raise ValidationError(self.error_messages['invalid_choice']) 881 877 return self.parent_instance 882 878 879 def _has_changed(self, initial, data): 880 return False 881 883 882 class ModelChoiceIterator(object): 884 883 def __init__(self, field): 885 884 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)): 208 208 """ 209 209 return data.get(name, None) 210 210 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 is216 # the same as an empty string, if the data or inital value we get217 # is None, replace it w/ ''.218 if data is None:219 data_value = ''220 else:221 data_value = data222 if initial is None:223 initial_value = ''224 else:225 initial_value = initial226 if force_text(initial_value) != force_text(data_value):227 return True228 return False229 230 211 def id_for_label(self, id_): 231 212 """ 232 213 Returns the HTML ID attribute of this Widget for use by a <label>, … … class FileInput(Input): 325 306 "File widgets take data from FILES, not POST" 326 307 return files.get(name, None) 327 308 328 def _has_changed(self, initial, data):329 if data is None:330 return False331 return True332 309 333 310 FILE_INPUT_CONTRADICTION = object() 334 311 … … class DateInput(TextInput): 426 403 return value.strftime(self.format) 427 404 return value 428 405 429 def _has_changed(self, initial, data):430 # If our field has show_hidden_initial=True, initial will be a string431 # formatted by HiddenInput using formats.localize_input, which is not432 # 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 pass438 return super(DateInput, self)._has_changed(self._format_value(initial), data)439 440 406 441 407 class DateTimeInput(TextInput): 442 408 def __init__(self, attrs=None, format=None): … … class DateTimeInput(TextInput): 456 422 return value.strftime(self.format) 457 423 return value 458 424 459 def _has_changed(self, initial, data):460 # If our field has show_hidden_initial=True, initial will be a string461 # formatted by HiddenInput using formats.localize_input, which is not462 # 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 pass468 return super(DateTimeInput, self)._has_changed(self._format_value(initial), data)469 470 425 471 426 class TimeInput(TextInput): 472 427 def __init__(self, attrs=None, format=None): … … class TimeInput(TextInput): 485 440 return value.strftime(self.format) 486 441 return value 487 442 488 def _has_changed(self, initial, data):489 # If our field has show_hidden_initial=True, initial will be a string490 # formatted by HiddenInput using formats.localize_input, which is not491 # 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 pass497 return super(TimeInput, self)._has_changed(self._format_value(initial), data)498 499 443 500 444 # Defined at module level so that CheckboxInput is picklable (#17976) 501 445 def boolean_check(v): … … class CheckboxInput(Widget): 530 474 value = values.get(value.lower(), value) 531 475 return bool(value) 532 476 533 def _has_changed(self, initial, data):534 # Sometimes data or initial could be None or '' which should be the535 # same thing as False.536 if initial == 'False':537 # show_hidden_initial may have transformed False to 'False'538 initial = False539 return bool(initial) != bool(data)540 477 541 478 class Select(Widget): 542 479 allow_multiple_selected = False … … class NullBooleanSelect(Select): 612 549 'False': False, 613 550 False: False}.get(value, None) 614 551 615 def _has_changed(self, initial, data):616 # For a NullBooleanSelect, None (unknown) and False (No)617 # are not the same618 if initial is not None:619 initial = bool(initial)620 if data is not None:621 data = bool(data)622 return initial != data623 552 624 553 class SelectMultiple(Select): 625 554 allow_multiple_selected = True … … class SelectMultiple(Select): 639 568 return data.getlist(name) 640 569 return data.get(name, None) 641 570 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 True649 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_set652 571 653 572 @python_2_unicode_compatible 654 573 class RadioInput(SubWidget): … … class MultiWidget(Widget): 844 763 def value_from_datadict(self, data, files, name): 845 764 return [widget.value_from_datadict(data, files, name + '_%s' % i) for i, widget in enumerate(self.widgets)] 846 765 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 True856 return False857 858 766 def format_output(self, rendered_widgets): 859 767 """ 860 768 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 41 41 42 42 Features deprecated in 1.6 43 43 ========================== 44 45 ``_has_changed`` method on widgets 46 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 47 48 If you defined your own form widgets and defined the ``_has_changed`` method 49 on 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): 425 425 '<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) 426 426 ) 427 427 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 435 428 def test_m2m_related_model_not_in_admin(self): 436 429 # M2M relationship with model not registered with admin site. Raw ID 437 430 # 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): 428 428 # If insufficient data is provided, None is substituted 429 429 self.assertFormErrors(['This field is required.'], f.clean, ['some text',['JP']]) 430 430 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 431 448 class ComplexFieldForm(Form): 432 449 field1 = ComplexField(widget=w) 433 450 … … class FormsExtraL10NTestCase(TestCase): 725 742 726 743 def test_l10n_date_changed(self): 727 744 """ 728 Ensure that SelectDateWidget._has_changed() works correctly with a729 localized date format.745 Ensure that DateField._has_changed() with SelectDateWidget works 746 correctly with a localized date format. 730 747 Refs #17165. 731 748 """ 732 749 # 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 35 35 from django.core.files.uploadedfile import SimpleUploadedFile 36 36 from django.forms import * 37 37 from django.test import SimpleTestCase 38 from django.utils import formats 38 39 from django.utils import six 39 40 from django.utils._os import upath 40 41 … … class FieldsTests(SimpleTestCase): 362 363 f = DateField() 363 364 self.assertRaisesMessage(ValidationError, "'Enter a valid date.'", f.clean, 'a\x00b') 364 365 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 365 373 # TimeField ################################################################### 366 374 367 375 def test_timefield_1(self): … … class FieldsTests(SimpleTestCase): 388 396 self.assertEqual(datetime.time(14, 25, 59), f.clean(' 14:25:59 ')) 389 397 self.assertRaisesMessage(ValidationError, "'Enter a valid time.'", f.clean, ' ') 390 398 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 391 411 # DateTimeField ############################################################### 392 412 393 413 def test_datetimefield_1(self): … … class FieldsTests(SimpleTestCase): 446 466 def test_datetimefield_5(self): 447 467 f = DateTimeField(input_formats=['%Y.%m.%d %H:%M:%S.%f']) 448 468 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 449 478 # RegexField ################################################################## 450 479 451 480 def test_regexfield_1(self): … … class FieldsTests(SimpleTestCase): 566 595 self.assertEqual(SimpleUploadedFile, 567 596 type(f.clean(SimpleUploadedFile('name', b'')))) 568 597 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 569 621 # URLField ################################################################## 570 622 571 623 def test_urlfield_1(self): … … class FieldsTests(SimpleTestCase): 709 761 def test_boolean_picklable(self): 710 762 self.assertIsInstance(pickle.loads(pickle.dumps(BooleanField())), BooleanField) 711 763 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 712 776 # ChoiceField ################################################################# 713 777 714 778 def test_choicefield_1(self): … … class FieldsTests(SimpleTestCase): 825 889 self.assertEqual(False, f.cleaned_data['nullbool1']) 826 890 self.assertEqual(None, f.cleaned_data['nullbool2']) 827 891 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 828 902 # MultipleChoiceField ######################################################### 829 903 830 904 def test_multiplechoicefield_1(self): … … class FieldsTests(SimpleTestCase): 866 940 self.assertRaisesMessage(ValidationError, "'Select a valid choice. 6 is not one of the available choices.'", f.clean, ['6']) 867 941 self.assertRaisesMessage(ValidationError, "'Select a valid choice. 6 is not one of the available choices.'", f.clean, ['1','6']) 868 942 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 869 953 # TypedMultipleChoiceField ############################################################ 870 954 # TypedMultipleChoiceField is just like MultipleChoiceField, except that coerced types 871 955 # will be returned: … … class FieldsTests(SimpleTestCase): 1048 1132 self.assertRaisesMessage(ValidationError, "'Enter a valid time.'", f.clean, ['2006-01-10', '']) 1049 1133 self.assertRaisesMessage(ValidationError, "'Enter a valid time.'", f.clean, ['2006-01-10']) 1050 1134 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): 148 148 149 149 self.assertHTMLEqual(w.render('email', 'ŠĐĆŽćžšđ', attrs={'class': 'fun'}), '<input type="file" class="fun" name="email" />') 150 150 151 # Test for the behavior of _has_changed for FileInput. The value of data will152 # more than likely come from request.FILES. The value of initial data will153 # likely be a filename stored in the database. Since its value is of no use to154 # 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 data164 self.assertFalse(w._has_changed('resume.txt', None))165 166 # A file was uploaded and there is initial data (file identity is not dealt167 # with here)168 self.assertTrue(w._has_changed('resume.txt', {'filename': 'resume.txt', 'content': 'My resume'}))169 170 151 def test_textarea(self): 171 152 w = Textarea() 172 153 self.assertHTMLEqual(w.render('msg', ''), '<textarea rows="10" cols="40" name="msg"></textarea>') … … class FormsWidgetTestCase(TestCase): 233 214 self.assertIsInstance(value, bool) 234 215 self.assertTrue(value) 235 216 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 246 217 def test_select(self): 247 218 w = Select() 248 219 self.assertHTMLEqual(w.render('beatle', 'J', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<select name="beatle"> … … class FormsWidgetTestCase(TestCase): 415 386 <option value="2">Yes</option> 416 387 <option value="3" selected="selected">No</option> 417 388 </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))425 389 426 390 def test_selectmultiple(self): 427 391 w = SelectMultiple() … … class FormsWidgetTestCase(TestCase): 535 499 # Unicode choices are correctly rendered as HTML 536 500 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>') 537 501 538 # Test the usage of _has_changed539 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 546 502 # Choices can be nested one level in order to create HTML optgroups: 547 503 w.choices = (('outer1', 'Outer 1'), ('Group "1"', (('inner1', 'Inner 1'), ('inner2', 'Inner 2')))) 548 504 self.assertHTMLEqual(w.render('nestchoice', None), """<select multiple="multiple" name="nestchoice"> … … beatle J R Ringo False""") 844 800 <li><label><input type="checkbox" name="escape" value="good" /> you > me</label></li> 845 801 </ul>""") 846 802 847 # Test the usage of _has_changed848 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 856 803 # Unicode choices are correctly rendered as HTML 857 804 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>') 858 805 … … beatle J R Ringo False""") 879 826 w = MyMultiWidget(widgets=(TextInput(attrs={'class': 'big'}), TextInput(attrs={'class': 'small'})), attrs={'id': 'bar'}) 880 827 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" />') 881 828 882 w = MyMultiWidget(widgets=(TextInput(), TextInput()))883 884 # test with no initial data885 self.assertTrue(w._has_changed(None, ['john', 'lennon']))886 887 # test when the data is the same as initial888 self.assertFalse(w._has_changed('john__lennon', ['john', 'lennon']))889 890 # test when the first widget's data has changed891 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 not894 # short circuiting while testing the widgets.895 self.assertTrue(w._has_changed('john__lennon', ['john', 'denver']))896 897 829 def test_splitdatetime(self): 898 830 w = SplitDateTimeWidget() 899 831 self.assertHTMLEqual(w.render('date', ''), '<input type="text" name="date_0" /><input type="text" name="date_1" />') … … beatle J R Ringo False""") 909 841 w = SplitDateTimeWidget(date_format='%d/%m/%Y', time_format='%H:%M') 910 842 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" />') 911 843 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 916 844 def test_datetimeinput(self): 917 845 w = DateTimeInput() 918 846 self.assertHTMLEqual(w.render('date', None), '<input type="text" name="date" />') … … beatle J R Ringo False""") 927 855 # Use 'format' to change the way a value is displayed. 928 856 w = DateTimeInput(format='%d/%m/%Y %H:%M', attrs={'type': 'datetime'}) 929 857 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 use933 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)))937 858 938 859 def test_dateinput(self): 939 860 w = DateInput() … … beatle J R Ringo False""") 950 871 # Use 'format' to change the way a value is displayed. 951 872 w = DateInput(format='%d/%m/%Y', attrs={'type': 'date'}) 952 873 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 use956 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)))960 874 961 875 def test_timeinput(self): 962 876 w = TimeInput() … … beatle J R Ringo False""") 975 889 # Use 'format' to change the way a value is displayed. 976 890 w = TimeInput(format='%H:%M', attrs={'type': 'time'}) 977 891 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 use981 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)))985 892 986 893 def test_splithiddendatetime(self): 987 894 from django.forms.widgets import SplitHiddenDateTimeWidget … … class FormsI18NWidgetsTestCase(TestCase): 1009 916 deactivate() 1010 917 super(FormsI18NWidgetsTestCase, self).tearDown() 1011 918 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 1016 919 def test_datetimeinput(self): 1017 920 w = DateTimeInput() 1018 921 d = datetime.datetime(2007, 9, 17, 12, 51, 34, 482548)