forked from ungleich-public/dynamicweb
118 lines
4.5 KiB
Python
118 lines
4.5 KiB
Python
import copy
|
|
import json
|
|
import os
|
|
import re
|
|
import shutil
|
|
import tempfile
|
|
from io import StringIO
|
|
|
|
from django.conf import settings
|
|
from django.core.management import call_command
|
|
from django.template import Context, Template
|
|
from django.test import SimpleTestCase, override_settings
|
|
|
|
import cms
|
|
|
|
MANIFEST_BACKEND = "django.contrib.staticfiles.storage.ManifestStaticFilesStorage"
|
|
|
|
CMS_TEMPLATE_DIR = os.path.join(os.path.dirname(cms.__file__), "templates")
|
|
|
|
# {% static "path/to/file.ext" %} / {% static_with_version "path/to/file.ext" %}
|
|
STATIC_TAG_RE = re.compile(
|
|
r"{%\s*(?P<tag>static|static_with_version)\s+"
|
|
r"(?P<quote>[\"'])(?P<path>[^\"']+)(?P=quote)\s*%}"
|
|
)
|
|
|
|
|
|
def iter_template_static_tags():
|
|
"""
|
|
Yield ``(template, tag)`` tuples for every static file reference with a
|
|
literal path found in the templates shipped with django CMS. Paths built
|
|
from template variables or filters are skipped, they cannot be resolved
|
|
without a rendering context.
|
|
"""
|
|
for dirpath, _, filenames in os.walk(CMS_TEMPLATE_DIR):
|
|
for filename in sorted(filenames):
|
|
if not filename.endswith(".html"):
|
|
continue
|
|
template = os.path.join(dirpath, filename)
|
|
with open(template, encoding="utf-8") as fobj:
|
|
contents = fobj.read()
|
|
for match in STATIC_TAG_RE.finditer(contents):
|
|
yield os.path.relpath(template, CMS_TEMPLATE_DIR), match.group(0)
|
|
|
|
|
|
class ManifestStaticFilesTestCase(SimpleTestCase):
|
|
"""
|
|
Projects commonly run ``collectstatic`` with Django's
|
|
``ManifestStaticFilesStorage``. Contrary to the plain storage backend it
|
|
hashes every collected file and looks up references in a manifest, which
|
|
turns a dangling reference into a hard error instead of a 404.
|
|
|
|
``DEBUG`` is turned off for these tests, as the manifest storage bypasses
|
|
the manifest lookup entirely while ``DEBUG`` is on.
|
|
"""
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
cls.static_root = tempfile.mkdtemp(prefix="cms-manifest-static-")
|
|
cls.addClassCleanup(shutil.rmtree, cls.static_root, ignore_errors=True)
|
|
|
|
storages = copy.deepcopy(settings.STORAGES)
|
|
storages["staticfiles"]["BACKEND"] = MANIFEST_BACKEND
|
|
overridden_settings = override_settings(
|
|
DEBUG=False,
|
|
STATIC_ROOT=cls.static_root,
|
|
STORAGES=storages,
|
|
)
|
|
overridden_settings.enable()
|
|
cls.addClassCleanup(overridden_settings.disable)
|
|
|
|
# A dangling reference inside a collected css/js file makes
|
|
# post-processing - and with it the management command - fail.
|
|
out = StringIO()
|
|
call_command(
|
|
"collectstatic",
|
|
interactive=False,
|
|
clear=True,
|
|
verbosity=0,
|
|
stdout=out,
|
|
stderr=out,
|
|
)
|
|
cls.collectstatic_output = out.getvalue()
|
|
|
|
def test_collectstatic_writes_manifest(self):
|
|
"""``collectstatic`` post-processes the static files shipped by the CMS."""
|
|
manifest_path = os.path.join(self.static_root, "staticfiles.json")
|
|
|
|
self.assertTrue(
|
|
os.path.exists(manifest_path),
|
|
f"No staticfiles manifest was written: {self.collectstatic_output}",
|
|
)
|
|
with open(manifest_path, encoding="utf-8") as fobj:
|
|
manifest = json.load(fobj)["paths"]
|
|
|
|
cms_files = {path: name for path, name in manifest.items() if path.startswith("cms/")}
|
|
self.assertTrue(cms_files, "No django CMS static files were collected")
|
|
for path, hashed_name in cms_files.items():
|
|
with self.subTest(path=path):
|
|
self.assertTrue(
|
|
os.path.exists(os.path.join(self.static_root, hashed_name)),
|
|
f"{path} is in the manifest, but {hashed_name} was not collected",
|
|
)
|
|
|
|
def test_template_static_references_resolve(self):
|
|
"""
|
|
Every static file referenced by a django CMS template can be resolved
|
|
against the manifest. A missing entry raises a ``ValueError`` and makes
|
|
the whole template fail to render.
|
|
"""
|
|
tags = list(iter_template_static_tags())
|
|
|
|
self.assertTrue(tags, "No static file references found in the CMS templates")
|
|
for template, tag in tags:
|
|
with self.subTest(template=template, tag=tag):
|
|
source = "{% load static cms_static %}" + tag
|
|
# Raises ValueError for files missing from the manifest
|
|
Template(source).render(Context({}))
|