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
1 change: 1 addition & 0 deletions apps/pages/templatetags/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Template tags for the pages app."""
39 changes: 39 additions & 0 deletions apps/pages/templatetags/pages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""Template filters for rendering CMS page content."""

import re
from collections import Counter

from dateutil import parser as date_parser
from django import template
from django.utils.html import mark_safe

register = template.Library()

_MONTH = r"(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[a-z]*\.?"
# A board resolution is a <blockquote> followed by a paragraph with its vote date, e.g.
# "Approved; 9-0-1, 2026-08-12", "Declined 3-4-0, December 2, 2014", "September 4, 2018".
_RESOLUTION_RE = re.compile(
r"<blockquote>(?P<body>(?:(?!<blockquote>).)*?</blockquote>\s*<p>[^<]*?)"
rf"(?P<date>\d{{4}}-\d\d-\d\d|{_MONTH}\s+\d{{1,2}},?\s+\d{{4}}|\d{{1,2}}\s+{_MONTH},?\s+\d{{4}})",
re.DOTALL,
)


@register.filter
def resolution_anchors(content):
"""Anchor each board resolution at its vote date and turn that date into a link to it.

Ids are ISO dates; later resolutions on the same date get ``-2``, ``-3``, ... in page order.
"""
seen = Counter()

def anchor(match):
Comment thread
JacobCoffee marked this conversation as resolved.
try:
date = date_parser.parse(match["date"]).date().isoformat()
except ValueError:
return match[0]
seen[date] += 1
anchor_id = date if seen[date] == 1 else f"{date}-{seen[date]}"
return f'<blockquote id="{anchor_id}">{match["body"]}<a href="#{anchor_id}">{match["date"]}</a>'

return mark_safe(_RESOLUTION_RE.sub(anchor, str(content))) # noqa: S308
34 changes: 34 additions & 0 deletions apps/pages/tests/test_templatetags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from django.test import SimpleTestCase

from apps.pages.templatetags.pages import resolution_anchors


class ResolutionAnchorsTests(SimpleTestCase):
def test_ids_from_vote_dates(self):
"""Each resolution is keyed by its vote date, numbered in page order when a date repeats."""
html = (
"<blockquote>A</blockquote>\n<p>Approved; 9-0-1, 2026-08-12</p>\n"
"<blockquote>B</blockquote>\n<p>Approved; 9-0-0, 2026-08-12</p>\n"
"<blockquote>C</blockquote>\n<p>Declined 3-4-0, December 2, 2014</p>\n"
"<blockquote>D</blockquote>\n<p>Approved, 10-0-0 by IRC vote, 8 June 2015.</p>\n"
"<blockquote>E</blockquote>\n<p>September 4, 2018</p>"
)
self.assertHTMLEqual(
resolution_anchors(html),
'<blockquote id="2026-08-12">A</blockquote><p>Approved; 9-0-1, <a href="#2026-08-12">2026-08-12</a></p>'
'<blockquote id="2026-08-12-2">B</blockquote><p>Approved; 9-0-0, <a href="#2026-08-12-2">2026-08-12</a></p>'
'<blockquote id="2014-12-02">C</blockquote><p>Declined 3-4-0, <a href="#2014-12-02">December 2, 2014</a></p>'
'<blockquote id="2015-06-08">D</blockquote>'
'<p>Approved, 10-0-0 by IRC vote, <a href="#2015-06-08">8 June 2015</a>.</p>'
'<blockquote id="2018-09-04">E</blockquote><p><a href="#2018-09-04">September 4, 2018</a></p>',
)

def test_unmatched_content_unchanged(self):
"""Paragraphs after a quote without a full date, and dated paragraphs not after a quote, are left alone."""
html = (
"<blockquote>A</blockquote><p>Approved, 10-0-0, May 2010.</p>"
"<blockquote>B</blockquote><p>with:</p>"
"<p>Approved; 9-0-0, 2026-08-12</p>"
"<blockquote>C</blockquote><p>Approved 5-0-0, Feb 30, 2014</p>"
)
self.assertHTMLEqual(resolution_anchors(html), html)
3 changes: 2 additions & 1 deletion templates/psf/default.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
{% extends "base.html" %}
{% load boxes %}
{% load banners %}
{% load pages %}

{# TODO: Try to deduplicate this and templates/pages/default.html. #}
{% block page_title %}{{ page.title }} | Python Software Foundation{% endblock %}
Expand Down Expand Up @@ -52,7 +53,7 @@
<h1 class="page-title">{{ page.title }}</h1>
</header>

{{ page.content }}
{{ page.content|resolution_anchors }}

</article>
{% endblock content %}
Expand Down
Loading