pax_global_header00006660000000000000000000000064147616152730014526gustar00rootroot0000000000000052 comment=008fec1d6504757dc489e6a2a1b27489ec74aae4 python-django-js-asset-3.1.2/000077500000000000000000000000001476161527300160415ustar00rootroot00000000000000python-django-js-asset-3.1.2/.editorconfig000066400000000000000000000003101476161527300205100ustar00rootroot00000000000000# top-most EditorConfig file root = true [*] end_of_line = lf insert_final_newline = true charset = utf-8 trim_trailing_whitespace = true indent_style = space indent_size = 2 [*.py] indent_size = 4 python-django-js-asset-3.1.2/.github/000077500000000000000000000000001476161527300174015ustar00rootroot00000000000000python-django-js-asset-3.1.2/.github/workflows/000077500000000000000000000000001476161527300214365ustar00rootroot00000000000000python-django-js-asset-3.1.2/.github/workflows/tests.yml000066400000000000000000000015721476161527300233300ustar00rootroot00000000000000name: Tests on: push: branches: [main] pull_request: branches: [main] schedule: - cron: 37 1 1 * * jobs: tests: name: Python ${{ matrix.python-version }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: python-version: - '3.10' - '3.11' - '3.12' - '3.13' steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip wheel setuptools tox - name: Run tox targets for ${{ matrix.python-version }} run: | ENV_PREFIX=$(tr -C -d "0-9" <<< "${{ matrix.python-version }}") TOXENV=$(tox --listenvs | grep "^py$ENV_PREFIX" | tr '\n' ',') python -m tox python-django-js-asset-3.1.2/.gitignore000066400000000000000000000002301476161527300200240ustar00rootroot00000000000000*.py? *~ *.sw? \#*# /secrets.py .DS_Store ._* /*.egg-info /MANIFEST /_build /build /dist tests/test.zip /docs/_build /.eggs .coverage htmlcov venv .tox python-django-js-asset-3.1.2/.pre-commit-config.yaml000066400000000000000000000016661476161527300223330ustar00rootroot00000000000000exclude: ".yarn/|yarn.lock|\\.min\\.(css|js)$" repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v5.0.0 hooks: - id: check-added-large-files - id: check-builtin-literals - id: check-executables-have-shebangs - id: check-merge-conflict - id: check-toml - id: check-yaml - id: detect-private-key - id: end-of-file-fixer - id: mixed-line-ending - id: trailing-whitespace - repo: https://github.com/adamchainz/django-upgrade rev: 1.22.2 hooks: - id: django-upgrade args: [--target-version, "3.2"] - repo: https://github.com/astral-sh/ruff-pre-commit rev: "v0.8.3" hooks: - id: ruff - id: ruff-format - repo: https://github.com/tox-dev/pyproject-fmt rev: v2.5.0 hooks: - id: pyproject-fmt - repo: https://github.com/abravalheri/validate-pyproject rev: v0.23 hooks: - id: validate-pyproject python-django-js-asset-3.1.2/CHANGELOG.rst000066400000000000000000000072761476161527300200760ustar00rootroot00000000000000 .. _changelog: Change log ========== Next version ~~~~~~~~~~~~ - Added a ``static_lazy`` helper. 3.1 (2025-02-28) ~~~~~~~~~~~~~~~~ - Made the ``id`` argument to ``JSON`` keyword-only. Also made the ``inline`` argument to ``CSS`` keyword-only. - Added the ``media`` attribute to ``CSS`` classes. - Added experimental support for shipping importmaps. 3.0 (2024-12-17) ~~~~~~~~~~~~~~~~ - Rewrite the internals using dataclasses, drop compatibility with Django < 4.2 and Python < 3.10. - Added a ``CSS`` and ``JSON`` class which can also be used with ``forms.Media``. It's recommended to pass them as JavaScript entries to ``forms.Media(js=[])`` because the ``js`` list doesn't use a media dictionary. - Added Django 5.1, Python 3.13. 2.2 (2023-12-12) ~~~~~~~~~~~~~~~~ - Started running the tests periodically to detect breakages early. - Added Django 5.0, Python 3.12. - Fixed building with hatchling 1.19. Thanks Michał Górny! 2.1 (2023-06-28) ~~~~~~~~~~~~~~~~ - Added Django 4.1, 4.2 and Python 3.11 to the CI. - Removed the pytz dependency from the tests. - Dropped Python < 3.8, Django < 3.2 from the CI. - Switched to hatchling and ruff. `2.0`_ (2022-02-10) ~~~~~~~~~~~~~~~~~~~ .. _2.0: https://github.com/matthiask/django-js-asset/compare/1.2...2.0 - Raised the minimum supported versions of Python to 3.6, Django to 2.2. - Added pre-commit. - Replaced the explicit configuration of whether ``static()`` should be used or not with automatic configuration. The ``static`` argument is still accepted but ignored and will be removed at a later time. - Added support for boolean attributes when using Django 4.1 or better. Released as 1.2.1 and 1.2.2: ---------------------------- - Made ``JS()`` objects hashable so that they can be put into sets in preparation for a possible fix for media ordering in Django #30179. - Confirmed support for Django 3.0 and 3.1a1. - Django dropped ``type="text/javascript"`` in 3.1, changed our tests to pass again. - Switched from Travis CI to GitHub actions. - Dropped Django 1.7 from the CI jobs list because it somehow didn't discover our tests. - Renamed the main branch to ``main``. - Added CI testing for Django 3.2. `1.2`_ (2019-02-08) ~~~~~~~~~~~~~~~~~~~ - Reformatted the code using Black. - Added equality of ``JS()`` objects to avoid adding the same script more than once in the same configuration. - Determine the ``static`` callable at module import time, not each time a static path is generated. - Customized the ``repr()`` of ``JS()`` objects. - Added Python 3.7 and Django 2.2 to the test matrix. `1.1`_ (2018-04-19) ~~~~~~~~~~~~~~~~~~~ - Added support for skipping ``static()``, mostly useful when adding external scripts via ``JS()`` (e.g for adding ``defer="defer"``). - Made the attributes dictionary optional. `1.0`_ (2018-01-16) ~~~~~~~~~~~~~~~~~~~ - Added an export of the ``js_asset.static()`` helper (which does the right thing regarding ``django.contrib.staticfiles``) - Fixed the documentation to not mention internal (and removed) API of Django's ``Media()`` class. - Switched to using tox_ for running tests and style checks locally. - Added more versions of Python and Django to the CI matrix. `0.1`_ (2017-04-19) ~~~~~~~~~~~~~~~~~~~ - Initial public release extracted from django-content-editor_. .. _Django: https://www.djangoproject.com/ .. _django-content-editor: https://django-content-editor.readthedocs.io/ .. _tox: https://tox.readthedocs.io/ .. _0.1: https://github.com/matthiask/django-js-asset/commit/e335c79a87 .. _1.0: https://github.com/matthiask/django-js-asset/compare/0.1...1.0 .. _1.1: https://github.com/matthiask/django-js-asset/compare/1.0...1.1 .. _1.2: https://github.com/matthiask/django-js-asset/compare/1.1...1.2 python-django-js-asset-3.1.2/LICENSE000066400000000000000000000030161476161527300170460ustar00rootroot00000000000000Copyright (c) 2016, FEINHEIT AG and individual contributors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of FEINHEIT GmbH nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. python-django-js-asset-3.1.2/README.rst000066400000000000000000000121521476161527300175310ustar00rootroot00000000000000================================================================== django-js-asset -- JS, CSS and JSON support for django.forms.Media ================================================================== .. image:: https://github.com/matthiask/django-js-asset/workflows/Tests/badge.svg :target: https://github.com/matthiask/django-js-asset **Note!** `Django 5.2 adds its own support for JavaScript objects `__. This library has a slightly different API and also supports much older versions of Django, *and* it also supports CSS and JSON tags. Usage ===== Use this to insert a script tag via ``forms.Media`` containing additional attributes (such as ``id`` and ``data-*`` for CSP-compatible data injection.): .. code-block:: python from js_asset import JS forms.Media(js=[ JS("asset.js", { "id": "asset-script", "data-answer": "42", }), ]) The rendered media tag (via ``{{ media.js }}`` or ``{{ media }}`` will now contain a script tag as follows, without line breaks: .. code-block:: html The attributes are automatically escaped. The data attributes may now be accessed inside ``asset.js``: .. code-block:: javascript var answer = document.querySelector("#asset-script").dataset.answer; Also, because the implementation of ``static`` differs between supported Django versions (older do not take the presence of ``django.contrib.staticfiles`` in ``INSTALLED_APPS`` into account), a ``js_asset.static`` function is provided which does the right thing automatically. CSS and JSON support ==================== Since 3.0 django-js-asset also ships a ``CSS`` and ``JSON`` media object which can be used to ship stylesheets, inline styles and JSON blobs to the frontend. It's recommended to pass those through ``forms.Media(js=[])`` as well since ``js`` is a simple list while ``css`` uses a dictionary keyed with the media to use for the stylesheet. So, you can add everything at once: .. code-block:: python from js_asset import CSS, JS, JSON forms.Media(js=[ JSON({"configuration": 42}, id="widget-configuration"), CSS("widget/style.css"), CSS("p{color:red;}", inline=True), JS("widget/script.js", {"type": "module"}), ]) This produces: .. code-block:: html Compatibility ============= At the time of writing this app is compatible with Django 4.2 and better (up to and including the Django main branch), but have a look at the `tox configuration `_ for definitive answers. Extremely experimental importmap support ======================================== django-js-asset ships an extremely experimental implementation adding support for using `importmaps `_. One of the reasons why importmaps are useful when used with Django is that this easily allows us to use the file name mangling offered for example by Django ``ManifestStaticFilesStorage`` without having to rewrite import statements in scripts themselves. Browser support for multiple importmaps is not generally available; at the time of writing (February 2025) it's not even clear if Mozilla wants to support them ever, so merging importmaps is -- for now -- the only viable way to use them in production. Because of this the implementation uses a global importmap variable where new entries can be added to and a context processor to make the importmap available to templates. The ``importmap`` object can be imported from ``js_asset``. Usage is as follows: .. code-block:: python # static is an alias for Django's static() function used in the # {% static %} template tag. from js_asset import JS, static, importmap # Run this during project initialization, e.g. in App.ready or whereever. importmap.update({ "imports": { "my-library": static("my-library.js"), }, }) You have to add ``js_asset.context_processors.importmap`` to the list of context processors in your settings (or choose some other way of making the ``importmap`` object available in templates) and add ``{{ importmap }}`` somewhere in your base template, preferrably at the top before including any scripts. When you've done that you can start profiting from the importmap by adding JavaScript modules: .. code-block:: python # Example for adding a code.js JavaScript *module* forms.Media(js=[ JS("code.js", {"type": "module"}), ]) The code in ``code.js`` can now use a JavaScript import to import assets from the library, even though the library's filename may contain hashes not known at programming time: .. code-block:: javascript import { Stuff } from "my-library" python-django-js-asset-3.1.2/js_asset/000077500000000000000000000000001476161527300176545ustar00rootroot00000000000000python-django-js-asset-3.1.2/js_asset/__init__.py000066400000000000000000000001761476161527300217710ustar00rootroot00000000000000__version__ = "3.1.2" import contextlib with contextlib.suppress(ImportError): from js_asset.js import * # noqa: F403 python-django-js-asset-3.1.2/js_asset/context_processors.py000066400000000000000000000001571476161527300241770ustar00rootroot00000000000000from js_asset.js import importmap as _importmap def importmap(request): return {"importmap": _importmap} python-django-js-asset-3.1.2/js_asset/js.py000066400000000000000000000053771476161527300206560ustar00rootroot00000000000000from dataclasses import dataclass, field from typing import Any from django.forms.utils import flatatt from django.templatetags.static import static from django.utils.functional import lazy from django.utils.html import format_html, html_safe, json_script, mark_safe __all__ = ["CSS", "ImportMap", "JS", "JSON", "importmap", "static", "static_lazy"] def static_if_relative(path): return path if path.startswith(("http://", "https://", "/")) else static(path) static_lazy = lazy(static, str) @html_safe @dataclass(eq=True) class CSS: src: str inline: bool = field(default=False, kw_only=True) media: str = "all" def __hash__(self): return hash(self.__str__()) def __str__(self): if self.inline: return format_html('', self.media, self.src) return format_html( '', static_if_relative(self.src), self.media, ) @html_safe @dataclass(eq=True) class JS: src: str attrs: dict[str, Any] = field(default_factory=dict) def __hash__(self): return hash(self.__str__()) def __str__(self): return format_html( '', static_if_relative(self.src), mark_safe(flatatt(self.attrs)), ) @html_safe @dataclass(eq=True) class JSON: data: dict[str, Any] id: str | None = field(default="", kw_only=True) def __hash__(self): return hash(self.__str__()) def __str__(self): return json_script(self.data, self.id) @html_safe class ImportMap: def __init__(self, importmap): self._importmap = importmap def __str__(self): if self._importmap: html = json_script(self._importmap).removeprefix( '""", ) c = ImportMap( { "imports": { "/app/": "./original-app/", "/app/helper": "./helper/index.mjs", }, "scopes": {"/js": {"/app/": "./js-app/"}}, } ) self.assertEqual( str(a | b | c), """\ """, ) python-django-js-asset-3.1.2/tests/testapp/test_js_asset.py000066400000000000000000000057031476161527300241140ustar00rootroot00000000000000from django.forms import Media from django.test import TestCase from js_asset.js import CSS, JS, JSON class AssetTest(TestCase): def test_asset(self): media = Media( css={"print": ["app/print.css"]}, js=[ "app/test.js", JS("app/asset.js", {"id": "asset-script", "data-the-answer": 42}), JS("app/asset-without.js", {}), ], ) html = str(media) # print(html) self.assertInHTML( '', html, ) self.assertInHTML( '', html, ) self.assertInHTML( '', html, ) self.assertInHTML( '', html, ) def test_absolute(self): media = Media(js=[JS("https://cdn.example.org/script.js")]) html = str(media) self.assertInHTML( '', html, ) def test_asset_merging(self): media1 = Media(js=["thing.js", JS("other.js"), "some.js"]) media2 = Media(js=["thing.js", JS("other.js"), "some.js"]) media = media1 + media2 self.assertEqual(len(media._js), 3) self.assertEqual(media._js[0], "thing.js") self.assertEqual(media._js[2], "some.js") def test_set(self): media = [ JS("app/asset.js", {"id": "asset-script", "data-the-answer": 42}), JS("app/asset.js", {"id": "asset-script", "data-the-answer": 42}), JS("app/asset.js", {"id": "asset-script", "data-the-answer": 43}), ] self.assertEqual(len(set(media)), 2) def test_boolean_attributes(self): self.assertEqual( str(JS("app/asset.js", {"bool": True, "cool": False})), '', ) def test_css(self): self.assertEqual( str(CSS("app/style.css")), '', ) self.assertEqual( str(CSS("app/style.css", media="screen")), '', ) self.assertEqual( str(CSS("p{color:red}", inline=True)), '', ) def test_json(self): self.assertEqual( str(JSON({"hello": "world"}, id="hello")), '', ) self.assertEqual( str(JSON({"hello": "world"})), '', ) python-django-js-asset-3.1.2/tox.ini000066400000000000000000000007111476161527300173530ustar00rootroot00000000000000[tox] envlist = py{310,311}-dj{42,50,51} py{312}-dj{42,50,51,52,main} py{313}-dj{51,52,main} [testenv] usedevelop = true extras = tests commands = python -Wd {envbindir}/coverage run tests/manage.py test -v2 --keepdb {posargs:testapp} coverage report -m deps = dj42: Django>=4.2,<5.0 dj50: Django>=5.0,<5.1 dj51: Django>=5.1,<5.2 dj52: Django>=5.2a1,<6.0 djmain: https://github.com/django/django/archive/main.tar.gz