Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions website/templates/website/member.html
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,11 @@
{% load thumbnail %}
{% load ml_tags %}

{# Title, description, og:type=profile, and canonical come from page_meta (see
website/views/member.py). Here we override the share image and add the
profile-specific structural OG tags. #}
{% comment %}
Title, description, og:type=profile, and canonical come from page_meta (see
website/views/member.py). Here we override the share image and add the
profile-specific structural OG tags.
{% endcomment %}
{% block social_image %}
{% thumbnail person.image '1200x630' box=person.cropping crop=True upscale=True as og_img %}
{% if og_img %}
Expand Down
8 changes: 5 additions & 3 deletions website/templates/website/news_item.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,11 @@
{% load thumbnail %}
{% load ml_tags %}

{# Title, description, og:type=article, and canonical come from page_meta (see
website/views/news_item.py). Here we override the share image and add the
article-specific structural OG tags. #}
{% comment %}
Title, description, og:type=article, and canonical come from page_meta (see
website/views/news_item.py). Here we override the share image and add the
article-specific structural OG tags.
{% endcomment %}
{% block social_image %}
{% if news_item.image %}{% thumbnail news_item.image '1200x630' box=news_item.cropping crop=True upscale=True as og_img %}{% endif %}
{% if og_img %}
Expand Down
6 changes: 4 additions & 2 deletions website/templates/website/project.html
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,10 @@
{% load thumbnail %}
{% load ml_tags %}

{# Title, description, og:type, and canonical come from page_meta (see
website/views/project.py). Here we only override the social share image. #}
{% comment %}
Title, description, og:type, and canonical come from page_meta (see
website/views/project.py). Here we only override the social share image.
{% endcomment %}
{% block social_image %}
{% thumbnail project.gallery_image 1200x630 box=project.cropping crop=True upscale=True as og_img %}
{% if og_img %}
Expand Down
65 changes: 65 additions & 0 deletions website/tests/test_template_comments.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"""
Mechanical guard for a recurring bug: Django's ``{# … #}`` template comment is
**single-line only**. A ``{# … #}`` that spans multiple lines is NOT tokenized as
a comment — Django renders the whole thing (text and ``#}`` included) as visible
page content. This shipped to prod once, printing a note on every award card
(fixed in 2.14.2). For multi-line comments use ``{% comment %} … {% endcomment %}``.

This is a ``SimpleTestCase`` (no DB), so it runs as part of the normal test suite
in CI (.github/workflows/test.yml) on every push/PR, and locally via
``manage.py test``. See the template-comment note in CLAUDE.md.
"""

from pathlib import Path

from django.test import SimpleTestCase

# Repo root: website/tests/this_file.py -> parents[2].
REPO_ROOT = Path(__file__).resolve().parents[2]
SKIP_DIRS = {".git", "node_modules", "static", "staticfiles", "media",
".venv", "venv", "htmlcov", "__pycache__"}


def _template_html_files():
"""Yield every ``*.html`` living under any ``templates/`` directory in the repo."""
for path in REPO_ROOT.rglob("*.html"):
if "templates" not in path.parts:
continue
if SKIP_DIRS.intersection(path.parts):
continue
yield path


def _multiline_comment_lines(text):
"""Return the 1-based line numbers where a ``{#`` opens but doesn't close on
the same line (i.e. a multi-line ``{# #}`` comment)."""
bad = []
for n, line in enumerate(text.splitlines(), 1):
idx = line.find("{#")
if idx != -1 and "#}" not in line[idx + 2:]:
bad.append(n)
return bad


class TemplateCommentLintTests(SimpleTestCase):
def test_no_multiline_django_comments(self):
offenders = []
for path in _template_html_files():
text = path.read_text(encoding="utf-8")
for n in _multiline_comment_lines(text):
offenders.append(f"{path.relative_to(REPO_ROOT)}:{n}")

self.assertEqual(
offenders, [],
"Multi-line Django `{# #}` comment(s) found — Django renders these as "
"visible page text. Use `{% comment %}…{% endcomment %}` instead:\n "
+ "\n ".join(offenders),
)

def test_detector_catches_a_multiline_comment(self):
# Pin the detection logic itself so the guard can't silently rot.
self.assertEqual(_multiline_comment_lines("{# one line ok #}\n<p>x</p>"), [])
self.assertEqual(
_multiline_comment_lines("<p>x</p>\n{# this opens\n and closes later #}\n"),
[2],
)
Loading