From d8f7df33940f18e75e7a2dbf306e19e59fd641fe Mon Sep 17 00:00:00 2001 From: Samson Nkrumah Date: Thu, 9 Apr 2026 22:26:52 +0100 Subject: [PATCH 1/3] fix: annotate conference list with has_errors, has_warnings, participants_count MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After expand_fields was removed (PR #19), the conference list no longer includes participants or issues arrays. This broke the dashboard charts for error/warning icons and participant counts. Adds lightweight annotations via Exists() subqueries and Count() so the list includes: - has_errors (boolean) - has_warnings (boolean) - participants_count (integer) No N+1 queries — all computed in the same SQL query. --- app/views/conference_view.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/app/views/conference_view.py b/app/views/conference_view.py index 14d6c1f..2a40fe8 100644 --- a/app/views/conference_view.py +++ b/app/views/conference_view.py @@ -1,11 +1,13 @@ import datetime from django.core.exceptions import ValidationError +from django.db.models import Count, Exists, OuterRef from ..errors import (INVALID_PARAMETERS, CONFERENCE_NOT_FOUND, MISSING_PARAMETERS, PMError) from ..utils import JSONHttpResponse, serialize, paginate_and_serialize from ..models.conference import Conference +from ..models.issue import Issue from .generic_view import GenericView class ConferencesView(GenericView): @@ -40,12 +42,23 @@ def filter(cls, request): filters['created_at__gt'] = datetime.datetime.fromisoformat(filters.get('created_at__gt')) try: - objs = Conference.filter(**filters) + objs = Conference.filter(**filters).annotate( + has_errors=Exists( + Issue.objects.filter(conference=OuterRef('pk'), type='e', is_active=True) + ), + has_warnings=Exists( + Issue.objects.filter(conference=OuterRef('pk'), type='w', is_active=True) + ), + participants_count=Count('participants', distinct=True), + ) except ValidationError: raise PMError(status=400, app_error=INVALID_PARAMETERS) return JSONHttpResponse( - content=paginate_and_serialize(request, objs), + content=paginate_and_serialize( + request, objs, + properties=['has_errors', 'has_warnings', 'participants_count'], + ), ) @classmethod From 638cab35563bbb8d1c04bf726d730d6d213ba6c4 Mon Sep 17 00:00:00 2001 From: Samson Nkrumah Date: Fri, 10 Apr 2026 15:07:48 +0100 Subject: [PATCH 2/3] fix: use Subquery for participants_count to avoid filtered join bug Count('participants') reuses the filtered JOIN when participantId is in the query string, returning 1 instead of the real total. Switched to a Subquery that counts independently of the request's participant filter. Addresses review feedback from Codex and Alberto. --- app/views/conference_view.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/views/conference_view.py b/app/views/conference_view.py index 2a40fe8..f56e97f 100644 --- a/app/views/conference_view.py +++ b/app/views/conference_view.py @@ -1,13 +1,14 @@ import datetime from django.core.exceptions import ValidationError -from django.db.models import Count, Exists, OuterRef +from django.db.models import Count, Exists, IntegerField, OuterRef, Subquery from ..errors import (INVALID_PARAMETERS, CONFERENCE_NOT_FOUND, MISSING_PARAMETERS, PMError) from ..utils import JSONHttpResponse, serialize, paginate_and_serialize from ..models.conference import Conference from ..models.issue import Issue +from ..models.participant import Participant from .generic_view import GenericView class ConferencesView(GenericView): @@ -49,7 +50,14 @@ def filter(cls, request): has_warnings=Exists( Issue.objects.filter(conference=OuterRef('pk'), type='w', is_active=True) ), - participants_count=Count('participants', distinct=True), + participants_count=Subquery( + Participant.objects.filter( + conferences=OuterRef('pk') + ).order_by().values('conferences').annotate( + cnt=Count('id', distinct=True) + ).values('cnt')[:1], + output_field=IntegerField(), + ), ) except ValidationError: raise PMError(status=400, app_error=INVALID_PARAMETERS) From 1e4565b698334dbadc401175e8f9b9bc6febeb85 Mon Sep 17 00:00:00 2001 From: Samson Nkrumah Date: Fri, 10 Apr 2026 15:17:11 +0100 Subject: [PATCH 3/3] fix: exclude soft-deleted participants from participants_count The Subquery used Participant.objects.filter() which bypasses the is_active=True check, overcounting if any participants were soft-deleted. --- app/views/conference_view.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/conference_view.py b/app/views/conference_view.py index f56e97f..9d35088 100644 --- a/app/views/conference_view.py +++ b/app/views/conference_view.py @@ -52,7 +52,8 @@ def filter(cls, request): ), participants_count=Subquery( Participant.objects.filter( - conferences=OuterRef('pk') + conferences=OuterRef('pk'), + is_active=True, ).order_by().values('conferences').annotate( cnt=Count('id', distinct=True) ).values('cnt')[:1],