Skip to content

Commit a35483a

Browse files
authored
Add ImportESM form media asset (#52)
1 parent b341968 commit a35483a

File tree

5 files changed

+120
-0
lines changed

5 files changed

+120
-0
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ jobs:
6868
- uses: codecov/codecov-action@v5
6969
with:
7070
flags: py${{ matrix.python-version }}
71+
token: ${{ secrets.CODECOV_TOKEN }}
7172

7273
pytest-django:
7374
name: PyTest
@@ -94,6 +95,7 @@ jobs:
9495
- uses: codecov/codecov-action@v5
9596
with:
9697
flags: dj${{ matrix.django-version }}
98+
token: ${{ secrets.CODECOV_TOKEN }}
9799

98100
pytest-extras:
99101
name: PyTest
@@ -115,6 +117,7 @@ jobs:
115117
- uses: codecov/codecov-action@v5
116118
with:
117119
flags: dj${{ matrix.extras }}
120+
token: ${{ secrets.CODECOV_TOKEN }}
118121

119122
codeql:
120123
name: CodeQL

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,29 @@ You can now import JavaScript modules in your Django templates:
8080
{% endblock %}
8181
```
8282

83+
### Form.media
84+
85+
To use your importmap in Django forms, you can use the `Form.media` attribute:
86+
87+
```python
88+
# forms.py
89+
from django import forms
90+
from django_esm.forms import ImportESModule
91+
92+
93+
class MyForm(forms.Form):
94+
name = forms.CharField()
95+
96+
class Media:
97+
js = [ImportESModule("@sentry/browser")]
98+
```
99+
100+
Now `{{ form.media.js }}` will render to like this:
101+
102+
```html
103+
<script type="module">import '@sentry/browser'</script>
104+
```
105+
83106
### Private modules
84107
85108
You can also import private modules from your Django app:

django_esm/forms.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
try:
2+
from django.forms import Script
3+
except ImportError: # pragma: no cover
4+
# Django < 5.2 backport
5+
from django.forms.utils import flatatt
6+
from django.templatetags.static import static
7+
from django.utils.html import format_html, html_safe
8+
9+
@html_safe
10+
class MediaAsset:
11+
element_template = "{path}"
12+
13+
def __init__(self, path, **attributes):
14+
self._path = path
15+
self.attributes = attributes
16+
17+
def __eq__(self, other):
18+
# Compare the path only, to ensure performant comparison in Media.merge.
19+
return (self.__class__ is other.__class__ and self.path == other.path) or (
20+
isinstance(other, str) and self._path == other
21+
)
22+
23+
def __hash__(self):
24+
# Hash the path only, to ensure performant comparison in Media.merge.
25+
return hash(self._path)
26+
27+
def __str__(self):
28+
return format_html(
29+
self.element_template,
30+
path=self.path,
31+
attributes=flatatt(self.attributes),
32+
)
33+
34+
def __repr__(self):
35+
return f"{type(self).__qualname__}({self._path!r})"
36+
37+
@property
38+
def path(self):
39+
if self._path.startswith(("http://", "https://", "/")):
40+
return self._path
41+
return static(self._path)
42+
43+
class Script(MediaAsset):
44+
element_template = '<script src="{path}"{attributes}></script>'
45+
46+
def __init__(self, src, **attributes):
47+
# Alter the signature to allow src to be passed as a keyword argument.
48+
super().__init__(src, **attributes)
49+
50+
51+
__all__ = ["ImportESModule"]
52+
53+
54+
class ImportESModule(Script):
55+
"""
56+
Import ES module inline via an importmap.
57+
58+
Usage:
59+
class MyForm(forms.Form):
60+
class Media:
61+
js = [ImportESModule("@sentry/browser")]
62+
"""
63+
64+
# https://code.djangoproject.com/ticket/36353
65+
element_template = "<script{attributes}>import '{path}'</script>"
66+
67+
def __init__(self, src, **attributes):
68+
super().__init__(src, **attributes | {"type": "module"})
69+
70+
@property
71+
def path(self):
72+
return self._path

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ dependencies = [
4141
whitenoise = [
4242
"whitenoise>=6.0",
4343
]
44+
csp = [
45+
"django-csp",
46+
]
4447
test = [
4548
"pytest",
4649
"pytest-cov",

tests/test_forms.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from django_esm import forms
2+
3+
4+
class TestImportESModule:
5+
6+
def test_str(self):
7+
assert str(forms.ImportESModule("@sentry/browser")) == (
8+
"""<script type="module">import '@sentry/browser'</script>"""
9+
)
10+
11+
assert str(forms.ImportESModule("#js/myEntryPoint")) == (
12+
"""<script type="module">import '#js/myEntryPoint'</script>"""
13+
)
14+
15+
def test_eq(self):
16+
"""Avoid duplication and enable form media merging."""
17+
assert forms.ImportESModule("@sentry/browser") == forms.ImportESModule(
18+
"@sentry/browser"
19+
)

0 commit comments

Comments
 (0)