From 4c76ffc2d6c77c850b4bef8d9acc197d11c47937 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 22 Jan 2022 17:21:57 +0100 Subject: [PATCH] Fixed #29490 -- Added support for object-based Media CSS and JS paths. --- django/forms/widgets.py | 8 +- docs/releases/4.1.txt | 5 + docs/topics/forms/media.txt | 27 ++++- tests/forms_tests/tests/test_media.py | 159 ++++++++++++++++++++++++++ 4 files changed, 196 insertions(+), 3 deletions(-) diff --git a/django/forms/widgets.py b/django/forms/widgets.py index 8c5122ad1d..208464b60a 100644 --- a/django/forms/widgets.py +++ b/django/forms/widgets.py @@ -101,7 +101,9 @@ class Media: def render_js(self): return [ - format_html('', self.absolute_path(path)) + path.__html__() + if hasattr(path, "__html__") + else format_html('', self.absolute_path(path)) for path in self._js ] @@ -111,7 +113,9 @@ class Media: media = sorted(self._css) return chain.from_iterable( [ - format_html( + path.__html__() + if hasattr(path, "__html__") + else format_html( '', self.absolute_path(path), medium, diff --git a/docs/releases/4.1.txt b/docs/releases/4.1.txt index 02d51ed2a5..4b62cf09cf 100644 --- a/docs/releases/4.1.txt +++ b/docs/releases/4.1.txt @@ -192,6 +192,11 @@ Forms * The new ``edit_only`` argument for :func:`.modelformset_factory` and :func:`.inlineformset_factory` allows preventing new objects creation. +* The ``js`` and ``css`` class attributes of :doc:`Media ` + now allow using hashable objects, not only path strings, as long as those + objects implement the ``__html__()`` method (typically when decorated with + the :func:`~django.utils.html.html_safe` decorator). + Generic Views ~~~~~~~~~~~~~ diff --git a/docs/topics/forms/media.txt b/docs/topics/forms/media.txt index 6ca7c66fde..7e5a04e3d9 100644 --- a/docs/topics/forms/media.txt +++ b/docs/topics/forms/media.txt @@ -206,7 +206,10 @@ return values for dynamic ``media`` properties. Paths in asset definitions ========================== -Paths used to specify assets can be either relative or absolute. If a +Paths as strings +---------------- + +String paths used to specify assets can be either relative or absolute. If a path starts with ``/``, ``http://`` or ``https://``, it will be interpreted as an absolute path, and left as-is. All other paths will be prepended with the value of the appropriate prefix. If the @@ -254,6 +257,28 @@ Or if :mod:`~django.contrib.staticfiles` is configured using the +Paths as objects +---------------- + +.. versionadded:: 4.1 + +Asset paths may also be given as hashable objects implementing an +``__html__()`` method. The ``__html__()`` method is typically added using the +:func:`~django.utils.html.html_safe` decorator. The object is responsible for +outputting the complete HTML ``' % ( + ' integrity="{}"' if self.integrity else "{}" + ) + return format_html(template, self.absolute_path(path), self.integrity) + + +@override_settings( + STATIC_URL="http://media.example.com/static/", +) +class FormsMediaObjectTestCase(SimpleTestCase): + """Media handling when media are objects instead of raw strings.""" + + def test_construction(self): + m = Media( + css={"all": (CSS("path/to/css1", "all"), CSS("/path/to/css2", "all"))}, + js=( + JS("/path/to/js1"), + JS("http://media.other.com/path/to/js2"), + JS( + "https://secure.other.com/path/to/js3", + integrity="9d947b87fdeb25030d56d01f7aa75800", + ), + ), + ) + self.assertEqual( + str(m), + '\n' + '\n' + '\n' + '\n' + '', + ) + self.assertEqual( + repr(m), + "Media(css={'all': ['path/to/css1', '/path/to/css2']}, " + "js=['/path/to/js1', 'http://media.other.com/path/to/js2', " + "'https://secure.other.com/path/to/js3'])", + ) + + def test_simplest_class(self): + @html_safe + class SimpleJS: + """The simplest possible asset class.""" + + def __str__(self): + return '\n' + '\n' + '\n' + '", + ) + + def test_media_deduplication(self): + # The deduplication doesn't only happen at the point of merging two or + # more media objects. + media = Media( + css={ + "all": ( + CSS("/path/to/css1", "all"), + CSS("/path/to/css1", "all"), + "/path/to/css1", + ) + }, + js=(JS("/path/to/js1"), JS("/path/to/js1"), "/path/to/js1"), + ) + self.assertEqual( + str(media), + '\n' + '', + )