Skip to content

Commit f7e8380

Browse files
authored
feat: rework the pagination with per-model setting (#494)
Signed-off-by: tdruez <tdruez@aboutcode.org>
1 parent acc2559 commit f7e8380

8 files changed

Lines changed: 87 additions & 19 deletions

File tree

component_catalog/tests/test_views.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
from component_catalog.tests import make_package
4343
from component_catalog.views import ComponentAddView
4444
from component_catalog.views import ComponentListView
45+
from component_catalog.views import PackageListView
4546
from component_catalog.views import PackageTabScanView
4647
from dejacode_toolkit.scancodeio import get_webhook_url
4748
from dejacode_toolkit.vulnerablecode import VulnerableCode
@@ -55,6 +56,7 @@
5556
from dje.tests import add_perms
5657
from dje.tests import create_superuser
5758
from dje.tests import create_user
59+
from dje.views import PaginationMixin
5860
from license_library.models import License
5961
from license_library.models import LicenseAssignedTag
6062
from license_library.models import LicenseTag
@@ -517,8 +519,8 @@ def test_component_catalog_history_tab(self):
517519
self.assertContains(response, "Changed name.")
518520

519521
def test_component_catalog_productcomponent_secured_hierarchy_and_product_usage(self):
520-
component1 = Component.objects.create(name="c1", dataspace=self.nexb_dataspace)
521-
product1 = Product.objects.create(name="p1", dataspace=self.nexb_dataspace)
522+
component1 = Component.objects.create(name="component1", dataspace=self.nexb_dataspace)
523+
product1 = Product.objects.create(name="product1", dataspace=self.nexb_dataspace)
522524
ProductComponent.objects.create(
523525
product=product1, component=component1, dataspace=self.nexb_dataspace
524526
)
@@ -1134,6 +1136,20 @@ def test_package_list_view_num_queries(self):
11341136
with self.assertNumQueries(16):
11351137
self.client.get(reverse("component_catalog:package_list"))
11361138

1139+
def test_package_list_view_pagination(self):
1140+
list_view = PackageListView()
1141+
1142+
# Default value from the PaginationMixin.default_paginate_by
1143+
with override_settings(DEJACODE_PAGINATE_BY={}):
1144+
self.assertIsNone(list_view.paginate_by)
1145+
expected = PaginationMixin.default_paginate_by
1146+
self.assertEqual(expected, list_view.get_paginate_by(queryset=None))
1147+
1148+
# Value from custom DEJACODE_PAGINATE_BY
1149+
with override_settings(DEJACODE_PAGINATE_BY={"package": 20}):
1150+
self.assertIsNone(list_view.paginate_by)
1151+
self.assertEqual(20, list_view.get_paginate_by(queryset=None))
1152+
11371153
def test_package_views_urls(self):
11381154
p1 = Package(
11391155
filename="filename.zip",

component_catalog/views.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,6 @@ class ComponentListView(
353353
template_list_table = "component_catalog/tables/component_list_table.html"
354354
include_reference_dataspace = True
355355
put_results_in_session = True
356-
paginate_by = settings.PAGINATE_BY or 200
357356
group_name_version = True
358357

359358
table_headers = (
@@ -1734,9 +1733,9 @@ class ScanListView(
17341733
AddPackagePermissionMixin,
17351734
APIWrapperListView,
17361735
):
1737-
paginate_by = 50
17381736
template_name = "component_catalog/scan_list.html"
17391737
template_list_table = "component_catalog/tables/scan_list_table.html"
1738+
paginate_by = settings.DEJACODE_PAGINATE_BY.get("scan", 50)
17401739

17411740
def dispatch(self, request, *args, **kwargs):
17421741
user = self.request.user

dejacode/settings.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
import sys
1212
import tempfile
13+
import warnings
1314
from pathlib import Path
1415

1516
import environ
@@ -383,9 +384,31 @@ def gettext_noop(s):
383384
# Set False to hide the "Product Portfolio" section in the navbar.
384385
SHOW_PP_IN_NAV = env.bool("SHOW_PP_IN_NAV", default=True)
385386

387+
# An integer specifying how many objects should be displayed per table whithin tabs.
388+
TAB_PAGINATE_BY = env.int("TAB_PAGINATE_BY", default=100)
389+
386390
# An integer specifying how many objects should be displayed per page.
387391
PAGINATE_BY = env.int("PAGINATE_BY", default=None)
388-
TAB_PAGINATE_BY = env.int("TAB_PAGINATE_BY", default=100)
392+
if PAGINATE_BY:
393+
warnings.warn("The PAGINATE_BY setting is deprecated. Use DEJACODE_PAGINATE_BY instead.")
394+
395+
396+
# List views pagination, controls the number of items displayed per page.
397+
# Syntax in .env: DEJACODE_PAGINATE_BY=product=20,package=100,license=100,request=50,scan=50
398+
DEJACODE_PAGINATE_BY = env.dict(
399+
"DEJACODE_PAGINATE_BY",
400+
default={
401+
"product": 50,
402+
"component": 100,
403+
"package": 100,
404+
"license": 100,
405+
"owner": 100,
406+
"report": 50,
407+
"request": 50,
408+
"scan": 50,
409+
"vulnerability": 100,
410+
},
411+
)
389412

390413
ADMIN_FORMS_CONFIGURATION = env.dict("ADMIN_FORMS_CONFIGURATION", default={})
391414

dje/views.py

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -260,8 +260,32 @@ def get_queryset(self):
260260
return qs.scope(dataspace, include_reference=self.include_reference_dataspace)
261261

262262

263-
class PreviousNextPaginationMixin:
263+
class PaginationMixin:
264264
query_dict_page_param = "page"
265+
paginate_by = None
266+
default_paginate_by = 100
267+
268+
def get_paginate_by(self, queryset):
269+
"""
270+
Determine the number of items per page.
271+
272+
Resolution order:
273+
1. ``paginate_by`` set directly on the view instance.
274+
2. Per-model value from the ``DEJACODE_PAGINATE_BY`` setting.
275+
3. ``default_paginate_by`` as a fallback.
276+
"""
277+
if self.paginate_by:
278+
return self.paginate_by
279+
280+
if self.model and settings.DEJACODE_PAGINATE_BY:
281+
model_name = self.model._meta.model_name
282+
if paginate_by := settings.DEJACODE_PAGINATE_BY.get(model_name):
283+
try:
284+
return int(paginate_by)
285+
except ValueError:
286+
return self.default_paginate_by
287+
288+
return self.default_paginate_by
265289

266290
def get_previous_next(self, page_obj):
267291
"""Return url links for the previous and next navigation."""
@@ -330,12 +354,11 @@ class DataspacedFilterView(
330354
GetDataspaceMixin,
331355
HasPermissionMixin,
332356
TableHeaderMixin,
333-
PreviousNextPaginationMixin,
357+
PaginationMixin,
334358
FilterView,
335359
):
336360
template_name = "object_list_base.html"
337361
template_list_table = None
338-
paginate_by = settings.PAGINATE_BY or 100
339362
# Required if `show_previous_and_next_object_links` enabled on the
340363
# details view.
341364
put_results_in_session = False
@@ -2246,7 +2269,7 @@ def page(self, number):
22462269

22472270

22482271
class APIWrapperListView(
2249-
PreviousNextPaginationMixin,
2272+
PaginationMixin,
22502273
ListView,
22512274
):
22522275
paginate_by = 100

docs/application-settings.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,16 @@ longer than this value.
162162
# 1 hour, in seconds.
163163
SESSION_COOKIE_AGE=3600
164164
165+
.. _dejacode_settings_paginate_by:
166+
167+
DEJACODE_PAGINATE_BY
168+
--------------------
169+
170+
The number of objects display per page for each object type can be customized with the
171+
following setting::
172+
173+
DEJACODE_PAGINATE_BY=product=20,package=100,license=100,report=50,request=50,scan=50
174+
165175
DEJACODE_LOG_LEVEL
166176
------------------
167177

product_portfolio/views.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@
9999
from dje.views import Header
100100
from dje.views import LicenseDataForBuilderMixin
101101
from dje.views import ObjectDetailsView
102-
from dje.views import PreviousNextPaginationMixin
102+
from dje.views import PaginationMixin
103103
from dje.views import SendAboutFilesView
104104
from dje.views import TabContentView
105105
from dje.views import TabField
@@ -164,7 +164,6 @@ class ProductListView(
164164
filterset_class = ProductFilterSet
165165
template_name = "product_portfolio/product_list.html"
166166
template_list_table = "product_portfolio/tables/product_list_table.html"
167-
paginate_by = 50
168167
put_results_in_session = False
169168
group_name_version = True
170169
table_headers = (
@@ -733,7 +732,7 @@ def get_context_data(self, **kwargs):
733732
class ProductTabInventoryView(
734733
LoginRequiredMixin,
735734
BaseProductViewMixin,
736-
PreviousNextPaginationMixin,
735+
PaginationMixin,
737736
TabContentView,
738737
):
739738
template_name = "product_portfolio/tabs/tab_inventory.html"
@@ -978,7 +977,7 @@ def inject_scan_data(scancodeio, feature_grouped, dataspace_uuid):
978977
class ProductTabCodebaseView(
979978
LoginRequiredMixin,
980979
BaseProductViewMixin,
981-
PreviousNextPaginationMixin,
980+
PaginationMixin,
982981
TabContentView,
983982
):
984983
template_name = "product_portfolio/tabs/tab_codebase.html"
@@ -1059,7 +1058,7 @@ def has_any_values(field_name):
10591058
class ProductTabDependenciesView(
10601059
LoginRequiredMixin,
10611060
BaseProductViewMixin,
1062-
PreviousNextPaginationMixin,
1061+
PaginationMixin,
10631062
TableHeaderMixin,
10641063
TabContentView,
10651064
):
@@ -1138,7 +1137,7 @@ def get_context_data(self, **kwargs):
11381137
class ProductTabVulnerabilitiesView(
11391138
LoginRequiredMixin,
11401139
BaseProductViewMixin,
1141-
PreviousNextPaginationMixin,
1140+
PaginationMixin,
11421141
TableHeaderMixin,
11431142
TabContentView,
11441143
):

reporting/views.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
from dje.views import DataspacedFilterView
3535
from dje.views import DownloadableMixin
3636
from dje.views import HasPermissionMixin
37-
from dje.views import PreviousNextPaginationMixin
37+
from dje.views import PaginationMixin
3838
from reporting.filters import ReportFilterSet
3939
from reporting.forms import RuntimeFilterBaseFormSet
4040
from reporting.forms import RuntimeFilterForm
@@ -81,7 +81,7 @@ def get(self, request, *args, **kwargs):
8181

8282
class ReportDetailsView(
8383
LoginRequiredMixin,
84-
PreviousNextPaginationMixin,
84+
PaginationMixin,
8585
BootstrapCSSMixin,
8686
DownloadableMixin,
8787
HasPermissionMixin,
@@ -294,7 +294,6 @@ class ReportListView(
294294
filterset_class = ReportFilterSet
295295
template_name = "reporting/report_list.html"
296296
template_list_table = "reporting/includes/report_list_table.html"
297-
paginate_by = 50
298297

299298
def get_queryset(self):
300299
qs = super().get_queryset()

workflow/views.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ class RequestListView(
4545
filterset_class = RequestFilterSet
4646
template_name = "workflow/request_list.html"
4747
template_list_table = "workflow/includes/request_list_table.html"
48-
paginate_by = 50
4948

5049
def get_queryset(self):
5150
"""

0 commit comments

Comments
 (0)