diff options
| author | Claude Paroz <claude@2xlibre.net> | 2013-01-25 20:50:46 +0100 |
|---|---|---|
| committer | Claude Paroz <claude@2xlibre.net> | 2013-01-25 20:50:46 +0100 |
| commit | ebb504db692cac496f4f45762d1d14644c9fa6fa (patch) | |
| tree | 1a1ac132d765f6e93c390fe532f9b39d1edee096 /django/forms | |
| parent | ce27fb198dcce5dad47de83fc81119d3bb6567ce (diff) | |
Moved has_changed logic from widget to form field
Refs #16612. Thanks Aymeric Augustin for the suggestion.
Diffstat (limited to 'django/forms')
| -rw-r--r-- | django/forms/extras/widgets.py | 8 | ||||
| -rw-r--r-- | django/forms/fields.py | 77 | ||||
| -rw-r--r-- | django/forms/forms.py | 8 | ||||
| -rw-r--r-- | django/forms/models.py | 9 | ||||
| -rw-r--r-- | django/forms/widgets.py | 92 |
5 files changed, 88 insertions, 106 deletions
diff --git a/django/forms/extras/widgets.py b/django/forms/extras/widgets.py index c5ca1424c8..e939a8f665 100644 --- a/django/forms/extras/widgets.py +++ b/django/forms/extras/widgets.py @@ -135,11 +135,3 @@ class SelectDateWidget(Widget): s = Select(choices=choices) select_html = s.render(field % name, val, local_attrs) return select_html - - def _has_changed(self, initial, data): - try: - input_format = get_format('DATE_INPUT_FORMATS')[0] - data = datetime_safe.datetime.strptime(data, input_format).date() - except (TypeError, ValueError): - pass - return super(SelectDateWidget, self)._has_changed(initial, data) diff --git a/django/forms/fields.py b/django/forms/fields.py index 4438812a37..1e9cbcb4d9 100644 --- a/django/forms/fields.py +++ b/django/forms/fields.py @@ -175,6 +175,25 @@ class Field(object): """ return {} + def _has_changed(self, initial, data): + """ + Return True if data differs from initial. + """ + # 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): + return True + return False + def __deepcopy__(self, memo): result = copy.copy(self) memo[id(self)] = result @@ -348,6 +367,13 @@ 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 input_formats = formats.get_format_lazy('DATE_INPUT_FORMATS') @@ -371,6 +397,7 @@ class DateField(BaseTemporalField): def strptime(self, value, format): return datetime.datetime.strptime(value, format).date() + class TimeField(BaseTemporalField): widget = TimeInput input_formats = formats.get_format_lazy('TIME_INPUT_FORMATS') @@ -529,6 +556,12 @@ class FileField(Field): return initial return data + def _has_changed(self, initial, data): + if data is None: + return False + return True + + class ImageField(FileField): default_error_messages = { 'invalid_image': _("Upload a valid image. The file you uploaded was either not an image or a corrupted image."), @@ -618,6 +651,7 @@ class URLField(CharField): value = urlunsplit(url_fields) return value + class BooleanField(Field): widget = CheckboxInput @@ -636,6 +670,15 @@ class BooleanField(Field): raise ValidationError(self.error_messages['required']) return value + def _has_changed(self, initial, data): + # Sometimes data or initial could be None or '' which should be the + # same thing as False. + if initial == 'False': + # show_hidden_initial may have transformed False to 'False' + initial = False + return bool(initial) != bool(data) + + class NullBooleanField(BooleanField): """ A field whose valid values are None, True and False. Invalid values are @@ -660,6 +703,15 @@ class NullBooleanField(BooleanField): def validate(self, value): pass + def _has_changed(self, initial, data): + # None (unknown) and False (No) are not the same + if initial is not None: + initial = bool(initial) + if data is not None: + data = bool(data) + return initial != data + + class ChoiceField(Field): widget = Select default_error_messages = { @@ -739,6 +791,7 @@ class TypedChoiceField(ChoiceField): def validate(self, value): pass + class MultipleChoiceField(ChoiceField): hidden_widget = MultipleHiddenInput widget = SelectMultiple @@ -765,6 +818,18 @@ class MultipleChoiceField(ChoiceField): if not self.valid_value(val): raise ValidationError(self.error_messages['invalid_choice'] % {'value': val}) + 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 + + class TypedMultipleChoiceField(MultipleChoiceField): def __init__(self, *args, **kwargs): self.coerce = kwargs.pop('coerce', lambda val: val) @@ -899,6 +964,18 @@ class MultiValueField(Field): """ raise NotImplementedError('Subclasses must implement this method.') + def _has_changed(self, initial, data): + if initial is None: + initial = ['' for x in range(0, len(data))] + else: + if not isinstance(initial, list): + initial = self.widget.decompress(initial) + for field, initial, data in zip(self.fields, initial, data): + if field._has_changed(initial, data): + return True + return False + + class FilePathField(ChoiceField): def __init__(self, path, match=None, recursive=False, allow_files=True, allow_folders=False, required=True, widget=None, label=None, diff --git a/django/forms/forms.py b/django/forms/forms.py index 3299c2becc..f532391296 100644 --- a/django/forms/forms.py +++ b/django/forms/forms.py @@ -341,7 +341,13 @@ class BaseForm(object): hidden_widget = field.hidden_widget() initial_value = hidden_widget.value_from_datadict( self.data, self.files, initial_prefixed_name) - if field.widget._has_changed(initial_value, data_value): + if hasattr(field.widget, '_has_changed'): + warnings.warn("The _has_changed method on widgets is deprecated," + " define it at field level instead.", + PendingDeprecationWarning, stacklevel=2) + if field.widget._has_changed(initial_value, data_value): + self._changed_data.append(name) + elif field._has_changed(initial_value, data_value): self._changed_data.append(name) return self._changed_data changed_data = property(_get_changed_data) diff --git a/django/forms/models.py b/django/forms/models.py index 03a14dc9ff..837da74814 100644 --- a/django/forms/models.py +++ b/django/forms/models.py @@ -858,15 +858,12 @@ def inlineformset_factory(parent_model, model, form=ModelForm, # Fields ##################################################################### -class InlineForeignKeyHiddenInput(HiddenInput): - def _has_changed(self, initial, data): - return False - class InlineForeignKeyField(Field): """ A basic integer field that deals with validating the given value to a given parent instance in an inline. """ + widget = HiddenInput default_error_messages = { 'invalid_choice': _('The inline foreign key did not match the parent instance primary key.'), } @@ -881,7 +878,6 @@ class InlineForeignKeyField(Field): else: kwargs["initial"] = self.parent_instance.pk kwargs["required"] = False - kwargs["widget"] = InlineForeignKeyHiddenInput super(InlineForeignKeyField, self).__init__(*args, **kwargs) def clean(self, value): @@ -899,6 +895,9 @@ class InlineForeignKeyField(Field): raise ValidationError(self.error_messages['invalid_choice']) return self.parent_instance + def _has_changed(self, initial, data): + return False + class ModelChoiceIterator(object): def __init__(self, field): self.field = field diff --git a/django/forms/widgets.py b/django/forms/widgets.py index d6ea56f0c8..303844d44b 100644 --- a/django/forms/widgets.py +++ b/django/forms/widgets.py @@ -208,25 +208,6 @@ class Widget(six.with_metaclass(MediaDefiningClass)): """ return data.get(name, None) - def _has_changed(self, initial, data): - """ - Return True if data differs from initial. - """ - # 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): - return True - return False - def id_for_label(self, id_): """ Returns the HTML ID attribute of this Widget for use by a <label>, @@ -325,10 +306,6 @@ class FileInput(Input): "File widgets take data from FILES, not POST" return files.get(name, None) - def _has_changed(self, initial, data): - if data is None: - return False - return True FILE_INPUT_CONTRADICTION = object() @@ -426,17 +403,6 @@ class DateInput(TextInput): return value.strftime(self.format) return value - def _has_changed(self, initial, data): - # If our field has show_hidden_initial=True, initial will be a string - # formatted by HiddenInput using formats.localize_input, which is not - # necessarily the format used for this widget. Attempt to convert it. - try: - input_format = formats.get_format('DATE_INPUT_FORMATS')[0] - initial = datetime.datetime.strptime(initial, input_format).date() - except (TypeError, ValueError): - pass - return super(DateInput, self)._has_changed(self._format_value(initial), data) - class DateTimeInput(TextInput): def __init__(self, attrs=None, format=None): @@ -456,17 +422,6 @@ class DateTimeInput(TextInput): return value.strftime(self.format) return value - def _has_changed(self, initial, data): - # If our field has show_hidden_initial=True, initial will be a string - # formatted by HiddenInput using formats.localize_input, which is not - # necessarily the format used for this widget. Attempt to convert it. - try: - input_format = formats.get_format('DATETIME_INPUT_FORMATS')[0] - initial = datetime.datetime.strptime(initial, input_format) - except (TypeError, ValueError): - pass - return super(DateTimeInput, self)._has_changed(self._format_value(initial), data) - class TimeInput(TextInput): def __init__(self, attrs=None, format=None): @@ -485,17 +440,6 @@ class TimeInput(TextInput): return value.strftime(self.format) return value - def _has_changed(self, initial, data): - # If our field has show_hidden_initial=True, initial will be a string - # formatted by HiddenInput using formats.localize_input, which is not - # necessarily the format used for this widget. Attempt to convert it. - try: - input_format = formats.get_format('TIME_INPUT_FORMATS')[0] - initial = datetime.datetime.strptime(initial, input_format).time() - except (TypeError, ValueError): - pass - return super(TimeInput, self)._has_changed(self._format_value(initial), data) - # Defined at module level so that CheckboxInput is picklable (#17976) def boolean_check(v): @@ -530,13 +474,6 @@ class CheckboxInput(Widget): value = values.get(value.lower(), value) return bool(value) - def _has_changed(self, initial, data): - # Sometimes data or initial could be None or '' which should be the - # same thing as False. - if initial == 'False': - # show_hidden_initial may have transformed False to 'False' - initial = False - return bool(initial) != bool(data) class Select(Widget): allow_multiple_selected = False @@ -612,14 +549,6 @@ class NullBooleanSelect(Select): 'False': False, False: False}.get(value, None) - def _has_changed(self, initial, data): - # For a NullBooleanSelect, None (unknown) and False (No) - # are not the same - if initial is not None: - initial = bool(initial) - if data is not None: - data = bool(data) - return initial != data class SelectMultiple(Select): allow_multiple_selected = True @@ -639,16 +568,6 @@ class SelectMultiple(Select): return data.getlist(name) return data.get(name, None) - 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 @python_2_unicode_compatible class RadioInput(SubWidget): @@ -844,17 +763,6 @@ class MultiWidget(Widget): def value_from_datadict(self, data, files, name): return [widget.value_from_datadict(data, files, name + '_%s' % i) for i, widget in enumerate(self.widgets)] - def _has_changed(self, initial, data): - if initial is None: - initial = ['' for x in range(0, len(data))] - else: - if not isinstance(initial, list): - initial = self.decompress(initial) - for widget, initial, data in zip(self.widgets, initial, data): - if widget._has_changed(initial, data): - return True - return False - def format_output(self, rendered_widgets): """ Given a list of rendered widgets (as strings), returns a Unicode string |
