From b0532aa58b5b761275cb07e619d720cfd005ae00 Mon Sep 17 00:00:00 2001 From: devswithme Date: Wed, 25 Mar 2026 16:49:55 +0800 Subject: [PATCH] Batch the per-course completed-content lookup in _consolidate_courses_data The learn home/course consolidation issued two queries per enrolled course: a descendant COUNT and a ContentSummaryLog COUNT for completed content (2N total). Gather each course's descendant content ids in a single pass and resolve completed content across all courses in one ContentSummaryLog query (N+1). Output is unchanged: total stays the descendant row count and completed stays the distinct count of matching summary logs (set intersection), matching the original filter(content_id__in=...).count() semantics. Co-Authored-By: Claude Opus 4.8 (1M context) --- kolibri/plugins/learn/viewsets.py | 35 +++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/kolibri/plugins/learn/viewsets.py b/kolibri/plugins/learn/viewsets.py index 184d6a8e951..e921a41f03f 100644 --- a/kolibri/plugins/learn/viewsets.py +++ b/kolibri/plugins/learn/viewsets.py @@ -131,29 +131,42 @@ def _consolidate_courses_data(request, courses): ), ) - course_data_map = {} - + # Gather each course's non-topic descendant content ids in one pass, then + # look up the user's completed content across all courses in a single query + # rather than running one ContentSummaryLog count per course. + course_meta = {} + all_content_ids = set() for course_node in course_nodes: - content_qs = ( + descendant_ids = list( course_node.get_descendants() .exclude(kind=content_kinds.TOPIC) .values_list("content_id", flat=True) ) + course_meta[course_node.id] = ( + course_node.unit_count, + course_node.lesson_count, + descendant_ids, + ) + all_content_ids.update(descendant_ids) - total_content = content_qs.count() + completed_content_ids = set( + ContentSummaryLog.objects.filter( + user=request.user, progress=1, content_id__in=all_content_ids + ).values_list("content_id", flat=True) + ) - user_completed_content = 0 + course_data_map = {} + for course_id, (unit_count, lesson_count, descendant_ids) in course_meta.items(): + total_content = len(descendant_ids) if total_content: - user_completed_content = ContentSummaryLog.objects.filter( - user=request.user, progress=1, content_id__in=content_qs - ).count() + user_completed_content = len(set(descendant_ids) & completed_content_ids) progress = user_completed_content / total_content else: progress = 0 - course_data_map[course_node.id] = { - "unit_count": course_node.unit_count, - "lesson_count": course_node.lesson_count, + course_data_map[course_id] = { + "unit_count": unit_count, + "lesson_count": lesson_count, "progress": progress, }