diff --git a/calendarium/liturgics/day.py b/calendarium/liturgics/day.py index 51acc0d..23b0118 100644 --- a/calendarium/liturgics/day.py +++ b/calendarium/liturgics/day.py @@ -5,6 +5,7 @@ from asgiref.sync import async_to_sync from django.db.models import Q from django.utils.functional import cached_property +from django.utils.html import strip_tags from .. import datetools, models from ..datetools import Calendar, Tradition, Weekday, FastLevels, FastLevelDesc, FastExceptions, FeastLevels, FloatIndex @@ -62,6 +63,15 @@ def _prefer_tradition(rows, tradition): return kept +def _has_story(dc): + """Whether dc.story has actual visible text, not just empty markup like + '
' -- a handful of rows have exactly that, which is truthy as a + plain Python string but renders as nothing, incorrectly earning a + clickable title and an empty entry in the story panel.""" + + return bool(strip_tags(dc.story or '').strip()) + + def _speech_worthy(dc): """Should the Alexa skill (alexa/speech.py) say this commemoration's name? @@ -71,7 +81,7 @@ def _speech_worthy(dc): whether it has a story, matching this project's behavior before that overlay existed.""" - return bool(dc.story) or dc.tradition != 'greek' + return _has_story(dc) or dc.tradition != 'greek' def _prefer_tradition_days(rows, tradition): @@ -334,11 +344,11 @@ async def _add_supplemental_commemorations(self): # feast-level-facts preference for this tradition. titles = [dc.title for dc in dcs] self.saints.extend(titles) - self.saint_links.extend((dc.title, dc.id if dc.story else None) for dc in dcs) + self.saint_links.extend((dc.title, dc.id if _has_story(dc) else None) for dc in dcs) self.spoken_saints.extend(dc.title for dc in dcs if _speech_worthy(dc)) self.saints.extend(dc.title for dc in additive) - self.saint_links.extend((dc.title, dc.id if dc.story else None) for dc in additive) + self.saint_links.extend((dc.title, dc.id if _has_story(dc) else None) for dc in additive) self.spoken_saints.extend(dc.title for dc in additive if _speech_worthy(dc)) # A length-capped view of self.saints for space-constrained displays @@ -364,7 +374,7 @@ async def _add_supplemental_commemorations(self): if not self.spoken_saints: self.spoken_saints = list(self.saints) - self.stories = [dc for dc in commemorations if dc.story] + self.stories = [dc for dc in commemorations if _has_story(dc)] def _apply_fasting_adjustments(self): """Tradition-specific -- see SlavicDay/GreekDay.""" diff --git a/calendarium/tests/test_liturgics.py b/calendarium/tests/test_liturgics.py index 6c3c3ad..0262aac 100644 --- a/calendarium/tests/test_liturgics.py +++ b/calendarium/tests/test_liturgics.py @@ -1,9 +1,11 @@ from datetime import date +from types import SimpleNamespace from django.test import TestCase from .. import datetools, liturgics, models from ..datetools import Tradition, Translation +from ..liturgics.day import _has_story from bible.models import Verse @@ -634,6 +636,25 @@ def test_leavetaking_theophany_weekday_float(self): self.assertNotIn(leavetaking_sunday, sunday_year.floats) +class TestHasStory(TestCase): + """A handful of DayCommemoration.story rows are exactly '' -- + non-empty and non-None, so truthy as a plain Python string, but with no + visible text once rendered. _has_story exists so those rows don't earn a + clickable commemoration title or an empty entry in the story panel (see + day.saint_links and day.stories).""" + + def test_none_and_empty_are_not_a_story(self): + self.assertFalse(_has_story(SimpleNamespace(story=None))) + self.assertFalse(_has_story(SimpleNamespace(story=''))) + + def test_empty_markup_is_not_a_story(self): + self.assertFalse(_has_story(SimpleNamespace(story=''))) + self.assertFalse(_has_story(SimpleNamespace(story='\n'))) + + def test_real_content_is_a_story(self): + self.assertTrue(_has_story(SimpleNamespace(story='
He was a deacon.
'))) + + class TestDay(TestCase): fixtures = ['calendarium.json', 'commemorations.json'] diff --git a/fixtures/commemorations.json b/fixtures/commemorations.json index be6deae..f536c16 100644 --- a/fixtures/commemorations.json +++ b/fixtures/commemorations.json @@ -23342,7 +23342,7 @@ "day": 560, "saint": 5740, "title": "St Monica, mother of Blessed Augustine (388)", - "story": "", + "story": null, "rank": 0, "high_rank": false, "new_style": false, @@ -23758,7 +23758,7 @@ "day": 579, "saint": 5766, "title": "Holy Myrrh-bearer Mary, wife of Cleopas", - "story": "", + "story": null, "rank": 0, "high_rank": false, "new_style": false, @@ -24558,7 +24558,7 @@ "day": 611, "saint": 5816, "title": "Synaxis of Sts Zacharias and Elisabeth", - "story": "", + "story": null, "rank": 0, "high_rank": false, "new_style": false, @@ -25198,7 +25198,7 @@ "day": 640, "saint": 5856, "title": "Righteous Anna (Hannah), mother of the prophet Samuel Righteous Anna (Hannah), mother of the prophet Samuel", - "story": "", + "story": null, "rank": 0, "high_rank": false, "new_style": false, @@ -25582,7 +25582,7 @@ "day": 659, "saint": 5880, "title": "Repose of Fr Georges Florovsky (1979) (July 29 OC)", - "story": "", + "story": null, "rank": 0, "high_rank": false, "new_style": true, @@ -25854,7 +25854,7 @@ "day": 676, "saint": 5898, "title": "Righteous Anna the Prophetess (1st c.)", - "story": "", + "story": null, "rank": 0, "high_rank": false, "new_style": false, @@ -25870,7 +25870,7 @@ "day": 676, "saint": 5899, "title": "Righteous Hezekiah, King of Judah (691 BC)", - "story": "", + "story": null, "rank": 0, "high_rank": false, "new_style": false, @@ -25886,7 +25886,7 @@ "day": 676, "saint": 5900, "title": "New Hieromartyr Chrysostom, metropolitan of Smyrna (1922)", - "story": "", + "story": null, "rank": 0, "high_rank": false, "new_style": false, @@ -29214,7 +29214,7 @@ "day": 662, "saint": 6112, "title": "Translation of the Relics of St Theodosius of the Kiev Caves", - "story": "", + "story": null, "rank": 0, "high_rank": true, "new_style": false,