Moved has_changed logic from widget to form field
Refs #16612. Thanks Aymeric Augustin for the suggestion.
This commit is contained in:
parent
ce27fb198d
commit
ebb504db69
|
@ -213,17 +213,6 @@ class ManyToManyRawIdWidget(ForeignKeyRawIdWidget):
|
||||||
if value:
|
if value:
|
||||||
return value.split(',')
|
return value.split(',')
|
||||||
|
|
||||||
def _has_changed(self, initial, data):
|
|
||||||
if initial is None:
|
|
||||||
initial = []
|
|
||||||
if data is None:
|
|
||||||
data = []
|
|
||||||
if len(initial) != len(data):
|
|
||||||
return True
|
|
||||||
for pk1, pk2 in zip(initial, data):
|
|
||||||
if force_text(pk1) != force_text(pk2):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
class RelatedFieldWidgetWrapper(forms.Widget):
|
class RelatedFieldWidgetWrapper(forms.Widget):
|
||||||
"""
|
"""
|
||||||
|
@ -279,9 +268,6 @@ class RelatedFieldWidgetWrapper(forms.Widget):
|
||||||
def value_from_datadict(self, data, files, name):
|
def value_from_datadict(self, data, files, name):
|
||||||
return self.widget.value_from_datadict(data, files, name)
|
return self.widget.value_from_datadict(data, files, name)
|
||||||
|
|
||||||
def _has_changed(self, initial, data):
|
|
||||||
return self.widget._has_changed(initial, data)
|
|
||||||
|
|
||||||
def id_for_label(self, id_):
|
def id_for_label(self, id_):
|
||||||
return self.widget.id_for_label(id_)
|
return self.widget.id_for_label(id_)
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ from django.utils import six
|
||||||
from django.utils import translation
|
from django.utils import translation
|
||||||
|
|
||||||
from django.contrib.gis.gdal import OGRException
|
from django.contrib.gis.gdal import OGRException
|
||||||
from django.contrib.gis.geos import GEOSGeometry, GEOSException, fromstr
|
from django.contrib.gis.geos import GEOSGeometry, GEOSException
|
||||||
|
|
||||||
# Creating a template context that contains Django settings
|
# Creating a template context that contains Django settings
|
||||||
# values needed by admin map templates.
|
# values needed by admin map templates.
|
||||||
|
@ -117,25 +117,3 @@ class OpenLayersWidget(Textarea):
|
||||||
raise TypeError
|
raise TypeError
|
||||||
map_options[js_name] = value
|
map_options[js_name] = value
|
||||||
return map_options
|
return map_options
|
||||||
|
|
||||||
def _has_changed(self, initial, data):
|
|
||||||
""" Compare geographic value of data with its initial value. """
|
|
||||||
|
|
||||||
# Ensure we are dealing with a geographic object
|
|
||||||
if isinstance(initial, six.string_types):
|
|
||||||
try:
|
|
||||||
initial = GEOSGeometry(initial)
|
|
||||||
except (GEOSException, ValueError):
|
|
||||||
initial = None
|
|
||||||
|
|
||||||
# Only do a geographic comparison if both values are available
|
|
||||||
if initial and data:
|
|
||||||
data = fromstr(data)
|
|
||||||
data.transform(initial.srid)
|
|
||||||
# If the initial value was not added by the browser, the geometry
|
|
||||||
# provided may be slightly different, the first time it is saved.
|
|
||||||
# The comparison is done with a very low tolerance.
|
|
||||||
return not initial.equals_exact(data, tolerance=0.000001)
|
|
||||||
else:
|
|
||||||
# Check for change of state of existence
|
|
||||||
return bool(initial) != bool(data)
|
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.utils import six
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
# While this couples the geographic forms to the GEOS library,
|
# While this couples the geographic forms to the GEOS library,
|
||||||
# it decouples from database (by not importing SpatialBackend).
|
# it decouples from database (by not importing SpatialBackend).
|
||||||
from django.contrib.gis.geos import GEOSException, GEOSGeometry
|
from django.contrib.gis.geos import GEOSException, GEOSGeometry, fromstr
|
||||||
|
|
||||||
|
|
||||||
class GeometryField(forms.Field):
|
class GeometryField(forms.Field):
|
||||||
"""
|
"""
|
||||||
|
@ -73,3 +75,25 @@ class GeometryField(forms.Field):
|
||||||
raise forms.ValidationError(self.error_messages['transform_error'])
|
raise forms.ValidationError(self.error_messages['transform_error'])
|
||||||
|
|
||||||
return geom
|
return geom
|
||||||
|
|
||||||
|
def _has_changed(self, initial, data):
|
||||||
|
""" Compare geographic value of data with its initial value. """
|
||||||
|
|
||||||
|
# Ensure we are dealing with a geographic object
|
||||||
|
if isinstance(initial, six.string_types):
|
||||||
|
try:
|
||||||
|
initial = GEOSGeometry(initial)
|
||||||
|
except (GEOSException, ValueError):
|
||||||
|
initial = None
|
||||||
|
|
||||||
|
# Only do a geographic comparison if both values are available
|
||||||
|
if initial and data:
|
||||||
|
data = fromstr(data)
|
||||||
|
data.transform(initial.srid)
|
||||||
|
# If the initial value was not added by the browser, the geometry
|
||||||
|
# provided may be slightly different, the first time it is saved.
|
||||||
|
# The comparison is done with a very low tolerance.
|
||||||
|
return not initial.equals_exact(data, tolerance=0.000001)
|
||||||
|
else:
|
||||||
|
# Check for change of state of existence
|
||||||
|
return bool(initial) != bool(data)
|
||||||
|
|
|
@ -38,7 +38,7 @@ class GeoAdminTest(TestCase):
|
||||||
""" Check that changes are accurately noticed by OpenLayersWidget. """
|
""" Check that changes are accurately noticed by OpenLayersWidget. """
|
||||||
geoadmin = admin.site._registry[City]
|
geoadmin = admin.site._registry[City]
|
||||||
form = geoadmin.get_changelist_form(None)()
|
form = geoadmin.get_changelist_form(None)()
|
||||||
has_changed = form.fields['point'].widget._has_changed
|
has_changed = form.fields['point']._has_changed
|
||||||
|
|
||||||
initial = Point(13.4197458572965953, 52.5194108501149799, srid=4326)
|
initial = Point(13.4197458572965953, 52.5194108501149799, srid=4326)
|
||||||
data_same = "SRID=3857;POINT(1493879.2754093995 6894592.019687599)"
|
data_same = "SRID=3857;POINT(1493879.2754093995 6894592.019687599)"
|
||||||
|
|
|
@ -135,11 +135,3 @@ class SelectDateWidget(Widget):
|
||||||
s = Select(choices=choices)
|
s = Select(choices=choices)
|
||||||
select_html = s.render(field % name, val, local_attrs)
|
select_html = s.render(field % name, val, local_attrs)
|
||||||
return select_html
|
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)
|
|
||||||
|
|
|
@ -175,6 +175,25 @@ class Field(object):
|
||||||
"""
|
"""
|
||||||
return {}
|
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):
|
def __deepcopy__(self, memo):
|
||||||
result = copy.copy(self)
|
result = copy.copy(self)
|
||||||
memo[id(self)] = result
|
memo[id(self)] = result
|
||||||
|
@ -348,6 +367,13 @@ class BaseTemporalField(Field):
|
||||||
def strptime(self, value, format):
|
def strptime(self, value, format):
|
||||||
raise NotImplementedError('Subclasses must define this method.')
|
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):
|
class DateField(BaseTemporalField):
|
||||||
widget = DateInput
|
widget = DateInput
|
||||||
input_formats = formats.get_format_lazy('DATE_INPUT_FORMATS')
|
input_formats = formats.get_format_lazy('DATE_INPUT_FORMATS')
|
||||||
|
@ -371,6 +397,7 @@ class DateField(BaseTemporalField):
|
||||||
def strptime(self, value, format):
|
def strptime(self, value, format):
|
||||||
return datetime.datetime.strptime(value, format).date()
|
return datetime.datetime.strptime(value, format).date()
|
||||||
|
|
||||||
|
|
||||||
class TimeField(BaseTemporalField):
|
class TimeField(BaseTemporalField):
|
||||||
widget = TimeInput
|
widget = TimeInput
|
||||||
input_formats = formats.get_format_lazy('TIME_INPUT_FORMATS')
|
input_formats = formats.get_format_lazy('TIME_INPUT_FORMATS')
|
||||||
|
@ -529,6 +556,12 @@ class FileField(Field):
|
||||||
return initial
|
return initial
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
def _has_changed(self, initial, data):
|
||||||
|
if data is None:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
class ImageField(FileField):
|
class ImageField(FileField):
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
'invalid_image': _("Upload a valid image. The file you uploaded was either not an image or a corrupted image."),
|
'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)
|
value = urlunsplit(url_fields)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
class BooleanField(Field):
|
class BooleanField(Field):
|
||||||
widget = CheckboxInput
|
widget = CheckboxInput
|
||||||
|
|
||||||
|
@ -636,6 +670,15 @@ class BooleanField(Field):
|
||||||
raise ValidationError(self.error_messages['required'])
|
raise ValidationError(self.error_messages['required'])
|
||||||
return value
|
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):
|
class NullBooleanField(BooleanField):
|
||||||
"""
|
"""
|
||||||
A field whose valid values are None, True and False. Invalid values are
|
A field whose valid values are None, True and False. Invalid values are
|
||||||
|
@ -660,6 +703,15 @@ class NullBooleanField(BooleanField):
|
||||||
def validate(self, value):
|
def validate(self, value):
|
||||||
pass
|
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):
|
class ChoiceField(Field):
|
||||||
widget = Select
|
widget = Select
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
|
@ -739,6 +791,7 @@ class TypedChoiceField(ChoiceField):
|
||||||
def validate(self, value):
|
def validate(self, value):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class MultipleChoiceField(ChoiceField):
|
class MultipleChoiceField(ChoiceField):
|
||||||
hidden_widget = MultipleHiddenInput
|
hidden_widget = MultipleHiddenInput
|
||||||
widget = SelectMultiple
|
widget = SelectMultiple
|
||||||
|
@ -765,6 +818,18 @@ class MultipleChoiceField(ChoiceField):
|
||||||
if not self.valid_value(val):
|
if not self.valid_value(val):
|
||||||
raise ValidationError(self.error_messages['invalid_choice'] % {'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):
|
class TypedMultipleChoiceField(MultipleChoiceField):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.coerce = kwargs.pop('coerce', lambda val: val)
|
self.coerce = kwargs.pop('coerce', lambda val: val)
|
||||||
|
@ -899,6 +964,18 @@ class MultiValueField(Field):
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError('Subclasses must implement this method.')
|
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):
|
class FilePathField(ChoiceField):
|
||||||
def __init__(self, path, match=None, recursive=False, allow_files=True,
|
def __init__(self, path, match=None, recursive=False, allow_files=True,
|
||||||
allow_folders=False, required=True, widget=None, label=None,
|
allow_folders=False, required=True, widget=None, label=None,
|
||||||
|
|
|
@ -341,8 +341,14 @@ class BaseForm(object):
|
||||||
hidden_widget = field.hidden_widget()
|
hidden_widget = field.hidden_widget()
|
||||||
initial_value = hidden_widget.value_from_datadict(
|
initial_value = hidden_widget.value_from_datadict(
|
||||||
self.data, self.files, initial_prefixed_name)
|
self.data, self.files, initial_prefixed_name)
|
||||||
|
if hasattr(field.widget, '_has_changed'):
|
||||||
|
warnings.warn("The _has_changed method on widgets is deprecated,"
|
||||||
|
" define it at field level instead.",
|
||||||
|
PendingDeprecationWarning, stacklevel=2)
|
||||||
if field.widget._has_changed(initial_value, data_value):
|
if field.widget._has_changed(initial_value, data_value):
|
||||||
self._changed_data.append(name)
|
self._changed_data.append(name)
|
||||||
|
elif field._has_changed(initial_value, data_value):
|
||||||
|
self._changed_data.append(name)
|
||||||
return self._changed_data
|
return self._changed_data
|
||||||
changed_data = property(_get_changed_data)
|
changed_data = property(_get_changed_data)
|
||||||
|
|
||||||
|
|
|
@ -858,15 +858,12 @@ def inlineformset_factory(parent_model, model, form=ModelForm,
|
||||||
|
|
||||||
# Fields #####################################################################
|
# Fields #####################################################################
|
||||||
|
|
||||||
class InlineForeignKeyHiddenInput(HiddenInput):
|
|
||||||
def _has_changed(self, initial, data):
|
|
||||||
return False
|
|
||||||
|
|
||||||
class InlineForeignKeyField(Field):
|
class InlineForeignKeyField(Field):
|
||||||
"""
|
"""
|
||||||
A basic integer field that deals with validating the given value to a
|
A basic integer field that deals with validating the given value to a
|
||||||
given parent instance in an inline.
|
given parent instance in an inline.
|
||||||
"""
|
"""
|
||||||
|
widget = HiddenInput
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
'invalid_choice': _('The inline foreign key did not match the parent instance primary key.'),
|
'invalid_choice': _('The inline foreign key did not match the parent instance primary key.'),
|
||||||
}
|
}
|
||||||
|
@ -881,7 +878,6 @@ class InlineForeignKeyField(Field):
|
||||||
else:
|
else:
|
||||||
kwargs["initial"] = self.parent_instance.pk
|
kwargs["initial"] = self.parent_instance.pk
|
||||||
kwargs["required"] = False
|
kwargs["required"] = False
|
||||||
kwargs["widget"] = InlineForeignKeyHiddenInput
|
|
||||||
super(InlineForeignKeyField, self).__init__(*args, **kwargs)
|
super(InlineForeignKeyField, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
def clean(self, value):
|
def clean(self, value):
|
||||||
|
@ -899,6 +895,9 @@ class InlineForeignKeyField(Field):
|
||||||
raise ValidationError(self.error_messages['invalid_choice'])
|
raise ValidationError(self.error_messages['invalid_choice'])
|
||||||
return self.parent_instance
|
return self.parent_instance
|
||||||
|
|
||||||
|
def _has_changed(self, initial, data):
|
||||||
|
return False
|
||||||
|
|
||||||
class ModelChoiceIterator(object):
|
class ModelChoiceIterator(object):
|
||||||
def __init__(self, field):
|
def __init__(self, field):
|
||||||
self.field = field
|
self.field = field
|
||||||
|
|
|
@ -208,25 +208,6 @@ class Widget(six.with_metaclass(MediaDefiningClass)):
|
||||||
"""
|
"""
|
||||||
return data.get(name, None)
|
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_):
|
def id_for_label(self, id_):
|
||||||
"""
|
"""
|
||||||
Returns the HTML ID attribute of this Widget for use by a <label>,
|
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"
|
"File widgets take data from FILES, not POST"
|
||||||
return files.get(name, None)
|
return files.get(name, None)
|
||||||
|
|
||||||
def _has_changed(self, initial, data):
|
|
||||||
if data is None:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
FILE_INPUT_CONTRADICTION = object()
|
FILE_INPUT_CONTRADICTION = object()
|
||||||
|
|
||||||
|
@ -426,17 +403,6 @@ class DateInput(TextInput):
|
||||||
return value.strftime(self.format)
|
return value.strftime(self.format)
|
||||||
return value
|
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):
|
class DateTimeInput(TextInput):
|
||||||
def __init__(self, attrs=None, format=None):
|
def __init__(self, attrs=None, format=None):
|
||||||
|
@ -456,17 +422,6 @@ class DateTimeInput(TextInput):
|
||||||
return value.strftime(self.format)
|
return value.strftime(self.format)
|
||||||
return value
|
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):
|
class TimeInput(TextInput):
|
||||||
def __init__(self, attrs=None, format=None):
|
def __init__(self, attrs=None, format=None):
|
||||||
|
@ -485,17 +440,6 @@ class TimeInput(TextInput):
|
||||||
return value.strftime(self.format)
|
return value.strftime(self.format)
|
||||||
return value
|
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)
|
# Defined at module level so that CheckboxInput is picklable (#17976)
|
||||||
def boolean_check(v):
|
def boolean_check(v):
|
||||||
|
@ -530,13 +474,6 @@ class CheckboxInput(Widget):
|
||||||
value = values.get(value.lower(), value)
|
value = values.get(value.lower(), value)
|
||||||
return bool(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):
|
class Select(Widget):
|
||||||
allow_multiple_selected = False
|
allow_multiple_selected = False
|
||||||
|
@ -612,14 +549,6 @@ class NullBooleanSelect(Select):
|
||||||
'False': False,
|
'False': False,
|
||||||
False: False}.get(value, None)
|
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):
|
class SelectMultiple(Select):
|
||||||
allow_multiple_selected = True
|
allow_multiple_selected = True
|
||||||
|
@ -639,16 +568,6 @@ class SelectMultiple(Select):
|
||||||
return data.getlist(name)
|
return data.getlist(name)
|
||||||
return data.get(name, None)
|
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
|
@python_2_unicode_compatible
|
||||||
class RadioInput(SubWidget):
|
class RadioInput(SubWidget):
|
||||||
|
@ -844,17 +763,6 @@ class MultiWidget(Widget):
|
||||||
def value_from_datadict(self, data, files, name):
|
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)]
|
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):
|
def format_output(self, rendered_widgets):
|
||||||
"""
|
"""
|
||||||
Given a list of rendered widgets (as strings), returns a Unicode string
|
Given a list of rendered widgets (as strings), returns a Unicode string
|
||||||
|
|
|
@ -67,3 +67,9 @@ If you're relying on this feature, you should add
|
||||||
``'django.middleware.common.BrokenLinkEmailsMiddleware'`` to your
|
``'django.middleware.common.BrokenLinkEmailsMiddleware'`` to your
|
||||||
:setting:`MIDDLEWARE_CLASSES` setting and remove ``SEND_BROKEN_LINK_EMAILS``
|
:setting:`MIDDLEWARE_CLASSES` setting and remove ``SEND_BROKEN_LINK_EMAILS``
|
||||||
from your settings.
|
from your settings.
|
||||||
|
|
||||||
|
``_has_changed`` method on widgets
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
If you defined your own form widgets and defined the ``_has_changed`` method
|
||||||
|
on a widget, you should now define this method on the form field itself.
|
||||||
|
|
|
@ -425,13 +425,6 @@ class ManyToManyRawIdWidgetTest(DjangoTestCase):
|
||||||
'<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)
|
'<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)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(w._has_changed(None, None), False)
|
|
||||||
self.assertEqual(w._has_changed([], None), False)
|
|
||||||
self.assertEqual(w._has_changed(None, ['1']), True)
|
|
||||||
self.assertEqual(w._has_changed([1, 2], ['1', '2']), False)
|
|
||||||
self.assertEqual(w._has_changed([1, 2], ['1']), True)
|
|
||||||
self.assertEqual(w._has_changed([1, 2], ['1', '3']), True)
|
|
||||||
|
|
||||||
def test_m2m_related_model_not_in_admin(self):
|
def test_m2m_related_model_not_in_admin(self):
|
||||||
# M2M relationship with model not registered with admin site. Raw ID
|
# M2M relationship with model not registered with admin site. Raw ID
|
||||||
# widget should have no magnifying glass link. See #16542
|
# widget should have no magnifying glass link. See #16542
|
||||||
|
|
|
@ -428,6 +428,23 @@ class FormsExtraTestCase(TestCase, AssertFormErrorsMixin):
|
||||||
# If insufficient data is provided, None is substituted
|
# If insufficient data is provided, None is substituted
|
||||||
self.assertFormErrors(['This field is required.'], f.clean, ['some text',['JP']])
|
self.assertFormErrors(['This field is required.'], f.clean, ['some text',['JP']])
|
||||||
|
|
||||||
|
# test with no initial data
|
||||||
|
self.assertTrue(f._has_changed(None, ['some text', ['J','P'], ['2007-04-25','6:24:00']]))
|
||||||
|
|
||||||
|
# test when the data is the same as initial
|
||||||
|
self.assertFalse(f._has_changed('some text,JP,2007-04-25 06:24:00',
|
||||||
|
['some text', ['J','P'], ['2007-04-25','6:24:00']]))
|
||||||
|
|
||||||
|
# test when the first widget's data has changed
|
||||||
|
self.assertTrue(f._has_changed('some text,JP,2007-04-25 06:24:00',
|
||||||
|
['other text', ['J','P'], ['2007-04-25','6:24:00']]))
|
||||||
|
|
||||||
|
# test when the last widget's data has changed. this ensures that it is not
|
||||||
|
# short circuiting while testing the widgets.
|
||||||
|
self.assertTrue(f._has_changed('some text,JP,2007-04-25 06:24:00',
|
||||||
|
['some text', ['J','P'], ['2009-04-25','11:44:00']]))
|
||||||
|
|
||||||
|
|
||||||
class ComplexFieldForm(Form):
|
class ComplexFieldForm(Form):
|
||||||
field1 = ComplexField(widget=w)
|
field1 = ComplexField(widget=w)
|
||||||
|
|
||||||
|
@ -725,8 +742,8 @@ class FormsExtraL10NTestCase(TestCase):
|
||||||
|
|
||||||
def test_l10n_date_changed(self):
|
def test_l10n_date_changed(self):
|
||||||
"""
|
"""
|
||||||
Ensure that SelectDateWidget._has_changed() works correctly with a
|
Ensure that DateField._has_changed() with SelectDateWidget works
|
||||||
localized date format.
|
correctly with a localized date format.
|
||||||
Refs #17165.
|
Refs #17165.
|
||||||
"""
|
"""
|
||||||
# With Field.show_hidden_initial=False -----------------------
|
# With Field.show_hidden_initial=False -----------------------
|
||||||
|
|
|
@ -35,6 +35,7 @@ from decimal import Decimal
|
||||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||||
from django.forms import *
|
from django.forms import *
|
||||||
from django.test import SimpleTestCase
|
from django.test import SimpleTestCase
|
||||||
|
from django.utils import formats
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
from django.utils._os import upath
|
from django.utils._os import upath
|
||||||
|
|
||||||
|
@ -362,6 +363,13 @@ class FieldsTests(SimpleTestCase):
|
||||||
f = DateField()
|
f = DateField()
|
||||||
self.assertRaisesMessage(ValidationError, "'Enter a valid date.'", f.clean, 'a\x00b')
|
self.assertRaisesMessage(ValidationError, "'Enter a valid date.'", f.clean, 'a\x00b')
|
||||||
|
|
||||||
|
def test_datefield_changed(self):
|
||||||
|
format = '%d/%m/%Y'
|
||||||
|
f = DateField(input_formats=[format])
|
||||||
|
d = datetime.date(2007, 9, 17)
|
||||||
|
self.assertFalse(f._has_changed(d, '17/09/2007'))
|
||||||
|
self.assertFalse(f._has_changed(d.strftime(format), '17/09/2007'))
|
||||||
|
|
||||||
# TimeField ###################################################################
|
# TimeField ###################################################################
|
||||||
|
|
||||||
def test_timefield_1(self):
|
def test_timefield_1(self):
|
||||||
|
@ -388,6 +396,18 @@ class FieldsTests(SimpleTestCase):
|
||||||
self.assertEqual(datetime.time(14, 25, 59), f.clean(' 14:25:59 '))
|
self.assertEqual(datetime.time(14, 25, 59), f.clean(' 14:25:59 '))
|
||||||
self.assertRaisesMessage(ValidationError, "'Enter a valid time.'", f.clean, ' ')
|
self.assertRaisesMessage(ValidationError, "'Enter a valid time.'", f.clean, ' ')
|
||||||
|
|
||||||
|
def test_timefield_changed(self):
|
||||||
|
t1 = datetime.time(12, 51, 34, 482548)
|
||||||
|
t2 = datetime.time(12, 51)
|
||||||
|
format = '%H:%M'
|
||||||
|
f = TimeField(input_formats=[format])
|
||||||
|
self.assertTrue(f._has_changed(t1, '12:51'))
|
||||||
|
self.assertFalse(f._has_changed(t2, '12:51'))
|
||||||
|
|
||||||
|
format = '%I:%M %p'
|
||||||
|
f = TimeField(input_formats=[format])
|
||||||
|
self.assertFalse(f._has_changed(t2.strftime(format), '12:51 PM'))
|
||||||
|
|
||||||
# DateTimeField ###############################################################
|
# DateTimeField ###############################################################
|
||||||
|
|
||||||
def test_datetimefield_1(self):
|
def test_datetimefield_1(self):
|
||||||
|
@ -446,6 +466,15 @@ class FieldsTests(SimpleTestCase):
|
||||||
def test_datetimefield_5(self):
|
def test_datetimefield_5(self):
|
||||||
f = DateTimeField(input_formats=['%Y.%m.%d %H:%M:%S.%f'])
|
f = DateTimeField(input_formats=['%Y.%m.%d %H:%M:%S.%f'])
|
||||||
self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 45, 200), f.clean('2006.10.25 14:30:45.0002'))
|
self.assertEqual(datetime.datetime(2006, 10, 25, 14, 30, 45, 200), f.clean('2006.10.25 14:30:45.0002'))
|
||||||
|
|
||||||
|
def test_datetimefield_changed(self):
|
||||||
|
format = '%Y %m %d %I:%M %p'
|
||||||
|
f = DateTimeField(input_formats=[format])
|
||||||
|
d = datetime.datetime(2006, 9, 17, 14, 30, 0)
|
||||||
|
self.assertFalse(f._has_changed(d, '2006 09 17 2:30 PM'))
|
||||||
|
# Initial value may be a string from a hidden input
|
||||||
|
self.assertFalse(f._has_changed(d.strftime(format), '2006 09 17 2:30 PM'))
|
||||||
|
|
||||||
# RegexField ##################################################################
|
# RegexField ##################################################################
|
||||||
|
|
||||||
def test_regexfield_1(self):
|
def test_regexfield_1(self):
|
||||||
|
@ -566,6 +595,29 @@ class FieldsTests(SimpleTestCase):
|
||||||
self.assertEqual(SimpleUploadedFile,
|
self.assertEqual(SimpleUploadedFile,
|
||||||
type(f.clean(SimpleUploadedFile('name', b''))))
|
type(f.clean(SimpleUploadedFile('name', b''))))
|
||||||
|
|
||||||
|
def test_filefield_changed(self):
|
||||||
|
'''
|
||||||
|
Test for the behavior of _has_changed for FileField. The value of data will
|
||||||
|
more than likely come from request.FILES. The value of initial data will
|
||||||
|
likely be a filename stored in the database. Since its value is of no use to
|
||||||
|
a FileField it is ignored.
|
||||||
|
'''
|
||||||
|
f = FileField()
|
||||||
|
|
||||||
|
# No file was uploaded and no initial data.
|
||||||
|
self.assertFalse(f._has_changed('', None))
|
||||||
|
|
||||||
|
# A file was uploaded and no initial data.
|
||||||
|
self.assertTrue(f._has_changed('', {'filename': 'resume.txt', 'content': 'My resume'}))
|
||||||
|
|
||||||
|
# A file was not uploaded, but there is initial data
|
||||||
|
self.assertFalse(f._has_changed('resume.txt', None))
|
||||||
|
|
||||||
|
# A file was uploaded and there is initial data (file identity is not dealt
|
||||||
|
# with here)
|
||||||
|
self.assertTrue(f._has_changed('resume.txt', {'filename': 'resume.txt', 'content': 'My resume'}))
|
||||||
|
|
||||||
|
|
||||||
# URLField ##################################################################
|
# URLField ##################################################################
|
||||||
|
|
||||||
def test_urlfield_1(self):
|
def test_urlfield_1(self):
|
||||||
|
@ -709,6 +761,18 @@ class FieldsTests(SimpleTestCase):
|
||||||
def test_boolean_picklable(self):
|
def test_boolean_picklable(self):
|
||||||
self.assertIsInstance(pickle.loads(pickle.dumps(BooleanField())), BooleanField)
|
self.assertIsInstance(pickle.loads(pickle.dumps(BooleanField())), BooleanField)
|
||||||
|
|
||||||
|
def test_booleanfield_changed(self):
|
||||||
|
f = BooleanField()
|
||||||
|
self.assertFalse(f._has_changed(None, None))
|
||||||
|
self.assertFalse(f._has_changed(None, ''))
|
||||||
|
self.assertFalse(f._has_changed('', None))
|
||||||
|
self.assertFalse(f._has_changed('', ''))
|
||||||
|
self.assertTrue(f._has_changed(False, 'on'))
|
||||||
|
self.assertFalse(f._has_changed(True, 'on'))
|
||||||
|
self.assertTrue(f._has_changed(True, ''))
|
||||||
|
# Initial value may have mutated to a string due to show_hidden_initial (#19537)
|
||||||
|
self.assertTrue(f._has_changed('False', 'on'))
|
||||||
|
|
||||||
# ChoiceField #################################################################
|
# ChoiceField #################################################################
|
||||||
|
|
||||||
def test_choicefield_1(self):
|
def test_choicefield_1(self):
|
||||||
|
@ -825,6 +889,16 @@ class FieldsTests(SimpleTestCase):
|
||||||
self.assertEqual(False, f.cleaned_data['nullbool1'])
|
self.assertEqual(False, f.cleaned_data['nullbool1'])
|
||||||
self.assertEqual(None, f.cleaned_data['nullbool2'])
|
self.assertEqual(None, f.cleaned_data['nullbool2'])
|
||||||
|
|
||||||
|
def test_nullbooleanfield_changed(self):
|
||||||
|
f = NullBooleanField()
|
||||||
|
self.assertTrue(f._has_changed(False, None))
|
||||||
|
self.assertTrue(f._has_changed(None, False))
|
||||||
|
self.assertFalse(f._has_changed(None, None))
|
||||||
|
self.assertFalse(f._has_changed(False, False))
|
||||||
|
self.assertTrue(f._has_changed(True, False))
|
||||||
|
self.assertTrue(f._has_changed(True, None))
|
||||||
|
self.assertTrue(f._has_changed(True, False))
|
||||||
|
|
||||||
# MultipleChoiceField #########################################################
|
# MultipleChoiceField #########################################################
|
||||||
|
|
||||||
def test_multiplechoicefield_1(self):
|
def test_multiplechoicefield_1(self):
|
||||||
|
@ -866,6 +940,16 @@ class FieldsTests(SimpleTestCase):
|
||||||
self.assertRaisesMessage(ValidationError, "'Select a valid choice. 6 is not one of the available choices.'", f.clean, ['6'])
|
self.assertRaisesMessage(ValidationError, "'Select a valid choice. 6 is not one of the available choices.'", f.clean, ['6'])
|
||||||
self.assertRaisesMessage(ValidationError, "'Select a valid choice. 6 is not one of the available choices.'", f.clean, ['1','6'])
|
self.assertRaisesMessage(ValidationError, "'Select a valid choice. 6 is not one of the available choices.'", f.clean, ['1','6'])
|
||||||
|
|
||||||
|
def test_multiplechoicefield_changed(self):
|
||||||
|
f = MultipleChoiceField(choices=[('1', 'One'), ('2', 'Two'), ('3', 'Three')])
|
||||||
|
self.assertFalse(f._has_changed(None, None))
|
||||||
|
self.assertFalse(f._has_changed([], None))
|
||||||
|
self.assertTrue(f._has_changed(None, ['1']))
|
||||||
|
self.assertFalse(f._has_changed([1, 2], ['1', '2']))
|
||||||
|
self.assertFalse(f._has_changed([2, 1], ['1', '2']))
|
||||||
|
self.assertTrue(f._has_changed([1, 2], ['1']))
|
||||||
|
self.assertTrue(f._has_changed([1, 2], ['1', '3']))
|
||||||
|
|
||||||
# TypedMultipleChoiceField ############################################################
|
# TypedMultipleChoiceField ############################################################
|
||||||
# TypedMultipleChoiceField is just like MultipleChoiceField, except that coerced types
|
# TypedMultipleChoiceField is just like MultipleChoiceField, except that coerced types
|
||||||
# will be returned:
|
# will be returned:
|
||||||
|
@ -1048,3 +1132,9 @@ class FieldsTests(SimpleTestCase):
|
||||||
self.assertRaisesMessage(ValidationError, "'Enter a valid time.'", f.clean, ['2006-01-10', ''])
|
self.assertRaisesMessage(ValidationError, "'Enter a valid time.'", f.clean, ['2006-01-10', ''])
|
||||||
self.assertRaisesMessage(ValidationError, "'Enter a valid time.'", f.clean, ['2006-01-10'])
|
self.assertRaisesMessage(ValidationError, "'Enter a valid time.'", f.clean, ['2006-01-10'])
|
||||||
self.assertRaisesMessage(ValidationError, "'Enter a valid date.'", f.clean, ['', '07:30'])
|
self.assertRaisesMessage(ValidationError, "'Enter a valid date.'", f.clean, ['', '07:30'])
|
||||||
|
|
||||||
|
def test_splitdatetimefield_changed(self):
|
||||||
|
f = SplitDateTimeField(input_date_formats=['%d/%m/%Y'])
|
||||||
|
self.assertTrue(f._has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), ['2008-05-06', '12:40:00']))
|
||||||
|
self.assertFalse(f._has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), ['06/05/2008', '12:40']))
|
||||||
|
self.assertTrue(f._has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), ['06/05/2008', '12:41']))
|
||||||
|
|
|
@ -148,25 +148,6 @@ class FormsWidgetTestCase(TestCase):
|
||||||
|
|
||||||
self.assertHTMLEqual(w.render('email', 'ŠĐĆŽćžšđ', attrs={'class': 'fun'}), '<input type="file" class="fun" name="email" />')
|
self.assertHTMLEqual(w.render('email', 'ŠĐĆŽćžšđ', attrs={'class': 'fun'}), '<input type="file" class="fun" name="email" />')
|
||||||
|
|
||||||
# Test for the behavior of _has_changed for FileInput. The value of data will
|
|
||||||
# more than likely come from request.FILES. The value of initial data will
|
|
||||||
# likely be a filename stored in the database. Since its value is of no use to
|
|
||||||
# a FileInput it is ignored.
|
|
||||||
w = FileInput()
|
|
||||||
|
|
||||||
# No file was uploaded and no initial data.
|
|
||||||
self.assertFalse(w._has_changed('', None))
|
|
||||||
|
|
||||||
# A file was uploaded and no initial data.
|
|
||||||
self.assertTrue(w._has_changed('', {'filename': 'resume.txt', 'content': 'My resume'}))
|
|
||||||
|
|
||||||
# A file was not uploaded, but there is initial data
|
|
||||||
self.assertFalse(w._has_changed('resume.txt', None))
|
|
||||||
|
|
||||||
# A file was uploaded and there is initial data (file identity is not dealt
|
|
||||||
# with here)
|
|
||||||
self.assertTrue(w._has_changed('resume.txt', {'filename': 'resume.txt', 'content': 'My resume'}))
|
|
||||||
|
|
||||||
def test_textarea(self):
|
def test_textarea(self):
|
||||||
w = Textarea()
|
w = Textarea()
|
||||||
self.assertHTMLEqual(w.render('msg', ''), '<textarea rows="10" cols="40" name="msg"></textarea>')
|
self.assertHTMLEqual(w.render('msg', ''), '<textarea rows="10" cols="40" name="msg"></textarea>')
|
||||||
|
@ -233,16 +214,6 @@ class FormsWidgetTestCase(TestCase):
|
||||||
self.assertIsInstance(value, bool)
|
self.assertIsInstance(value, bool)
|
||||||
self.assertTrue(value)
|
self.assertTrue(value)
|
||||||
|
|
||||||
self.assertFalse(w._has_changed(None, None))
|
|
||||||
self.assertFalse(w._has_changed(None, ''))
|
|
||||||
self.assertFalse(w._has_changed('', None))
|
|
||||||
self.assertFalse(w._has_changed('', ''))
|
|
||||||
self.assertTrue(w._has_changed(False, 'on'))
|
|
||||||
self.assertFalse(w._has_changed(True, 'on'))
|
|
||||||
self.assertTrue(w._has_changed(True, ''))
|
|
||||||
# Initial value may have mutated to a string due to show_hidden_initial (#19537)
|
|
||||||
self.assertTrue(w._has_changed('False', 'on'))
|
|
||||||
|
|
||||||
def test_select(self):
|
def test_select(self):
|
||||||
w = Select()
|
w = Select()
|
||||||
self.assertHTMLEqual(w.render('beatle', 'J', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<select name="beatle">
|
self.assertHTMLEqual(w.render('beatle', 'J', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), """<select name="beatle">
|
||||||
|
@ -415,13 +386,6 @@ class FormsWidgetTestCase(TestCase):
|
||||||
<option value="2">Yes</option>
|
<option value="2">Yes</option>
|
||||||
<option value="3" selected="selected">No</option>
|
<option value="3" selected="selected">No</option>
|
||||||
</select>""")
|
</select>""")
|
||||||
self.assertTrue(w._has_changed(False, None))
|
|
||||||
self.assertTrue(w._has_changed(None, False))
|
|
||||||
self.assertFalse(w._has_changed(None, None))
|
|
||||||
self.assertFalse(w._has_changed(False, False))
|
|
||||||
self.assertTrue(w._has_changed(True, False))
|
|
||||||
self.assertTrue(w._has_changed(True, None))
|
|
||||||
self.assertTrue(w._has_changed(True, False))
|
|
||||||
|
|
||||||
def test_selectmultiple(self):
|
def test_selectmultiple(self):
|
||||||
w = SelectMultiple()
|
w = SelectMultiple()
|
||||||
|
@ -535,14 +499,6 @@ class FormsWidgetTestCase(TestCase):
|
||||||
# Unicode choices are correctly rendered as HTML
|
# Unicode choices are correctly rendered as HTML
|
||||||
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>')
|
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>')
|
||||||
|
|
||||||
# Test the usage of _has_changed
|
|
||||||
self.assertFalse(w._has_changed(None, None))
|
|
||||||
self.assertFalse(w._has_changed([], None))
|
|
||||||
self.assertTrue(w._has_changed(None, ['1']))
|
|
||||||
self.assertFalse(w._has_changed([1, 2], ['1', '2']))
|
|
||||||
self.assertTrue(w._has_changed([1, 2], ['1']))
|
|
||||||
self.assertTrue(w._has_changed([1, 2], ['1', '3']))
|
|
||||||
|
|
||||||
# Choices can be nested one level in order to create HTML optgroups:
|
# Choices can be nested one level in order to create HTML optgroups:
|
||||||
w.choices = (('outer1', 'Outer 1'), ('Group "1"', (('inner1', 'Inner 1'), ('inner2', 'Inner 2'))))
|
w.choices = (('outer1', 'Outer 1'), ('Group "1"', (('inner1', 'Inner 1'), ('inner2', 'Inner 2'))))
|
||||||
self.assertHTMLEqual(w.render('nestchoice', None), """<select multiple="multiple" name="nestchoice">
|
self.assertHTMLEqual(w.render('nestchoice', None), """<select multiple="multiple" name="nestchoice">
|
||||||
|
@ -844,15 +800,6 @@ beatle J R Ringo False""")
|
||||||
<li><label><input type="checkbox" name="escape" value="good" /> you > me</label></li>
|
<li><label><input type="checkbox" name="escape" value="good" /> you > me</label></li>
|
||||||
</ul>""")
|
</ul>""")
|
||||||
|
|
||||||
# Test the usage of _has_changed
|
|
||||||
self.assertFalse(w._has_changed(None, None))
|
|
||||||
self.assertFalse(w._has_changed([], None))
|
|
||||||
self.assertTrue(w._has_changed(None, ['1']))
|
|
||||||
self.assertFalse(w._has_changed([1, 2], ['1', '2']))
|
|
||||||
self.assertTrue(w._has_changed([1, 2], ['1']))
|
|
||||||
self.assertTrue(w._has_changed([1, 2], ['1', '3']))
|
|
||||||
self.assertFalse(w._has_changed([2, 1], ['1', '2']))
|
|
||||||
|
|
||||||
# Unicode choices are correctly rendered as HTML
|
# Unicode choices are correctly rendered as HTML
|
||||||
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>')
|
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>')
|
||||||
|
|
||||||
|
@ -886,21 +833,6 @@ beatle J R Ringo False""")
|
||||||
w = MyMultiWidget(widgets=(TextInput(attrs={'class': 'big'}), TextInput(attrs={'class': 'small'})), attrs={'id': 'bar'})
|
w = MyMultiWidget(widgets=(TextInput(attrs={'class': 'big'}), TextInput(attrs={'class': 'small'})), attrs={'id': 'bar'})
|
||||||
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" />')
|
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" />')
|
||||||
|
|
||||||
w = MyMultiWidget(widgets=(TextInput(), TextInput()))
|
|
||||||
|
|
||||||
# test with no initial data
|
|
||||||
self.assertTrue(w._has_changed(None, ['john', 'lennon']))
|
|
||||||
|
|
||||||
# test when the data is the same as initial
|
|
||||||
self.assertFalse(w._has_changed('john__lennon', ['john', 'lennon']))
|
|
||||||
|
|
||||||
# test when the first widget's data has changed
|
|
||||||
self.assertTrue(w._has_changed('john__lennon', ['alfred', 'lennon']))
|
|
||||||
|
|
||||||
# test when the last widget's data has changed. this ensures that it is not
|
|
||||||
# short circuiting while testing the widgets.
|
|
||||||
self.assertTrue(w._has_changed('john__lennon', ['john', 'denver']))
|
|
||||||
|
|
||||||
def test_splitdatetime(self):
|
def test_splitdatetime(self):
|
||||||
w = SplitDateTimeWidget()
|
w = SplitDateTimeWidget()
|
||||||
self.assertHTMLEqual(w.render('date', ''), '<input type="text" name="date_0" /><input type="text" name="date_1" />')
|
self.assertHTMLEqual(w.render('date', ''), '<input type="text" name="date_0" /><input type="text" name="date_1" />')
|
||||||
|
@ -916,10 +848,6 @@ beatle J R Ringo False""")
|
||||||
w = SplitDateTimeWidget(date_format='%d/%m/%Y', time_format='%H:%M')
|
w = SplitDateTimeWidget(date_format='%d/%m/%Y', time_format='%H:%M')
|
||||||
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" />')
|
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" />')
|
||||||
|
|
||||||
self.assertTrue(w._has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), ['2008-05-06', '12:40:00']))
|
|
||||||
self.assertFalse(w._has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), ['06/05/2008', '12:40']))
|
|
||||||
self.assertTrue(w._has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), ['06/05/2008', '12:41']))
|
|
||||||
|
|
||||||
def test_datetimeinput(self):
|
def test_datetimeinput(self):
|
||||||
w = DateTimeInput()
|
w = DateTimeInput()
|
||||||
self.assertHTMLEqual(w.render('date', None), '<input type="text" name="date" />')
|
self.assertHTMLEqual(w.render('date', None), '<input type="text" name="date" />')
|
||||||
|
@ -934,13 +862,6 @@ beatle J R Ringo False""")
|
||||||
# Use 'format' to change the way a value is displayed.
|
# Use 'format' to change the way a value is displayed.
|
||||||
w = DateTimeInput(format='%d/%m/%Y %H:%M', attrs={'type': 'datetime'})
|
w = DateTimeInput(format='%d/%m/%Y %H:%M', attrs={'type': 'datetime'})
|
||||||
self.assertHTMLEqual(w.render('date', d), '<input type="datetime" name="date" value="17/09/2007 12:51" />')
|
self.assertHTMLEqual(w.render('date', d), '<input type="datetime" name="date" value="17/09/2007 12:51" />')
|
||||||
self.assertFalse(w._has_changed(d, '17/09/2007 12:51'))
|
|
||||||
|
|
||||||
# Make sure a custom format works with _has_changed. The hidden input will use
|
|
||||||
data = datetime.datetime(2010, 3, 6, 12, 0, 0)
|
|
||||||
custom_format = '%d.%m.%Y %H:%M'
|
|
||||||
w = DateTimeInput(format=custom_format)
|
|
||||||
self.assertFalse(w._has_changed(formats.localize_input(data), data.strftime(custom_format)))
|
|
||||||
|
|
||||||
def test_dateinput(self):
|
def test_dateinput(self):
|
||||||
w = DateInput()
|
w = DateInput()
|
||||||
|
@ -957,13 +878,6 @@ beatle J R Ringo False""")
|
||||||
# Use 'format' to change the way a value is displayed.
|
# Use 'format' to change the way a value is displayed.
|
||||||
w = DateInput(format='%d/%m/%Y', attrs={'type': 'date'})
|
w = DateInput(format='%d/%m/%Y', attrs={'type': 'date'})
|
||||||
self.assertHTMLEqual(w.render('date', d), '<input type="date" name="date" value="17/09/2007" />')
|
self.assertHTMLEqual(w.render('date', d), '<input type="date" name="date" value="17/09/2007" />')
|
||||||
self.assertFalse(w._has_changed(d, '17/09/2007'))
|
|
||||||
|
|
||||||
# Make sure a custom format works with _has_changed. The hidden input will use
|
|
||||||
data = datetime.date(2010, 3, 6)
|
|
||||||
custom_format = '%d.%m.%Y'
|
|
||||||
w = DateInput(format=custom_format)
|
|
||||||
self.assertFalse(w._has_changed(formats.localize_input(data), data.strftime(custom_format)))
|
|
||||||
|
|
||||||
def test_timeinput(self):
|
def test_timeinput(self):
|
||||||
w = TimeInput()
|
w = TimeInput()
|
||||||
|
@ -982,13 +896,6 @@ beatle J R Ringo False""")
|
||||||
# Use 'format' to change the way a value is displayed.
|
# Use 'format' to change the way a value is displayed.
|
||||||
w = TimeInput(format='%H:%M', attrs={'type': 'time'})
|
w = TimeInput(format='%H:%M', attrs={'type': 'time'})
|
||||||
self.assertHTMLEqual(w.render('time', t), '<input type="time" name="time" value="12:51" />')
|
self.assertHTMLEqual(w.render('time', t), '<input type="time" name="time" value="12:51" />')
|
||||||
self.assertFalse(w._has_changed(t, '12:51'))
|
|
||||||
|
|
||||||
# Make sure a custom format works with _has_changed. The hidden input will use
|
|
||||||
data = datetime.time(13, 0)
|
|
||||||
custom_format = '%I:%M %p'
|
|
||||||
w = TimeInput(format=custom_format)
|
|
||||||
self.assertFalse(w._has_changed(formats.localize_input(data), data.strftime(custom_format)))
|
|
||||||
|
|
||||||
def test_splithiddendatetime(self):
|
def test_splithiddendatetime(self):
|
||||||
from django.forms.widgets import SplitHiddenDateTimeWidget
|
from django.forms.widgets import SplitHiddenDateTimeWidget
|
||||||
|
@ -1016,10 +923,6 @@ class FormsI18NWidgetsTestCase(TestCase):
|
||||||
deactivate()
|
deactivate()
|
||||||
super(FormsI18NWidgetsTestCase, self).tearDown()
|
super(FormsI18NWidgetsTestCase, self).tearDown()
|
||||||
|
|
||||||
def test_splitdatetime(self):
|
|
||||||
w = SplitDateTimeWidget(date_format='%d/%m/%Y', time_format='%H:%M')
|
|
||||||
self.assertTrue(w._has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), ['06.05.2008', '12:41']))
|
|
||||||
|
|
||||||
def test_datetimeinput(self):
|
def test_datetimeinput(self):
|
||||||
w = DateTimeInput()
|
w = DateTimeInput()
|
||||||
d = datetime.datetime(2007, 9, 17, 12, 51, 34, 482548)
|
d = datetime.datetime(2007, 9, 17, 12, 51, 34, 482548)
|
||||||
|
|
Loading…
Reference in New Issue