Skip to content

Commit 8a6c020

Browse files
mgalignianafelixxm
authored andcommitted
Fixed #34488 -- Made ClearableFileInput preserve "Clear" checked attribute when form is invalid.
1 parent fb535e0 commit 8a6c020

File tree

4 files changed

+76
-32
lines changed

4 files changed

+76
-32
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{% if widget.is_initial %}<p class="file-upload">{{ widget.initial_text }}: <a href="{{ widget.value.url }}">{{ widget.value }}</a>{% if not widget.required %}
22
<span class="clearable-file-input">
3-
<input type="checkbox" name="{{ widget.checkbox_name }}" id="{{ widget.checkbox_id }}"{% if widget.attrs.disabled %} disabled{% endif %}>
3+
<input type="checkbox" name="{{ widget.checkbox_name }}" id="{{ widget.checkbox_id }}"{% include "django/forms/widgets/attrs.html" %}>
44
<label for="{{ widget.checkbox_id }}">{{ widget.clear_checkbox_label }}</label></span>{% endif %}<br>
55
{{ widget.input_text }}:{% endif %}
66
<input type="{{ widget.type }}" name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %}>{% if widget.is_initial %}</p>{% endif %}

django/forms/widgets.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,7 @@ class ClearableFileInput(FileInput):
433433
initial_text = _("Currently")
434434
input_text = _("Change")
435435
template_name = "django/forms/widgets/clearable_file_input.html"
436+
checked = False
436437

