From e38893109b9d82ad7f063100f5f4a218e260f674 Mon Sep 17 00:00:00 2001 From: Brian Glass Date: Tue, 4 Aug 2026 15:10:06 -0400 Subject: [PATCH] Default readings page to LXX2012+WEB; add translation-aware RSS feed The readings/landing page now defaults to the modern LXX2012+WEB pairing instead of KJV (remember_translation in calendarium/views.py). The API and RSS feeds are unaffected -- neither calls that function, so translation=None still resolves to kjv via bible.models.DEFAULT_TRANSLATIONS as before. Adds a new RSS feed URL, /api/feed////, for subscribing to the modern translation specifically. While verifying the new feed, found and fixed a real pre-existing bug: ReadingsFeed.items() never passed fetch_content=True to day.get_readings(), and feed_description.html called reading.pericope.get_passage (a method that re-queries fresh with its own kjv-defaulting arguments) instead of .passage (the cached, correctly-translated attribute) -- so any explicit translation was silently discarded and every feed rendered KJV regardless of what was requested. Had zero test coverage before; added a regression test confirming the two feed URLs actually differ in passage content, not just title/description text. Also adds Day.translation_label as a reusable property, replacing a similarly-hardcoded "(KJV)" heading in the feed description template and simplifying readings_view's own translation label lookup. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01Hf6j2xXQXywHVh3HAVRxB3 --- calendarium/api_urls.py | 1 + calendarium/feeds.py | 21 ++++++++++++++------- calendarium/liturgics/day.py | 11 +++++++++++ calendarium/templates/feed_description.html | 4 ++-- calendarium/tests/test_feeds.py | 19 +++++++++++++++++++ calendarium/views.py | 12 +++++++----- orthocal/templates/feeds.html | 5 +++++ 7 files changed, 59 insertions(+), 14 deletions(-) diff --git a/calendarium/api_urls.py b/calendarium/api_urls.py index cd04cd0..df2166b 100644 --- a/calendarium/api_urls.py +++ b/calendarium/api_urls.py @@ -15,4 +15,5 @@ path('feed/', cache(ReadingsFeed()), name='rss-feed'), path('feed//', cache(ReadingsFeed()), name='rss-feed-cal'), path('feed///', cache(ReadingsFeed()), name='rss-feed-cal'), + path('feed////', cache(ReadingsFeed()), name='rss-feed-cal'), ] diff --git a/calendarium/feeds.py b/calendarium/feeds.py index ea134d9..639b0d6 100644 --- a/calendarium/feeds.py +++ b/calendarium/feeds.py @@ -9,7 +9,7 @@ from django.utils.feedgenerator import Rss201rev2Feed from . import liturgics -from .datetools import Calendar, Tradition +from .datetools import Calendar, Tradition, TRANSLATION_LABELS class WSRssFeed(Rss201rev2Feed): @@ -27,22 +27,29 @@ class ReadingsFeed(Feed): description_template = 'feed_description.html' item_categories = categories = 'orthodox', 'christian', 'religion' - def get_object(self, request, cal=Calendar.Gregorian, tradition=Tradition.Slavic): - return {'cal': cal, 'tradition': tradition} + def get_object(self, request, cal=Calendar.Gregorian, tradition=Tradition.Slavic, translation=None): + return {'cal': cal, 'tradition': tradition, 'translation': translation} def title(self, obj): - return f'Orthodox Daily Readings ({obj["tradition"].title()}, {obj["cal"].title()})' + title = f'Orthodox Daily Readings ({obj["tradition"].title()}, {obj["cal"].title()})' + if obj['translation']: + title += f' [{TRANSLATION_LABELS[obj["translation"]]}]' + return title def description(self, obj): - return (f'Daily readings from scripture and the lives of the saints according to the ' - f'{obj["tradition"].title()} tradition, {obj["cal"].title()} calendar.') + description = (f'Daily readings from scripture and the lives of the saints according to the ' + f'{obj["tradition"].title()} tradition, {obj["cal"].title()} calendar.') + if obj['translation']: + description += f' Scripture from the {TRANSLATION_LABELS[obj["translation"]]}.' + return description def items(self, obj): now = timezone.localtime() start_dt = now - timedelta(days=10) for dt in rrule(DAILY, dtstart=start_dt, until=now): - day = liturgics.Day(dt.year, dt.month, dt.day, calendar=obj['cal'], tradition=obj['tradition']) + day = liturgics.Day(dt.year, dt.month, dt.day, calendar=obj['cal'], tradition=obj['tradition'], translation=obj['translation']) day.initialize() + day.get_readings(fetch_content=True) yield day def item_pubdate(self, day): diff --git a/calendarium/liturgics/day.py b/calendarium/liturgics/day.py index f2138ca..abe1c4a 100644 --- a/calendarium/liturgics/day.py +++ b/calendarium/liturgics/day.py @@ -139,6 +139,17 @@ async def ainitialize(self): def __str__(self): return str(self.date) + @cached_property + def translation_label(self): + """Human-readable label for the Bible translation actually in effect + -- resolves the per-language default the same way VerseManager does, + since self.translation is often None (meaning "use the default") + rather than always holding a concrete value.""" + + from bible.models import DEFAULT_TRANSLATIONS + + return datetools.TRANSLATION_LABELS[self.translation or DEFAULT_TRANSLATIONS[self.language]] + @cached_property def summary_title(self): """A simplified title that summarizes the day's commemorations.""" diff --git a/calendarium/templates/feed_description.html b/calendarium/templates/feed_description.html index bfd3d44..7489d41 100644 --- a/calendarium/templates/feed_description.html +++ b/calendarium/templates/feed_description.html @@ -35,7 +35,7 @@

