From 75d22f618b22de6d26ed66bd9a15bf7a66e97af9 Mon Sep 17 00:00:00 2001 From: Dennis Fink Date: Fri, 21 Aug 2026 13:13:23 +0200 Subject: feat(sender): paginate sent webmention details Load sent Webmentions with database-backed pagination instead of eagerly loading the full relationship for each source. Condense delivery, revision, and activity metadata in the source detail table and add pagination controls for large Webmention sets. --- webmentions_ssg/models.py | 2 +- webmentions_ssg/templates/sent_source.html | 220 +++++++++++++++-------------- webmentions_ssg/views.py | 13 +- 3 files changed, 123 insertions(+), 112 deletions(-) diff --git a/webmentions_ssg/models.py b/webmentions_ssg/models.py index 5d22395..73805de 100644 --- a/webmentions_ssg/models.py +++ b/webmentions_ssg/models.py @@ -167,7 +167,7 @@ class Source(Base): ) sent_webmentions: Mapped[list[SentWebmention]] = relationship( - back_populates="source", cascade="all, delete-orphan", lazy="selectin" + back_populates="source", cascade="all, delete-orphan" ) def __repr__(self) -> str: diff --git a/webmentions_ssg/templates/sent_source.html b/webmentions_ssg/templates/sent_source.html index 77376b5..db8f898 100644 --- a/webmentions_ssg/templates/sent_source.html +++ b/webmentions_ssg/templates/sent_source.html @@ -4,6 +4,7 @@ SPDX-FileCopyrightText: 2026 Dennis Fink SPDX-License-Identifier: BSD-3-Clause --> {% extends "base.html" %} +{% from "bootstrap5/pagination.html" import render_pagination %} {% block content %}
← Back to sources @@ -65,100 +66,102 @@ SPDX-License-Identifier: BSD-3-Clause

Sent Webmentions

- {{ webmentions | length }} webmentions + {{ webmentions.item | length }} webmentions
- {% if webmentions %} -
- - - - - - - - - - - - - - - - - - - - {% for webmention in webmentions %} - {% if webmention.status is none %} - {% set row_class = "table-info" %} - {% set badge_class = "text-bg-info" %} - {% elif webmention.status.value == "sent" %} - {% set row_class = "table-success" %} - {% set badge_class = "text-bg-success" %} - {% elif webmention.status.value == "unsupported" %} - {% set row_class = "table-warning" %} - {% set badge_class = "text-bg-warning" %} - {% elif webmention.status.value == "failed" %} - {% set row_class = "table-danger" %} - {% set badge_class = "text-bg-danger" %} - {% else %} - {% set row_class = "table-info" %} - {% set badge_class = "text-bg-info" %} - {% endif %} - - - - - - + + + {% endfor %} + +
TargetStatusActiveDesired revisionProcessed revisionSent revisionFailure ReasonEndpointResponse StatusStatus URLLast attempted atLast sent atActions
- {{ webmention.target }} - - {% if webmention.status is none %} - Pending - {% else %} - {{ webmention.status.value.title() }} - {% endif %} - - {% if webmention.active %} - Active - {% else %} - Removed - {% endif %} - {{ webmention.desired_revision }} + {% if webmentions.items %} + + + + + + + + + + + + + + {% for webmention in webmentions %} + {% if webmention.status is none %} + {% set row_class = "table-info" %} + {% set badge_class = "text-bg-info" %} + {% elif webmention.status.value == "sent" %} + {% set row_class = "table-success" %} + {% set badge_class = "text-bg-success" %} + {% elif webmention.status.value == "unsupported" %} + {% set row_class = "table-warning" %} + {% set badge_class = "text-bg-warning" %} + {% elif webmention.status.value == "failed" %} + {% set row_class = "table-danger" %} + {% set badge_class = "text-bg-danger" %} + {% else %} + {% set row_class = "table-info" %} + {% set badge_class = "text-bg-info" %} + {% endif %} + + + + + - - - - - - + + - - - - {% endfor %} - -
TargetStatusActiveRevisionsDeliveryLast activityActions
+ {{ webmention.target }} + + {% if webmention.status is none %} + Pending + {% else %} + {{ webmention.status.value.title() }} + {% endif %} + {% if webmention.failure_reason is not none %} +
{{ webmention.failure_reason }}
+ {% endif %} +
+ {% if webmention.active %} + Active + {% else %} + Removed + {% endif %} + +
+ Desired: + {{ webmention.desired_revision }} +
+
+ Processed: {{ webmention.processed_revision if webmention.processed_revision is not none else "—" }} -
+ +
+ Sent: {{ webmention.sent_revision if webmention.sent_revision is not none else "—" }} -
- {{ webmention.failure_reason - if webmention.failure_reason is not none - else "—" }} - - {% if webmention.endpoint is not none %} - {{ webmention.endpoint }} - {% else %} - — - {% endif %} - - {{ webmention.response_status - if webmention.response_status is not none - else "—" }} - - {% if webmention.status_url is not none %} - {{ webmention.status_url }} - {% else %} - — - {% endif %} - + + + {% if webmention.endpoint is not none %} +
+ Endpoint: + {{ webmention.endpoint }} +
+ {% endif %} + {% if webmention.response_status is not none %} +
+ HTTP: + {{ webmention.response_status }} +
+ {% endif %} + {% if webmention.status_url is not none %} + + {% endif %} +
+
+ Attempted: +
{% if webmention.last_attempted_at is not none %}
+ +
+ Sent: +
{% if webmention.last_sent_at is not none %}
- -
- + +
+ +
+ {% if webmentions.pages > 1 %} +
{{ render_pagination(webmentions) }}
+ {% endif %} {% include "includes/confirmation_modal.html" %} {% else %}

No Webmentions have been discovered for this source.

diff --git a/webmentions_ssg/views.py b/webmentions_ssg/views.py index 2235735..543a44c 100644 --- a/webmentions_ssg/views.py +++ b/webmentions_ssg/views.py @@ -220,16 +220,19 @@ def sent_source(source_identifier: uuid.UUID) -> ResponseReturnValue: :return: Rendered source details page. """ source = db.session.scalar( - sa.select(Source) - .options(selectinload(Source.sent_webmentions)) - .where(Source.uuid == source_identifier) + sa.select(Source).where(Source.uuid == source_identifier) ) + if source is None: return abort(404) - webmentions = sorted( - source.sent_webmentions, key=lambda webmention: webmention.target + webmentions = db.paginate( + sa.select(SentWebmention) + .where(SentWebmention.source_id == source.uuid) + .order_by(SentWebmention.target, SentWebmention.uuid), + per_page=25, ) + return render_template( "sent_source.html", title="Sent Webmentions", -- cgit v1.3.1