From af857c22348993c76ea06b94a64de784d4d0ffb5 Mon Sep 17 00:00:00 2001 From: Franciszek Stachura Date: Wed, 12 Aug 2026 00:20:40 +0200 Subject: [PATCH 1/4] paginator: Remove long_page property This property is not used anymore, the last reference was removed in 17726d7 (views: Move and refactor patch-forms). Computing it may execute an unnecessary count query. --- patchwork/paginator.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/patchwork/paginator.py b/patchwork/paginator.py index 1b7bd9145..6005f401f 100644 --- a/patchwork/paginator.py +++ b/patchwork/paginator.py @@ -90,7 +90,3 @@ def __init__(self, request, objects): # prevent e.g. 1 2 ... 5 6 7 ... 8 9 if self.trailing_set[0] != self.adjacent_set[-1] + 1: self.show_trailing_ellipsis = True - - self.long_page = ( - len(self.current_page.object_list) >= LONG_PAGE_THRESHOLD - ) From c9e4d3de4ac9b6754b9de6d4c2f899146e32b115 Mon Sep 17 00:00:00 2001 From: Franciszek Stachura Date: Wed, 12 Aug 2026 00:27:30 +0200 Subject: [PATCH 2/4] views: Optimize patch list Patch list is pretty slow partially because it filters on a query with a lot of extra fields. For example, I suspect that the tag count fields are computed even for rows that did not make it into the final result, same with joins. This patch changes this by separating the filtering stage, in which only patch IDs are queried, and the aggregation stage. --- .../patchwork/partials/patch-list.html | 2 +- patchwork/views/__init__.py | 23 +++++++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/patchwork/templates/patchwork/partials/patch-list.html b/patchwork/templates/patchwork/partials/patch-list.html index 981ceee58..b9bb62d21 100644 --- a/patchwork/templates/patchwork/partials/patch-list.html +++ b/patchwork/templates/patchwork/partials/patch-list.html @@ -154,7 +154,7 @@ -{% for patch in page.object_list %} +{% for patch in patches %} {% if user.is_authenticated %} diff --git a/patchwork/views/__init__.py b/patchwork/views/__init__.py index 92adbbccd..e8621301d 100644 --- a/patchwork/views/__init__.py +++ b/patchwork/views/__init__.py @@ -280,13 +280,26 @@ def generic_list( else: context['filters'].set_status(filterclass, setting) + # Filtering + if patches is None: patches = Patch.objects.filter(project=project) - # annotate with tag counts - patches = patches.with_tag_counts(project) + patch_ids = patches + + patch_ids = patch_ids.only('id') + patch_ids = context['filters'].apply(patch_ids) + if not editable_order: + patch_ids = order.apply(patch_ids) - patches = context['filters'].apply(patches) + paginator = Paginator(request, patch_ids) + patch_ids = list( + paginator.current_page.object_list.values_list('id', flat=True) + ) + + # Aggregation + + patches = patches.filter(id__in=patch_ids) if not editable_order: patches = order.apply(patches) @@ -321,11 +334,13 @@ def generic_list( ) ) - paginator = Paginator(request, patches) + # annotate with tag counts + patches = patches.with_tag_counts(project) context.update( { 'page': paginator.current_page, + 'patches': patches, 'patch_form': properties_form, 'create_bundle_form': create_bundle_form, 'project': project, From 6aa0712e9757ae0c70dad7b3759432d45e2ea4ef Mon Sep 17 00:00:00 2001 From: Franciszek Stachura Date: Wed, 12 Aug 2026 16:15:25 +0200 Subject: [PATCH 3/4] views: Add a new covering index for patch table The new index uses project_id as the first key. This is important, as it seems that almost all web queries filter over project_id. The index also includes the submission date sorted descending, as this is the default sorting key for the patches list. --- .../0049_patch_patch_covering_idx_default.py | 28 +++++++++++++++++++ patchwork/models.py | 11 ++++++++ 2 files changed, 39 insertions(+) create mode 100644 patchwork/migrations/0049_patch_patch_covering_idx_default.py diff --git a/patchwork/migrations/0049_patch_patch_covering_idx_default.py b/patchwork/migrations/0049_patch_patch_covering_idx_default.py new file mode 100644 index 000000000..272ca5efe --- /dev/null +++ b/patchwork/migrations/0049_patch_patch_covering_idx_default.py @@ -0,0 +1,28 @@ +# Generated by Django 6.0.6 on 2026-08-13 00:14 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ('patchwork', '0048_series_dependencies'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddIndex( + model_name='patch', + index=models.Index( + fields=[ + 'project', + 'archived', + 'state', + '-date', + 'delegate', + 'submitter', + ], + name='patch_covering_idx_default', + ), + ), + ] diff --git a/patchwork/models.py b/patchwork/models.py index d5cb31de9..4f2008c8a 100644 --- a/patchwork/models.py +++ b/patchwork/models.py @@ -735,6 +735,17 @@ class Meta: ], name='patch_covering_idx', ), + models.Index( + fields=[ + 'project', + 'archived', + 'state', + '-date', + 'delegate', + 'submitter', + ], + name='patch_covering_idx_default', + ), ] From 55bdbad6ac5bef3f36b07b49d274522b106f4bc9 Mon Sep 17 00:00:00 2001 From: Franciszek Stachura Date: Thu, 13 Aug 2026 14:00:30 +0200 Subject: [PATCH 4/4] views: Limit max number of items per page to 1000 Allowing the users to set an arbitrary number of items per page may cause excessive load on the database. For example, a request to the patch list has to compute the number of tags for each listed patch. --- patchwork/models.py | 5 +++++ patchwork/paginator.py | 2 +- patchwork/settings/base.py | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/patchwork/models.py b/patchwork/models.py index 4f2008c8a..77ca6aaa3 100644 --- a/patchwork/models.py +++ b/patchwork/models.py @@ -13,6 +13,7 @@ from django.contrib.auth.models import User from django.core.exceptions import ValidationError from django.core.validators import validate_unicode_slug +from django.core.validators import MaxValueValidator from django.db import models from django.urls import reverse from django.utils.functional import cached_property @@ -172,6 +173,7 @@ class UserProfile(models.Model): null=False, blank=False, help_text='Number of items to display per page', + validators=[MaxValueValidator(settings.MAX_ITEMS_PER_PAGE)], ) show_ids = models.BooleanField( default=False, @@ -208,6 +210,9 @@ def token(self): except Token.DoesNotExist: return + def get_items_per_page(self): + return min(self.items_per_page, settings.MAX_ITEMS_PER_PAGE) + def todo_patches(self, project=None): # filter on project, if necessary if project: diff --git a/patchwork/paginator.py b/patchwork/paginator.py index 6005f401f..c79b493ae 100644 --- a/patchwork/paginator.py +++ b/patchwork/paginator.py @@ -25,7 +25,7 @@ def __init__(self, request, objects): items_per_page = settings.DEFAULT_ITEMS_PER_PAGE if request.user.is_authenticated: - items_per_page = request.user.profile.items_per_page + items_per_page = request.user.profile.get_items_per_page() super().__init__(objects, items_per_page) diff --git a/patchwork/settings/base.py b/patchwork/settings/base.py index 2b557fc1c..d5dd82c41 100644 --- a/patchwork/settings/base.py +++ b/patchwork/settings/base.py @@ -266,6 +266,8 @@ REST_RESULTS_PER_PAGE = 30 MAX_REST_RESULTS_PER_PAGE = 250 +MAX_ITEMS_PER_PAGE = 1000 + # Set to True to enable redirections or URLs from previous versions # of patchwork COMPAT_REDIR = True