Commemorations

{% endif %} -

Scripture Readings (KJV)

+

Scripture Readings ({{ obj.translation_label }})

{% for reading in obj.get_readings %}
@@ -47,7 +47,7 @@

- {% for verse in reading.pericope.get_passage %} + {% for verse in reading.pericope.passage %} {% if verse.paragraph_start and not forloop.first %}

{% endif %} {{ verse.verse }} {{ verse.content }} {% endfor %} diff --git a/calendarium/tests/test_feeds.py b/calendarium/tests/test_feeds.py index 9d09bd9..8df474b 100644 --- a/calendarium/tests/test_feeds.py +++ b/calendarium/tests/test_feeds.py @@ -1,5 +1,7 @@ import re +from freezegun import freeze_time + from django.test import TestCase from django.urls import reverse @@ -42,3 +44,20 @@ def test_title_distinguishes_tradition(self): slavic_title = re.search(r'(.*?)', self.client.get(slavic_url).content.decode('utf-8')).group(1) greek_title = re.search(r'(.*?)', self.client.get(greek_url).content.decode('utf-8')).group(1) self.assertNotEqual(slavic_title, greek_title) + + @freeze_time('2026-07-25 12:00:00') # noon UTC stays July 25 in America/Los_Angeles too + def test_translation_changes_passage_content(self): + """A regression test: the feed's items() didn't pass fetch_content=True + to get_readings(), and feed_description.html called the get_passage() + method (fresh query, its own kjv-defaulting args) instead of the + passage attribute -- so an explicit translation was silently ignored + and every feed rendered KJV regardless of what was requested.""" + kjv_url = reverse('rss-feed-cal', kwargs={'tradition': Tradition.Slavic, 'cal': Calendar.Gregorian}) + lxx_url = reverse('rss-feed-cal', kwargs={'tradition': Tradition.Slavic, 'cal': Calendar.Gregorian, 'translation': 'lxx2012-web'}) + + kjv_body = self.client.get(kjv_url).content.decode('utf-8') + lxx_body = self.client.get(lxx_url).content.decode('utf-8') + + self.assertIn('subject unto the higher powers', kjv_body) + self.assertIn('in subjection to the higher authorities', lxx_body) + self.assertNotIn('subject unto the higher powers', lxx_body) diff --git a/calendarium/views.py b/calendarium/views.py index 35b305b..702d0d3 100644 --- a/calendarium/views.py +++ b/calendarium/views.py @@ -12,8 +12,6 @@ from django.urls import reverse from django.utils import timezone -from bible.models import DEFAULT_TRANSLATIONS - from . import liturgics, models from .datetools import Calendar, Tradition, Translation, TRANSLATION_LABELS, cal_session_key, translation_session_key @@ -50,7 +48,7 @@ async def readings_view(request, cal=None, tradition=None, translation=None, yea 'cal': cal, 'tradition': tradition, 'translation': translation, - 'translation_label': TRANSLATION_LABELS[translation or DEFAULT_TRANSLATIONS[request.LANGUAGE_CODE]], + 'translation_label': day.translation_label, # Only the selectable (English) translations, not every code that can # appear in Verse rows -- ro/sr each have one fixed translation with # no dropdown, so rccv/srp1865 aren't offered as choices here. @@ -166,14 +164,18 @@ def remember_translation(request, translation, language): session_key = translation_session_key(language) if translation: - if translation != request.session.get(session_key, Translation.KJV): + if translation != request.session.get(session_key, Translation.LXX2012WEB): request.session[session_key] = translation # Don't send vary on cookie header when we have an explicit translation. # In this case, the session does not actually impact the content. request.session.accessed = False else: - translation = request.session.get(session_key, Translation.KJV) + # Only the readings page defaults to the modern translation -- the + # API and RSS feeds are unaffected, since neither calls this function; + # they resolve translation=None straight through to + # bible.models.DEFAULT_TRANSLATIONS['en'] (kjv), unchanged. + translation = request.session.get(session_key, Translation.LXX2012WEB) return translation diff --git a/orthocal/templates/feeds.html b/orthocal/templates/feeds.html index 8ba2c8f..f4d16e1 100644 --- a/orthocal/templates/feeds.html +++ b/orthocal/templates/feeds.html @@ -25,6 +25,11 @@

RSS

{% fullurl "rss-feed-cal" tradition="greek" cal="gregorian" %}
{% fullurl "rss-feed-cal" tradition="greek" cal="julian" %}
+

Every RSS feed above reads from the King James Version by default. For the more modern LXX2012+WEB + pairing instead, add the translation to the URL:

+ +
{% fullurl "rss-feed-cal" tradition="slavic" cal="gregorian" translation="lxx2012-web" %}
+

iCal

An ical feed for the Slavic tradition, new calendar is available at: