Fixed #27037 -- Prevented required attribute on ClearableFileInput when initial data exists.
This commit is contained in:
parent
f842d1011c
commit
fab46ce6f5
|
@ -134,18 +134,7 @@ class BoundField(object):
|
||||||
the form is not bound or the data otherwise.
|
the form is not bound or the data otherwise.
|
||||||
"""
|
"""
|
||||||
if not self.form.is_bound:
|
if not self.form.is_bound:
|
||||||
data = self.form.initial.get(self.name, self.field.initial)
|
data = self.initial
|
||||||
if callable(data):
|
|
||||||
if self._initial_value is not UNSET:
|
|
||||||
data = self._initial_value
|
|
||||||
else:
|
|
||||||
data = data()
|
|
||||||
# If this is an auto-generated default date, nix the
|
|
||||||
# microseconds for standardized handling. See #22502.
|
|
||||||
if (isinstance(data, (datetime.datetime, datetime.time)) and
|
|
||||||
not self.field.widget.supports_microseconds):
|
|
||||||
data = data.replace(microsecond=0)
|
|
||||||
self._initial_value = data
|
|
||||||
else:
|
else:
|
||||||
data = self.field.bound_data(
|
data = self.field.bound_data(
|
||||||
self.data, self.form.initial.get(self.name, self.field.initial)
|
self.data, self.form.initial.get(self.name, self.field.initial)
|
||||||
|
@ -231,11 +220,27 @@ class BoundField(object):
|
||||||
id_ = widget.attrs.get('id') or self.auto_id
|
id_ = widget.attrs.get('id') or self.auto_id
|
||||||
return widget.id_for_label(id_)
|
return widget.id_for_label(id_)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def initial(self):
|
||||||
|
data = self.form.initial.get(self.name, self.field.initial)
|
||||||
|
if callable(data):
|
||||||
|
if self._initial_value is not UNSET:
|
||||||
|
data = self._initial_value
|
||||||
|
else:
|
||||||
|
data = data()
|
||||||
|
# If this is an auto-generated default date, nix the
|
||||||
|
# microseconds for standardized handling. See #22502.
|
||||||
|
if (isinstance(data, (datetime.datetime, datetime.time)) and
|
||||||
|
not self.field.widget.supports_microseconds):
|
||||||
|
data = data.replace(microsecond=0)
|
||||||
|
self._initial_value = data
|
||||||
|
return data
|
||||||
|
|
||||||
def build_widget_attrs(self, attrs, widget=None):
|
def build_widget_attrs(self, attrs, widget=None):
|
||||||
if not widget:
|
if not widget:
|
||||||
widget = self.field.widget
|
widget = self.field.widget
|
||||||
attrs = dict(attrs) # Copy attrs to avoid modifying the argument.
|
attrs = dict(attrs) # Copy attrs to avoid modifying the argument.
|
||||||
if not widget.is_hidden and self.field.required and self.form.use_required_attribute:
|
if widget.use_required_attribute(self.initial) and self.field.required and self.form.use_required_attribute:
|
||||||
attrs['required'] = True
|
attrs['required'] = True
|
||||||
if self.field.disabled:
|
if self.field.disabled:
|
||||||
attrs['disabled'] = True
|
attrs['disabled'] = True
|
||||||
|
|
|
@ -248,6 +248,9 @@ class Widget(six.with_metaclass(RenameWidgetMethods)):
|
||||||
"""
|
"""
|
||||||
return id_
|
return id_
|
||||||
|
|
||||||
|
def use_required_attribute(self, initial):
|
||||||
|
return not self.is_hidden
|
||||||
|
|
||||||
|
|
||||||
class Input(Widget):
|
class Input(Widget):
|
||||||
"""
|
"""
|
||||||
|
@ -429,6 +432,9 @@ class ClearableFileInput(FileInput):
|
||||||
return False
|
return False
|
||||||
return upload
|
return upload
|
||||||
|
|
||||||
|
def use_required_attribute(self, initial):
|
||||||
|
return super(ClearableFileInput, self).use_required_attribute(initial) and not initial
|
||||||
|
|
||||||
|
|
||||||
class Textarea(Widget):
|
class Textarea(Widget):
|
||||||
def __init__(self, attrs=None):
|
def __init__(self, attrs=None):
|
||||||
|
@ -795,12 +801,10 @@ class CheckboxSelectMultiple(RendererMixin, SelectMultiple):
|
||||||
renderer = CheckboxFieldRenderer
|
renderer = CheckboxFieldRenderer
|
||||||
_empty_value = []
|
_empty_value = []
|
||||||
|
|
||||||
def build_attrs(self, extra_attrs=None, **kwargs):
|
def use_required_attribute(self, initial):
|
||||||
attrs = super(CheckboxSelectMultiple, self).build_attrs(extra_attrs, **kwargs)
|
# Don't use the 'required' attribute because browser validation would
|
||||||
# Remove the 'required' attribute because browser validation would
|
|
||||||
# require all checkboxes to be checked instead of at least one.
|
# require all checkboxes to be checked instead of at least one.
|
||||||
attrs.pop('required', None)
|
return False
|
||||||
return attrs
|
|
||||||
|
|
||||||
|
|
||||||
class MultiWidget(Widget):
|
class MultiWidget(Widget):
|
||||||
|
|
|
@ -45,3 +45,6 @@ Bugfixes
|
||||||
|
|
||||||
* Fixed crash of ``django.views.static.serve()`` with ``show_indexes`` enabled
|
* Fixed crash of ``django.views.static.serve()`` with ``show_indexes`` enabled
|
||||||
(:ticket:`26973`).
|
(:ticket:`26973`).
|
||||||
|
|
||||||
|
* Fixed ``ClearableFileInput`` to avoid the ``required`` HTML attribute when
|
||||||
|
initial data exists (:ticket:`27037`).
|
||||||
|
|
|
@ -2360,6 +2360,15 @@ Password: <input type="password" name="password" required />
|
||||||
'<tr><th>File1:</th><td><input type="file" name="file1" required /></td></tr>',
|
'<tr><th>File1:</th><td><input type="file" name="file1" required /></td></tr>',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# A required file field with initial data should not contain the
|
||||||
|
# required HTML attribute. The file input is left blank by the user to
|
||||||
|
# keep the existing, initial value.
|
||||||
|
f = FileForm(initial={'file1': 'resume.txt'}, auto_id=False)
|
||||||
|
self.assertHTMLEqual(
|
||||||
|
f.as_table(),
|
||||||
|
'<tr><th>File1:</th><td><input type="file" name="file1" /></td></tr>',
|
||||||
|
)
|
||||||
|
|
||||||
def test_basic_processing_in_view(self):
|
def test_basic_processing_in_view(self):
|
||||||
class UserRegistration(Form):
|
class UserRegistration(Form):
|
||||||
username = CharField(max_length=10)
|
username = CharField(max_length=10)
|
||||||
|
|
|
@ -112,3 +112,11 @@ class CheckboxSelectMultipleTest(WidgetTest):
|
||||||
</ul>
|
</ul>
|
||||||
"""
|
"""
|
||||||
self.check_html(widget, 'letters', ['a', 'c'], html=html)
|
self.check_html(widget, 'letters', ['a', 'c'], html=html)
|
||||||
|
|
||||||
|
def test_use_required_attribute(self):
|
||||||
|
widget = self.widget(choices=self.beatles)
|
||||||
|
# Always False because browser validation would require all checkboxes
|
||||||
|
# to be checked instead of at least one.
|
||||||
|
self.assertIs(widget.use_required_attribute(None), False)
|
||||||
|
self.assertIs(widget.use_required_attribute([]), False)
|
||||||
|
self.assertIs(widget.use_required_attribute(['J', 'P']), False)
|
||||||
|
|
|
@ -143,3 +143,9 @@ class ClearableFileInputTest(WidgetTest):
|
||||||
|
|
||||||
html = self.widget.render('myfile', NoURLFieldFile())
|
html = self.widget.render('myfile', NoURLFieldFile())
|
||||||
self.assertHTMLEqual(html, '<input name="myfile" type="file" />')
|
self.assertHTMLEqual(html, '<input name="myfile" type="file" />')
|
||||||
|
|
||||||
|
def test_use_required_attribute(self):
|
||||||
|
# False when initial data exists. The file input is left blank by the
|
||||||
|
# user to keep the existing, initial value.
|
||||||
|
self.assertIs(self.widget.use_required_attribute(None), True)
|
||||||
|
self.assertIs(self.widget.use_required_attribute('resume.txt'), False)
|
||||||
|
|
|
@ -8,3 +8,10 @@ class HiddenInputTest(WidgetTest):
|
||||||
|
|
||||||
def test_render(self):
|
def test_render(self):
|
||||||
self.check_html(self.widget, 'email', '', html='<input type="hidden" name="email" />')
|
self.check_html(self.widget, 'email', '', html='<input type="hidden" name="email" />')
|
||||||
|
|
||||||
|
def test_use_required_attribute(self):
|
||||||
|
# Always False to avoid browser validation on inputs hidden from the
|
||||||
|
# user.
|
||||||
|
self.assertIs(self.widget.use_required_attribute(None), False)
|
||||||
|
self.assertIs(self.widget.use_required_attribute(''), False)
|
||||||
|
self.assertIs(self.widget.use_required_attribute('foo'), False)
|
||||||
|
|
|
@ -76,3 +76,9 @@ class TextInputTest(WidgetTest):
|
||||||
def test_attrs_safestring(self):
|
def test_attrs_safestring(self):
|
||||||
widget = TextInput(attrs={'onBlur': mark_safe("function('foo')")})
|
widget = TextInput(attrs={'onBlur': mark_safe("function('foo')")})
|
||||||
self.check_html(widget, 'email', '', html='<input onBlur="function(\'foo\')" type="text" name="email" />')
|
self.check_html(widget, 'email', '', html='<input onBlur="function(\'foo\')" type="text" name="email" />')
|
||||||
|
|
||||||
|
def test_use_required_attribute(self):
|
||||||
|
# Text inputs can safely trigger the browser validation.
|
||||||
|
self.assertIs(self.widget.use_required_attribute(None), True)
|
||||||
|
self.assertIs(self.widget.use_required_attribute(''), True)
|
||||||
|
self.assertIs(self.widget.use_required_attribute('resume.txt'), True)
|
||||||
|
|
Loading…
Reference in New Issue