Skip to content

Commit f01d0c2

Browse files
perf: Use lazy loading for Product_Tab to improve edit finding performance (#13805)
Replace eager query execution in Product_Tab.__init__ with @cached_property decorators. This defers expensive database queries until they are actually accessed, improving page load performance. Fixes #10313
1 parent b8f5e53 commit f01d0c2

File tree

1 file changed

+50
-27
lines changed

1 file changed

+50
-27
lines changed

dojo/utils.py

Lines changed: 50 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from calendar import monthrange
1111
from collections.abc import Callable
1212
from datetime import date, datetime, timedelta
13+
from functools import cached_property
1314
from math import pi, sqrt
1415
from pathlib import Path
1516

@@ -1303,52 +1304,77 @@ def get_celery_worker_status():
13031304

13041305

13051306
# Used to display the counts and enabled tabs in the product view
1307+
# Uses @cached_property for lazy loading to avoid expensive queries on every page load
1308+
# See: https://github.com/DefectDojo/django-DefectDojo/issues/10313
13061309
class Product_Tab:
13071310
def __init__(self, product, title=None, tab=None):
1308-
self.product = product
1309-
self.title = title
1310-
self.tab = tab
1311-
self.engagement_count = Engagement.objects.filter(
1312-
product=self.product, active=True).count()
1313-
self.open_findings_count = Finding.objects.filter(test__engagement__product=self.product,
1314-
false_p=False,
1315-
duplicate=False,
1316-
out_of_scope=False,
1317-
active=True,
1318-
mitigated__isnull=True).count()
1319-
active_endpoints = Endpoint.objects.filter(
1320-
product=self.product,
1311+
self._product = product
1312+
self._title = title
1313+
self._tab = tab
1314+
self._engagement = None
1315+
1316+
@cached_property
1317+
def engagement_count(self):
1318+
return Engagement.objects.filter(
1319+
product=self._product, active=True).count()
1320+
1321+
@cached_property
1322+
def open_findings_count(self):
1323+
return Finding.objects.filter(
1324+
test__engagement__product=self._product,
1325+
false_p=False,
1326+
duplicate=False,
1327+
out_of_scope=False,
1328+
active=True,
1329+
mitigated__isnull=True).count()
1330+
1331+
@cached_property
1332+
def _active_endpoints(self):
1333+
return Endpoint.objects.filter(
1334+
product=self._product,
13211335
status_endpoint__mitigated=False,
13221336
status_endpoint__false_positive=False,
13231337
status_endpoint__out_of_scope=False,
13241338
status_endpoint__risk_accepted=False,
13251339
)
1326-
self.endpoints_count = active_endpoints.distinct().count()
1327-
self.endpoint_hosts_count = active_endpoints.values("host").distinct().count()
1328-
self.benchmark_type = Benchmark_Type.objects.filter(
1340+
1341+
@cached_property
1342+
def endpoints_count(self):
1343+
return self._active_endpoints.distinct().count()
1344+
1345+
@cached_property
1346+
def endpoint_hosts_count(self):
1347+
return self._active_endpoints.values("host").distinct().count()
1348+
1349+
@cached_property
1350+
def benchmark_type(self):
1351+
return Benchmark_Type.objects.filter(
13291352
enabled=True).order_by("name")
1330-
self.engagement = None
13311353

13321354
def setTab(self, tab):
1333-
self.tab = tab
1355+
self._tab = tab
13341356

13351357
def setEngagement(self, engagement):
1336-
self.engagement = engagement
1358+
self._engagement = engagement
13371359

1360+
@property
13381361
def engagement(self):
1339-
return self.engagement
1362+
return self._engagement
13401363

1364+
@property
13411365
def tab(self):
1342-
return self.tab
1366+
return self._tab
13431367

13441368
def setTitle(self, title):
1345-
self.title = title
1369+
self._title = title
13461370

1371+
@property
13471372
def title(self):
1348-
return self.title
1373+
return self._title
13491374

1375+
@property
13501376
def product(self):
1351-
return self.product
1377+
return self._product
13521378

13531379
def engagements(self):
13541380
return self.engagement_count
@@ -1362,9 +1388,6 @@ def endpoints(self):
13621388
def endpoint_hosts(self):
13631389
return self.endpoint_hosts_count
13641390

1365-
def benchmark_type(self):
1366-
return self.benchmark_type
1367-
13681391

13691392
# Used to display the counts and enabled tabs in the product view
13701393
def tab_view_count(product_id):

0 commit comments

Comments
 (0)