From b9b6451c880aa386a17a336d1fc30cf07447b4a3 Mon Sep 17 00:00:00 2001 From: Thibaud Colas <thibaudcolas@gmail.com> Date: Tue, 19 Dec 2023 10:45:24 +0000 Subject: [PATCH 1/4] Copy template to new draft DEP doc --- draft/0000-rejuvenate-form-media.rst | 104 +++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 draft/0000-rejuvenate-form-media.rst diff --git a/draft/0000-rejuvenate-form-media.rst b/draft/0000-rejuvenate-form-media.rst new file mode 100644 index 00000000..8e857349 --- /dev/null +++ b/draft/0000-rejuvenate-form-media.rst @@ -0,0 +1,104 @@ +====================== +DEP XXXX: DEP template +====================== + +:DEP: XXXX +:Author: Jacob Kaplan-Moss +:Implementation Team: Jacob Kaplan-Moss +:Shepherd: Andrew Godwin, Carl Meyer +:Status: Draft +:Type: Feature +:Created: 2014-11-16 +:Last-Modified: 2014-11-18 + +.. contents:: Table of Contents + :depth: 3 + :local: + +This DEP provides a sample template for creating your own DEPs. In conjunction +with the content guidelines in `DEP 1 <https://github.com/django/deps/final/0001-dep-process.rst>`_, +this should make it easy for you to conform your own DEPs to the format +outlined below. + +Note: if you are reading this DEP via the web, you should first grab `the source +of this DEP <https://raw.githubusercontent.com/django/deps/template.rst>`_ in +order to complete the steps below. **DO NOT USE THE HTML FILE AS YOUR +TEMPLATE!** + +To get the source this (or any) DEP, look at the top of the Github page +and click "raw". + +If you're unfamiliar with reStructuredText (the format required of DEPs), +see these resources: + +* `A ReStructuredText Primer`__, a gentle introduction. +* `Quick reStructuredText`__, a users' quick reference. +* `reStructuredText Markup Specification`__, the final authority. + +__ http://docutils.sourceforge.net/docs/rst/quickstart.html +__ http://docutils.sourceforge.net/docs/rst/quickref.html +__ http://docutils.sourceforge.net/spec/rst/reStructuredText.html + +Once you've made a copy of this template, remove this abstract, fill out the +metadata above and the sections below, then submit the DEP. Follow the +guidelines in `DEP 1 <https://github.com/django/deps/final/0001-dep-process.rst>`_. + +Abstract +======== + +This should be a short (~200 word) description of the technical issue being +addressed. + +This (and the above metadata) is the only section strictly required to submit a +draft DEP; the following sections can be barebones and fleshed out as you work +through the DEP process. + +Specification +============= + +This section should contain a complete, detailed technical specification should +describe the syntax and semantics of any new feature. The specification should +be detailed enough to allow implementation -- that is, developers other than the +author should (given the right experience) be able to independently implement +the feature, given only the DEP. + +Motivation +========== + +This section should explain *why* this DEP is needed. The motivation is critical +for DEPs that want to add substantial new features or materially refactor +existing ones. It should clearly explain why the existing solutions are +inadequate to address the problem that the DEP solves. DEP submissions without +sufficient motivation may be rejected outright. + +Rationale +========= + +This section should flesh out out the specification by describing what motivated +the specific design and why particular design decisions were made. It +should describe alternate designs that were considered and related work. + +The rationale should provide evidence of consensus within the community and +discuss important objections or concerns raised during discussion. + +Backwards Compatibility +======================= + +If this DEP introduces backwards incompatibilities, you must must include this +section. It should describe these incompatibilities and their severity, and what +mitigation you plan to take to deal with these incompatibilities. + +Reference Implementation +======================== + +If there's an implementation of the feature under discussion in this DEP, +this section should include or link to that implementation and provide any +notes about installing/using/trying out the implementation. + +Copyright +========= + +This document has been placed in the public domain per the Creative Commons +CC0 1.0 Universal license (http://creativecommons.org/publicdomain/zero/1.0/deed). + +(All DEPs must include this exact copyright statement.) From 07962bd67947104c038514124b158d8621d1bdf6 Mon Sep 17 00:00:00 2001 From: Thibaud Colas <thibaudcolas@gmail.com> Date: Tue, 19 Dec 2023 10:58:32 +0000 Subject: [PATCH 2/4] Add first draft content --- draft/0000-rejuvenate-form-media.rst | 107 ++++++++++++--------------- 1 file changed, 49 insertions(+), 58 deletions(-) diff --git a/draft/0000-rejuvenate-form-media.rst b/draft/0000-rejuvenate-form-media.rst index 8e857349..87a7667b 100644 --- a/draft/0000-rejuvenate-form-media.rst +++ b/draft/0000-rejuvenate-form-media.rst @@ -1,75 +1,52 @@ ====================== -DEP XXXX: DEP template +DEP XXXX: Rejuvenate form media ====================== :DEP: XXXX -:Author: Jacob Kaplan-Moss -:Implementation Team: Jacob Kaplan-Moss -:Shepherd: Andrew Godwin, Carl Meyer +:Author: Thibaud Colas +:Implementation Team: You? People in the `forum thread: Rejuvenating vs deprecating Form.Media <https://forum.djangoproject.com/t/rejuvenating-vs-deprecating-form-media/21285>`_ +:Shepherd: You? :Status: Draft :Type: Feature -:Created: 2014-11-16 -:Last-Modified: 2014-11-18 +:Created: 2023-12-12 +:Last-Modified: 2023-12-19 .. contents:: Table of Contents :depth: 3 :local: -This DEP provides a sample template for creating your own DEPs. In conjunction -with the content guidelines in `DEP 1 <https://github.com/django/deps/final/0001-dep-process.rst>`_, -this should make it easy for you to conform your own DEPs to the format -outlined below. - -Note: if you are reading this DEP via the web, you should first grab `the source -of this DEP <https://raw.githubusercontent.com/django/deps/template.rst>`_ in -order to complete the steps below. **DO NOT USE THE HTML FILE AS YOUR -TEMPLATE!** - -To get the source this (or any) DEP, look at the top of the Github page -and click "raw". - -If you're unfamiliar with reStructuredText (the format required of DEPs), -see these resources: - -* `A ReStructuredText Primer`__, a gentle introduction. -* `Quick reStructuredText`__, a users' quick reference. -* `reStructuredText Markup Specification`__, the final authority. - -__ http://docutils.sourceforge.net/docs/rst/quickstart.html -__ http://docutils.sourceforge.net/docs/rst/quickref.html -__ http://docutils.sourceforge.net/spec/rst/reStructuredText.html - -Once you've made a copy of this template, remove this abstract, fill out the -metadata above and the sections below, then submit the DEP. Follow the -guidelines in `DEP 1 <https://github.com/django/deps/final/0001-dep-process.rst>`_. - Abstract ======== -This should be a short (~200 word) description of the technical issue being -addressed. - -This (and the above metadata) is the only section strictly required to submit a -draft DEP; the following sections can be barebones and fleshed out as you work -through the DEP process. +See `forum thread: Rejuvenating vs deprecating Form.Media <https://forum.djangoproject.com/t/rejuvenating-vs-deprecating-form-media/21285>`_. +We want `form.Media <https://docs.djangoproject.com/en/5.0/topics/forms/media/>`_ to catch up with modern web standards, so Django projects can more easily leverage those standards. Specification ============= -This section should contain a complete, detailed technical specification should -describe the syntax and semantics of any new feature. The specification should -be detailed enough to allow implementation -- that is, developers other than the -author should (given the right experience) be able to independently implement -the feature, given only the DEP. +See the `MDN script element documentation <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script>`_ and `link element documentation <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link>`_. + +Here are requirements which Django could/should better support: + +- ES modules +- Import maps +- Dynamic module imports +- async, defer scripts +- CSPs via nonce attributes +- integrity attribute +- fetchpriority attribute +- nomodule attribute +- Arbitrary script attributes +- Preloading / speculative loading +- Resource ordering (see `capo.js <https://rviscomi.github.io/capo.js/>`_) +- Web Components + Motivation ========== -This section should explain *why* this DEP is needed. The motivation is critical -for DEPs that want to add substantial new features or materially refactor -existing ones. It should clearly explain why the existing solutions are -inadequate to address the problem that the DEP solves. DEP submissions without -sufficient motivation may be rejected outright. +`form.Media <https://docs.djangoproject.com/en/5.0/topics/forms/media/>`_ is a good API to manage a form widget’s dependencies. +Though not all projects need to keep a strict record of each widget’s dependencies Rationale ========= @@ -84,21 +61,35 @@ discuss important objections or concerns raised during discussion. Backwards Compatibility ======================= -If this DEP introduces backwards incompatibilities, you must must include this -section. It should describe these incompatibilities and their severity, and what -mitigation you plan to take to deal with these incompatibilities. +At this stage I wouldn’t expect this to introduce any backwards-incompatible changes. If we did so, it would be with a very gradual deprecation path, likely only in the interest of: + +- Better performance (for example default to more modern script loading techniques) +- Better security (for example default to… more modern script loading techniques) Reference Implementation ======================== -If there's an implementation of the feature under discussion in this DEP, -this section should include or link to that implementation and provide any -notes about installing/using/trying out the implementation. +Here are the most fully-fledged implementations so far: + +- https://github.com/matthiask/django-js-asset/ +- https://github.com/rails/importmap-rails + +Other references: + +- https://github.com/dropseed/django-importmap +- https://github.com/tonysm/importmap-laravel + +TODOs +===== + +- Add more possible requirements +- Review https://github.com/wsvincent/awesome-django for packages with form media-related functionality. +- Review https://djangopackages.org/ for packages with form media-related functionality. +- Update https://github.com/st3v3nmw/awesome-django-performance with existing performance-related Django packages relating to form assets. +- Also update https://github.com/wsvincent/awesome-django with good packages in this category Copyright ========= This document has been placed in the public domain per the Creative Commons CC0 1.0 Universal license (http://creativecommons.org/publicdomain/zero/1.0/deed). - -(All DEPs must include this exact copyright statement.) From a968cbddcd8d733f5d1c58c3afb772206d26a804 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz <mk@feinheit.ch> Date: Fri, 14 Feb 2025 17:34:15 +0100 Subject: [PATCH 3/4] Continue working on the DEP --- draft/0000-rejuvenate-form-media.rst | 142 ++++++++++++++++++++++----- 1 file changed, 120 insertions(+), 22 deletions(-) diff --git a/draft/0000-rejuvenate-form-media.rst b/draft/0000-rejuvenate-form-media.rst index 87a7667b..9b44553f 100644 --- a/draft/0000-rejuvenate-form-media.rst +++ b/draft/0000-rejuvenate-form-media.rst @@ -3,13 +3,13 @@ DEP XXXX: Rejuvenate form media ====================== :DEP: XXXX -:Author: Thibaud Colas +:Author: Matthias Kestenholz, Thibaud Colas :Implementation Team: You? People in the `forum thread: Rejuvenating vs deprecating Form.Media <https://forum.djangoproject.com/t/rejuvenating-vs-deprecating-form-media/21285>`_ :Shepherd: You? :Status: Draft :Type: Feature :Created: 2023-12-12 -:Last-Modified: 2023-12-19 +:Last-Modified: 2025-02-14 .. contents:: Table of Contents :depth: 3 @@ -18,35 +18,126 @@ DEP XXXX: Rejuvenate form media Abstract ======== -See `forum thread: Rejuvenating vs deprecating Form.Media <https://forum.djangoproject.com/t/rejuvenating-vs-deprecating-form-media/21285>`_. -We want `form.Media <https://docs.djangoproject.com/en/5.0/topics/forms/media/>`_ to catch up with modern web standards, so Django projects can more easily leverage those standards. +``forms.Media`` allows Django's forms, widgets and the administration interface to define required CSS and JavaScript assets, collecting the required assets from all widgets on a page, ordering them and outputting the result to the browser. + +The functionality has been somewhat neglected since it has been introduced initially, and the question was raised whether `the media object should be rejuvenated or deprecated <https://forum.djangoproject.com/t/rejuvenating-vs-deprecating-form-media/21285>`_. The consensus was that ``forms.Media`` was widely used and seen as useful. + +In the meantime, `object-based Script objects <https://docs.djangoproject.com/en/5.2/topics/forms/media/#script-objects>`_ were introduced to Django adding an easy way to add attributes to script tags. This DEP proposes ways to further enhance ``forms.Media`` so Django projects can more easily leverage the Web standards around asset loading. + Specification ============= -See the `MDN script element documentation <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script>`_ and `link element documentation <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link>`_. +A renewed ``forms.Media`` should take full advantage of the object-based assets functionality. + +The ``Script`` implementation is already there. + +A new ``Stylesheet`` implementation can be easily added by reusing the existing ``django.forms.widgets.MediaAsset`` class, while preserving the ``media="all"`` default of the existing code: + +.. code-block:: python + + class Stylesheet(MediaAsset): + element_template = '<link href="{path}"{attributes}>' + + def __init__(self, path, **attributes): + attributes.setdefault("media", "all") + # Alter the signature to allow src to be passed as a keyword argument. + super().__init__(path, **attributes) + + +The current differentiation between CSS and JS assets isn't necessary, instead, assets can be immediately converted into object-based assets and collected in one list. The existing ``_css_lists`` and ``_js_lists`` attributes of ``forms.Media`` are replaced by a single ``_assets_lists`` attribute. This avoids the confusion of allowing to specify the medium for stylesheets both in the dictionary key (``css = {"all": ["style.css"]}``) and also in the object (``Stylesheet("style.css", media="all")``). + +Browser support for `importmaps <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap>`_ has reached baseline in 2023. However, browsers still only support one importmap per page, which means that there has to exist a way to aggregate importmap entries from all sorts of forms, widgets, etc. The ``forms.Media`` class already does something like this, so it could be easily extended to also allow importmap entries: + +.. code-block:: python + + @html_safe + @dataclass(eq=True) + class ImportMapImport: + key: str + value: str + scope: str | None = None + + def __hash__(self): + return hash((self.key, self.value, self.scope)) + + def __str__(self): + return "" + +The ``forms.Media`` rendering should then be extended to automatically collect importmap entries and produce the appropriate ``<script type="importmap">`` element on the page: + +.. code-block:: python + + class Media: + # ... + + def render(self): + assets = self.merge(*self._asset_lists) + + importmap = self.render_importmap( + asset for asset in assets if isinstance(asset, ImportMapImport) + ) -Here are requirements which Django could/should better support: + return mark_safe( + "\n".join( + filter(None, chain([importmap], (asset.__html__() for asset in assets))) + ) + ) + + def render_importmap(self, entries): + if not entries: + return "" + importmap = {"imports": {}} + for entry in entries: + if entry.scope: + scope = importmap.setdefault("scopes", {}).setdefault(entry.scope, {}) + scope[entry.key] = entry.value + else: + importmap["imports"][entry.key] = entry.value + html = json_script(importmap).removeprefix('<script type="application/json">') + return mark_safe(f'<script type="importmap">{html}') + + +Deferred functionality +~~~~~~~~~~~~~~~~~~~~~~ + +This DEP doesn't yet propose a way to add support for the following functionalities, but the groundwork done here would offer a better foundation for adding support for: + +- CSP via ``nonce`` attributes +- Automatic ``integrity`` attributes +- Possible postprocessing and/or bundling of assets + +And maybe also: -- ES modules -- Import maps -- Dynamic module imports -- async, defer scripts -- CSPs via nonce attributes -- integrity attribute -- fetchpriority attribute -- nomodule attribute -- Arbitrary script attributes - Preloading / speculative loading - Resource ordering (see `capo.js <https://rviscomi.github.io/capo.js/>`_) -- Web Components +- Web Components (@Thibaud, I'm not sure I understand this point) Motivation ========== -`form.Media <https://docs.djangoproject.com/en/5.0/topics/forms/media/>`_ is a good API to manage a form widget’s dependencies. -Though not all projects need to keep a strict record of each widget’s dependencies +Django has supported object-based assets in ``forms.Media`` for several years. Proper support has been added in `#29490 <https://code.djangoproject.com/ticket/29490>`_, however Django hasn't shipped any classes using this facility until recently. + +Django 5.2 has introduced support for `object-based JavaScript objects <https://docs.djangoproject.com/en/5.2/topics/forms/media/#script-objects>`_, making it possible to easily add script tags with arbitrary HTML attributes, for example to add ``type="module"``: + +.. code-block:: python + + from django import forms + + media = forms.Media( + js=[forms.Script("module.js", type="module")] + ) + +``forms.Media`` can contain arbitrary object-based assets The same doesn't +exist for stylesheets or other asset types. + +As an example, the third party package `django-js-asset +<https://pypi.org/project/django-js-asset/>`_ (Disclaimer: I'm the primary +author.) have taken advantage of object-based media for a long time, and ship +objects which allow adding CSS, JavaScript and JSON as media assets. + +This section should explain why this DEP is needed. The motivation is critical for DEPs that want to add substantial new features or materially refactor existing ones. It should clearly explain why the existing solutions are inadequate to address the problem that the DEP solves. DEP submissions without sufficient motivation may be rejected outright. Rationale ========= @@ -61,14 +152,22 @@ discuss important objections or concerns raised during discussion. Backwards Compatibility ======================= -At this stage I wouldn’t expect this to introduce any backwards-incompatible changes. If we did so, it would be with a very gradual deprecation path, likely only in the interest of: +Code which directly uses the existing ``_css_lists`` and ``_js_lists`` attributes would have to be changed. Those attributes are not documented, and the leading underscore clearly communicates that they are an implementation detail. They are not part of the public API and we should therefore be able to remove them as discussed above without too much fanfare. + +- django-csp-helpers (``CSPAwareMedia``) -- Better performance (for example default to more modern script loading techniques) -- Better security (for example default to… more modern script loading techniques) Reference Implementation ======================== +An experimental implementation supporting importmaps and the discussed unification of object-based media is available here: + +https://github.com/matthiask/django-js-asset/compare/mk/importmaps + +It even works with relased Django versions, but it doesn't use the ``forms.Script`` class yet, that would have to be changed. + + + Here are the most fully-fledged implementations so far: - https://github.com/matthiask/django-js-asset/ @@ -85,7 +184,6 @@ TODOs - Add more possible requirements - Review https://github.com/wsvincent/awesome-django for packages with form media-related functionality. - Review https://djangopackages.org/ for packages with form media-related functionality. -- Update https://github.com/st3v3nmw/awesome-django-performance with existing performance-related Django packages relating to form assets. - Also update https://github.com/wsvincent/awesome-django with good packages in this category Copyright From e0ca33098838ca1dde6c3e3d653efeb5a85938b3 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz <mk@feinheit.ch> Date: Fri, 14 Feb 2025 17:39:13 +0100 Subject: [PATCH 4/4] Remove a copy-pasted and confusing comment --- draft/0000-rejuvenate-form-media.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/draft/0000-rejuvenate-form-media.rst b/draft/0000-rejuvenate-form-media.rst index 9b44553f..57629170 100644 --- a/draft/0000-rejuvenate-form-media.rst +++ b/draft/0000-rejuvenate-form-media.rst @@ -41,7 +41,6 @@ A new ``Stylesheet`` implementation can be easily added by reusing the existing def __init__(self, path, **attributes): attributes.setdefault("media", "all") - # Alter the signature to allow src to be passed as a keyword argument. super().__init__(path, **attributes)