437438
def clear_checkbox_name(self, name):
438439
"""
@@ -475,10 +476,12 @@ def get_context(self, name, value, attrs):
475476
}
476477
)
477478
context["widget"]["attrs"].setdefault("disabled", False)
479+
context["widget"]["attrs"]["checked"] = self.checked
478480
return context
479481

480482
def value_from_datadict(self, data, files, name):
481483
upload = super().value_from_datadict(data, files, name)
484+
self.checked = self.clear_checkbox_name(name) in data
482485
if not self.is_required and CheckboxInput().value_from_datadict(
483486
data, files, self.clear_checkbox_name(name)
484487
):

tests/admin_widgets/tests.py

Lines changed: 68 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1772,39 +1772,59 @@ def test_ForeignKey_using_to_field(self):
17721772

17731773
@skipUnless(Image, "Pillow not installed")
17741774
class ImageFieldWidgetsSeleniumTests(AdminWidgetSeleniumTestCase):
1775-
def test_clearablefileinput_widget(self):
1775+
name_input_id = "id_name"
1776+
photo_input_id = "id_photo"
1777+
tests_files_folder = "%s/files" % os.getcwd()
1778+
clear_checkbox_id = "photo-clear_id"
1779+
1780+
def _submit_and_wait(self):
1781+
from selenium.webdriver.common.by import By
1782+
1783+
with self.wait_page_loaded():
1784+
self.selenium.find_element(
1785+
By.CSS_SELECTOR, "input[value='Save and continue editing']"
1786+
).click()
1787+
1788+
def _run_image_upload_path(self):
17761789
from selenium.webdriver.common.by import By
17771790

17781791
self.admin_login(username="super", password="secret", login_url="/")
17791792
self.selenium.get(
17801793
self.live_server_url + reverse("admin:admin_widgets_student_add"),
17811794
)
1795+
# Add a student.
1796+
name_input = self.selenium.find_element(By.ID, self.name_input_id)
1797+
name_input.send_keys("Joe Doe")
1798+
photo_input = self.selenium.find_element(By.ID, self.photo_input_id)
1799+
photo_input.send_keys(f"{self.tests_files_folder}/test.png")
1800+
self._submit_and_wait()
1801+
student = Student.objects.last()
1802+
self.assertEqual(student.name, "Joe Doe")
1803+
self.assertRegex(student.photo.name, r"^photos\/(test|test_.+).png")
17821804

1783-
photo_input_id = "id_photo"
1784-
save_and_edit_button_css_selector = "input[value='Save and continue editing']"
1785-
tests_files_folder = "%s/files" % os.getcwd()
1786-
clear_checkbox_id = "photo-clear_id"
1787-
1788-
def _submit_and_wait():
1789-
with self.wait_page_loaded():
1790-
self.selenium.find_element(
1791-
By.CSS_SELECTOR, save_and_edit_button_css_selector
1792-
).click()
1805+
def test_clearablefileinput_widget(self):
1806+
from selenium.webdriver.common.by import By
17931807

1794-
# Add a student.
1795-
title_input = self.selenium.find_element(By.ID, "id_name")
1796-
title_input.send_keys("Joe Doe")
1797-
photo_input = self.selenium.find_element(By.ID, photo_input_id)
1798-
photo_input.send_keys(f"{tests_files_folder}/test.png")
1799-
_submit_and_wait()
1808+
self._run_image_upload_path()
1809+
self.selenium.find_element(By.ID, self.clear_checkbox_id).click()
1810+
self._submit_and_wait()
18001811
student = Student.objects.last()
18011812
self.assertEqual(student.name, "Joe Doe")
1802-
self.assertEqual(student.photo.name, "photos/test.png")
1813+
self.assertEqual(student.photo.name, "")
1814+
# "Currently" with "Clear" checkbox and "Change" are not shown.
1815+
photo_field_row = self.selenium.find_element(By.CSS_SELECTOR, ".field-photo")
1816+
self.assertNotIn("Currently", photo_field_row.text)
1817+
self.assertNotIn("Change", photo_field_row.text)
1818+
1819+
def test_clearablefileinput_widget_invalid_file(self):
1820+
from selenium.webdriver.common.by import By
1821+
1822+
self._run_image_upload_path()
18031823
# Uploading non-image files is not supported by Safari with Selenium,
18041824
# so upload a broken one instead.
1805-
photo_input = self.selenium.find_element(By.ID, photo_input_id)
1806-
photo_input.send_keys(f"{tests_files_folder}/brokenimg.png")
1807-
_submit_and_wait()
1825+
photo_input = self.selenium.find_element(By.ID, self.photo_input_id)
1826+
photo_input.send_keys(f"{self.tests_files_folder}/brokenimg.png")
1827+
self._submit_and_wait()
18081828
self.assertEqual(
18091829
self.selenium.find_element(By.CSS_SELECTOR, ".errorlist li").text,
18101830
(
@@ -1813,12 +1833,30 @@ def _submit_and_wait():
18131833
),
18141834
)
18151835
# "Currently" with "Clear" checkbox and "Change" still shown.
1816-
cover_field_row = self.selenium.find_element(By.CSS_SELECTOR, ".field-photo")
1817-
self.assertIn("Currently", cover_field_row.text)
1818-
self.assertIn("Change", cover_field_row.text)
1819-
# "Clear" box works.
1820-
self.selenium.find_element(By.ID, clear_checkbox_id).click()
1821-
_submit_and_wait()
1822-
student.refresh_from_db()
1823-
self.assertEqual(student.name, "Joe Doe")
1824-
self.assertEqual(student.photo.name, "")
1836+
photo_field_row = self.selenium.find_element(By.CSS_SELECTOR, ".field-photo")
1837+
self.assertIn("Currently", photo_field_row.text)
1838+
self.assertIn("Change", photo_field_row.text)
1839+
1840+
def test_clearablefileinput_widget_preserve_clear_checkbox(self):
1841+
from selenium.webdriver.common.by import By
1842+
1843+
self._run_image_upload_path()
1844+
# "Clear" is not checked by default.
1845+
self.assertIs(
1846+
self.selenium.find_element(By.ID, self.clear_checkbox_id).is_selected(),
1847+
False,
1848+
)
1849+
# "Clear" was checked, but a validation error is raised.
1850+
name_input = self.selenium.find_element(By.ID, self.name_input_id)
1851+
name_input.clear()
1852+
self.selenium.find_element(By.ID, self.clear_checkbox_id).click()
1853+
self._submit_and_wait()
1854+
self.assertEqual(
1855+
self.selenium.find_element(By.CSS_SELECTOR, ".errorlist li").text,
1856+
"This field is required.",
1857+
)
1858+
# "Clear" persists checked.
1859+
self.assertIs(
1860+
self.selenium.find_element(By.ID, self.clear_checkbox_id).is_selected(),
1861+
True,
1862+
)

tests/forms_tests/widget_tests/test_clearablefileinput.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ def __str__(self):
1717

1818

1919
class ClearableFileInputTest(WidgetTest):
20-
widget = ClearableFileInput()
20+
def setUp(self):
21+
self.widget = ClearableFileInput()
2122

2223
def test_clear_input_renders(self):
2324
"""
@@ -148,6 +149,7 @@ def test_clear_input_checked_returns_false(self):
148149
name="myfile",
149150
)
150151
self.assertIs(value, False)
152+
self.assertIs(self.widget.checked, True)
151153

152154
def test_clear_input_checked_returns_false_only_if_not_required(self):
153155
"""
@@ -164,6 +166,7 @@ def test_clear_input_checked_returns_false_only_if_not_required(self):
164166
name="myfile",
165167
)
166168
self.assertEqual(value, field)
169+
self.assertIs(widget.checked, True)
167170

168171
def test_html_does_not_mask_exceptions(self):
169172
"""

0 commit comments

Comments
 (0)