From f70020873cb54b1b1753cedb3569445009d33a7f Mon Sep 17 00:00:00 2001 From: Kevin Rocker Date: Sat, 12 Sep 2026 12:50:55 +0200 Subject: [PATCH v2 2/2] Offer a daily mbox download on the per-day archive pages --- .../mailarchives/templates/datelist.html | 4 +- django/archives/mailarchives/views.py | 64 +++++++++++++------ django/archives/urls.py | 3 +- django/media/css/main.css | 19 ++++++ 4 files changed, 69 insertions(+), 21 deletions(-) diff --git a/django/archives/mailarchives/templates/datelist.html b/django/archives/mailarchives/templates/datelist.html index df0fb50..12dcc76 100644 --- a/django/archives/mailarchives/templates/datelist.html +++ b/django/archives/mailarchives/templates/datelist.html @@ -11,13 +11,15 @@ {%include "datelist_topandbottom.html"%} +
+ {%for m in messages%} {%ifchanged m.date.date%} {%if not forloop.first%} {%endif%} -

{{m.date.date}}

+

{{m.date.date}}Download mbox

diff --git a/django/archives/mailarchives/views.py b/django/archives/mailarchives/views.py index c034f90..97a036f 100644 --- a/django/archives/mailarchives/views.py +++ b/django/archives/mailarchives/views.py @@ -14,7 +14,7 @@ import copy import re import os import base64 -from datetime import datetime, timedelta, date +from datetime import datetime, timedelta import calendar import email.parser import email.policy @@ -744,23 +744,13 @@ def message_mbox(request, msgid): msgid, filename='%s.mbox' % quote(msgid, safe='')) -@csrf_exempt -@nocache -@antispam_auth -def mbox(request, listname, listname2, mboxyear, mboxmonth): - if (listname != listname2): - raise Http404('List name mismatch') - l = get_object_or_404(List, listname=listname) - ensure_list_permissions(request, l) - - mboxyear = int(mboxyear) - mboxmonth = int(mboxmonth) - - query = "SELECT messageid, rawtxt FROM messages m INNER JOIN list_threads t ON t.threadid=m.threadid WHERE listid=%(listid)s AND hiddenstatus IS NULL AND date >= %(startdate)s AND date <= %(enddate)s %%% ORDER BY date" +def _list_mbox(request, l, startdate, enddate, filename): + # NOTE! Basic permissions checks must be done before calling this function! + query = "SELECT messageid, rawtxt FROM messages m INNER JOIN list_threads t ON t.threadid=m.threadid WHERE listid=%(listid)s AND hiddenstatus IS NULL AND date >= %(startdate)s AND date < %(enddate)s %%% ORDER BY date" params = { 'listid': l.listid, - 'startdate': date(mboxyear, mboxmonth, 1), - 'enddate': datetime(mboxyear, mboxmonth, calendar.monthrange(mboxyear, mboxmonth)[1], 23, 59, 59), + 'startdate': startdate, + 'enddate': enddate, } if not settings.PUBLIC_ARCHIVES and not request.user.is_superuser: @@ -770,11 +760,47 @@ def mbox(request, listname, listname2, mboxyear, mboxmonth): else: # Just return the whole thing query = query.replace('%%%', '') + return _build_mbox(query, params, filename=filename) + + +@csrf_exempt +@nocache +@antispam_auth +def mbox(request, listname, listname2, mboxyear, mboxmonth): + if (listname != listname2): + raise Http404('List name mismatch') + l = get_object_or_404(List, listname=listname) + ensure_list_permissions(request, l) + + try: + startdate = datetime(int(mboxyear), int(mboxmonth), 1) + except ValueError: + raise Http404('Invalid date') + enddate = startdate + timedelta(days=calendar.monthrange(startdate.year, startdate.month)[1]) + # Set a per-list, per-month filename (matching the URL scheme, # listname.YYYYMM) so downloads don't all collide on the same name. - return _build_mbox( - query, params, - filename='%s.%04d%02d.mbox' % (listname, mboxyear, mboxmonth)) + return _list_mbox(request, l, startdate, enddate, + '%s.%s.mbox' % (listname, startdate.strftime('%Y%m'))) + + +@csrf_exempt +@nocache +@antispam_auth +def mbox_day(request, listname, listname2, mboxyear, mboxmonth, mboxday): + if (listname != listname2): + raise Http404('List name mismatch') + l = get_object_or_404(List, listname=listname) + ensure_list_permissions(request, l) + + try: + startdate = datetime(int(mboxyear), int(mboxmonth), int(mboxday)) + except ValueError: + raise Http404('Invalid date') + enddate = startdate + timedelta(days=1) + + return _list_mbox(request, l, startdate, enddate, + '%s.%s.mbox' % (listname, startdate.strftime('%Y%m%d'))) @transaction.atomic diff --git a/django/archives/urls.py b/django/archives/urls.py index e811b53..cbcb4d8 100644 --- a/django/archives/urls.py +++ b/django/archives/urls.py @@ -40,7 +40,8 @@ urlpatterns = [ re_path(r'^message-id/legacy/([\w-]+)/(\d+)-(\d+)/msg(\d+).php$', archives.mailarchives.views.legacy), re_path(r'^message-id/(.+)$', archives.mailarchives.views.message), - re_path(r'^list/([\w-]+)/mbox/([\w-]+)\.(\d{4})(\d{2})', archives.mailarchives.views.mbox), + re_path(r'^list/([\w-]+)/mbox/([\w-]+)\.(\d{4})(\d{2})$', archives.mailarchives.views.mbox), + re_path(r'^list/([\w-]+)/mbox/([\w-]+)\.(\d{4})(\d{2})(\d{2})$', archives.mailarchives.views.mbox_day), # Search re_path(r'^archives-search/', archives.mailarchives.views.search), diff --git a/django/media/css/main.css b/django/media/css/main.css index f7d6646..d750bf9 100644 --- a/django/media/css/main.css +++ b/django/media/css/main.css @@ -99,6 +99,25 @@ h1 a, h2 a, h3 a, h4 a, h5 a, h6 a { color: inherit; } +/* Day heading with a plain link between the date and the trailing rule */ +h2.datelist-day > span, h2.datelist-day > a { + align-self: baseline; +} + +h2.datelist-day > a { + font-family: 'Open Sans', sans-serif; + font-size: 11.5pt; + font-weight: 600; + text-transform: none; + white-space: nowrap; + color: #336791; + margin-left: 1em; +} + +h2.datelist-day > a:hover { + color: #193145; +} + a, .news a { color: #336791; font-weight: 600; -- 2.54.0