From b303b4c1615b9dc8968c61077df6b8f53965855f Mon Sep 17 00:00:00 2001
From: Christoph Berg <myon@debian.org>
Date: Fri, 16 Oct 2026 15:33:29 +0200
Subject: [PATCH v14 02/12] Determine "has image" without full detoasting

The with_has_image function claimed it wouldn't transfer the whole
image, but it was still fully reading it on the database side. Switch to
looking at the length only.
---
 pgweb/contributors/models.py | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/pgweb/contributors/models.py b/pgweb/contributors/models.py
index 62d5c3a0..e02a896d 100644
--- a/pgweb/contributors/models.py
+++ b/pgweb/contributors/models.py
@@ -4,6 +4,8 @@ import re
 from django.core.exceptions import ValidationError
 from django.db import models
 from django.db.models import Case, When
+from django.db.models.functions import Length
+from django.db.models.lookups import GreaterThan
 from django.contrib.auth.models import User
 from django.template.defaultfilters import slugify
 from django.utils.safestring import mark_safe
@@ -87,10 +89,11 @@ class BadgeManager(models.Manager):
 
     def with_has_image(self):
         # Annotate the image-fallback decision so views and templates can
-        # avoid touching imagedata. The expression is evaluated SQL-side,
-        # so the (up to 1 MB) value itself is never transferred.
+        # avoid touching imagedata at all. LENGTH() is computed from the
+        # TOAST header, whereas comparing the value (imagedata > '') would
+        # first have to fetch and reassemble the whole (up to 1 MB) field.
         return self.get_queryset().annotate(has_image=Case(
-            When(imagedata__gt=b'', then=True),
+            When(GreaterThan(Length('imagedata'), 0), then=True),
             default=False,
             output_field=models.BooleanField(),
         ))
-- 
2.53.0

