diff --git a/django/forms/widgets.py b/django/forms/widgets.py index 7ea3c7bcbf..1b1c1439cb 100644 --- a/django/forms/widgets.py +++ b/django/forms/widgets.py @@ -146,8 +146,14 @@ class Media: def __add__(self, other): combined = Media() - combined._css_lists = self._css_lists + other._css_lists - combined._js_lists = self._js_lists + other._js_lists + combined._css_lists = self._css_lists[:] + combined._js_lists = self._js_lists[:] + for item in other._css_lists: + if item and item not in self._css_lists: + combined._css_lists.append(item) + for item in other._js_lists: + if item and item not in self._js_lists: + combined._js_lists.append(item) return combined diff --git a/tests/forms_tests/tests/test_media.py b/tests/forms_tests/tests/test_media.py index 7d8f978ecd..26f81a4602 100644 --- a/tests/forms_tests/tests/test_media.py +++ b/tests/forms_tests/tests/test_media.py @@ -592,3 +592,57 @@ class FormsMediaTestCase(SimpleTestCase): # Media ordering does not matter. merged = widget1 + widget4 self.assertEqual(merged._css, {'screen': ['c.css'], 'all': ['d.css', 'e.css']}) + + def test_add_js_deduplication(self): + widget1 = Media(js=['a', 'b', 'c']) + widget2 = Media(js=['a', 'b']) + widget3 = Media(js=['a', 'c', 'b']) + merged = widget1 + widget1 + self.assertEqual(merged._js_lists, [['a', 'b', 'c']]) + self.assertEqual(merged._js, ['a', 'b', 'c']) + merged = widget1 + widget2 + self.assertEqual(merged._js_lists, [['a', 'b', 'c'], ['a', 'b']]) + self.assertEqual(merged._js, ['a', 'b', 'c']) + # Lists with items in a different order are preserved when added. + merged = widget1 + widget3 + self.assertEqual(merged._js_lists, [['a', 'b', 'c'], ['a', 'c', 'b']]) + msg = ( + "Detected duplicate Media files in an opposite order: " + "['a', 'b', 'c'], ['a', 'c', 'b']" + ) + with self.assertWarnsMessage(RuntimeWarning, msg): + merged._js + + def test_add_css_deduplication(self): + widget1 = Media(css={'screen': ['a.css'], 'all': ['b.css']}) + widget2 = Media(css={'screen': ['c.css']}) + widget3 = Media(css={'screen': ['a.css'], 'all': ['b.css', 'c.css']}) + widget4 = Media(css={'screen': ['a.css'], 'all': ['c.css', 'b.css']}) + merged = widget1 + widget1 + self.assertEqual(merged._css_lists, [{'screen': ['a.css'], 'all': ['b.css']}]) + self.assertEqual(merged._css, {'screen': ['a.css'], 'all': ['b.css']}) + merged = widget1 + widget2 + self.assertEqual(merged._css_lists, [ + {'screen': ['a.css'], 'all': ['b.css']}, + {'screen': ['c.css']}, + ]) + self.assertEqual(merged._css, {'screen': ['a.css', 'c.css'], 'all': ['b.css']}) + merged = widget3 + widget4 + # Ordering within lists is preserved. + self.assertEqual(merged._css_lists, [ + {'screen': ['a.css'], 'all': ['b.css', 'c.css']}, + {'screen': ['a.css'], 'all': ['c.css', 'b.css']} + ]) + msg = ( + "Detected duplicate Media files in an opposite order: " + "['b.css', 'c.css'], ['c.css', 'b.css']" + ) + with self.assertWarnsMessage(RuntimeWarning, msg): + merged._css + + def test_add_empty(self): + media = Media(css={'screen': ['a.css']}, js=['a']) + empty_media = Media() + merged = media + empty_media + self.assertEqual(merged._css_lists, [{'screen': ['a.css']}]) + self.assertEqual(merged._js_lists, [['a']])