1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-08 11:16:47 +00:00

chore(deps): the icons come from the committed cache, not django-bootstrap-icons

django-bootstrap-icons (last release July 2024) only read the SVG of
each {% bs_icon %} from static/icon_cache; for an icon not there it
fetched the SVG from a CDN while rendering the page, and it brought
defusedxml along. A 20-line template tag in webvirtcloud reads the
same files; the package, its CDN path and the BS_ICONS_CACHE setting
go. requests stays: websockify needs it.
This commit is contained in:
catborise 2026-11-05 12:07:10 +03:00
parent 74483fc5f3
commit 2573228569
5 changed files with 39 additions and 12 deletions

View file

@ -1,8 +1,7 @@
Django==5.2.18
django_bootstrap5==26.3
django-bootstrap-icons==0.9.0
django-otp==1.7.4
# for django-bootstrap-icons and websockify; a host's python3-requests would
# for websockify; a host's python3-requests would
# otherwise bring idna, requests and urllib3 with advisories
idna>=3.15
requests>=2.33.0

View file

@ -0,0 +1,24 @@
"""{% bs_icon %}: a Bootstrap icon's SVG, inline, from static/icon_cache,
which holds each icon a template uses at the size, color and classes it
asks for (webvirtcloud/tests/test_icon_cache.py checks every template)."""
from functools import cache
from pathlib import Path
from django import template
from django.conf import settings
from django.utils.safestring import mark_safe
register = template.Library()
ICONS = Path(settings.BASE_DIR, "static", "icon_cache")
@register.simple_tag
def bs_icon(name, size=None, color=None, extra_classes=None):
return _svg(f"{name}_{size}_{color}_{extra_classes}.svg".replace(" ", "_"))
@cache # one file a literal of the templates
def _svg(file_name):
return mark_safe((ICONS / file_name).read_text(encoding="utf-8"))

View file

@ -62,7 +62,6 @@ INSTALLED_APPS = [
"django.contrib.messages",
"django.contrib.staticfiles",
"django_bootstrap5",
"django_bootstrap_icons",
"django_otp",
"django_otp.plugins.otp_totp",
"drf_spectacular",
@ -127,6 +126,7 @@ TEMPLATES = [
],
"libraries": {
"common_tags": "webvirtcloud.common_tags",
"bootstrap_icons": "webvirtcloud.bootstrap_icons",
},
},
},
@ -227,8 +227,6 @@ STATICFILES_DIRS = [BASE_DIR / "static"]
STATIC_ROOT = BASE_DIR / "staticfiles"
BS_ICONS_CACHE = Path.joinpath(BASE_DIR, 'static/icon_cache')
# Default primary key field type
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field

View file

@ -1,12 +1,14 @@
"""Every {% bs_icon %} of the templates is in the committed icon cache
(BS_ICONS_CACHE). A missing one is fetched from the CDN when a page is
rendered, or shown as an error where the server has no internet access."""
(webvirtcloud/bootstrap_icons.py): a missing one is an error page."""
import re
import unittest
from pathlib import Path
from django.conf import settings
from django.template import Context, Template
from webvirtcloud.bootstrap_icons import ICONS
BASE = Path(settings.BASE_DIR)
CALL = re.compile(r"\{%\s*bs_icon\s+(.*?)\s*%\}")
@ -16,7 +18,7 @@ KWARG = re.compile(r"""(\w+)=['"]([^'"]*)['"]""")
class IconCacheTestCase(unittest.TestCase):
def test_every_template_icon_is_cached(self):
cache = Path(settings.BS_ICONS_CACHE)
cache = ICONS
missing = []
templates = [*BASE.glob("templates/**/*.html"), *BASE.glob("*/templates/**/*.html")]
self.assertGreater(len(templates), 50) # the globs still find the templates
@ -25,8 +27,13 @@ class IconCacheTestCase(unittest.TestCase):
name = NAME.match(args)
self.assertIsNotNone(name, f"{template}: icon name is not a literal: {args}")
kw = dict(KWARG.findall(args))
# the file name django-bootstrap-icons uses for its cache
# the file name bs_icon reads
cached = f"{name.group(1)}_{kw.get('size')}_{kw.get('color')}_{kw.get('extra_classes')}.svg".replace(" ", "_")
if not (cache / cached).exists():
missing.append(f"{template.relative_to(BASE)}: {cached}")
self.assertEqual(missing, [])
def test_an_icon_is_its_svg_inline(self):
html = Template("{% load bootstrap_icons %}{% bs_icon 'pencil' %}").render(Context())
self.assertEqual(html, (ICONS / "pencil_None_None_None.svg").read_text(encoding="utf-8"))
self.assertIn("<svg", html)

View file

@ -18,14 +18,13 @@ class StaticFilesTestCase(SimpleTestCase):
self.assertIn(SOURCE, [Path(d) for d in settings.STATICFILES_DIRS])
def test_no_collected_files_in_the_source_dir(self):
for byproduct in ("staticfiles.json", "rest_framework", "bootstrap_icons"):
for byproduct in ("staticfiles.json", "rest_framework"):
self.assertFalse((SOURCE / byproduct).exists(), byproduct)
self.assertEqual(list(SOURCE.rglob("*.gz")), [])
def test_collectstatic_gathers_source_and_app_assets(self):
with tempfile.TemporaryDirectory() as root, override_settings(STATIC_ROOT=root):
call_command("collectstatic", "--noinput", verbosity=0)
for name in ("css/webvirtcloud.css", "rest_framework/css/bootstrap.min.css",
"bootstrap_icons/css/bootstrap_icons.css"):
for name in ("css/webvirtcloud.css", "rest_framework/css/bootstrap.min.css"):
self.assertTrue((Path(root) / name).exists(), name)
self.assertIsNotNone(finders.find("css/webvirtcloud.css"))