44import logging
55from collections import OrderedDict
66from datetime import date , datetime , timedelta
7+ from functools import partial
78from math import ceil
89
910from dateutil .relativedelta import relativedelta
1213from django .contrib .postgres .aggregates import StringAgg
1314from django .core .exceptions import PermissionDenied , ValidationError
1415from django .db import DEFAULT_DB_ALIAS , connection
15- from django .db .models import Count , F , Max , OuterRef , Prefetch , Q , Subquery , Sum
16+ from django .db .models import Count , DateField , F , OuterRef , Prefetch , Q , Subquery , Sum
1617from django .db .models .expressions import Value
18+ from django .db .models .functions import Coalesce
1719from django .db .models .query import QuerySet
1820from django .http import Http404 , HttpRequest , HttpResponseRedirect , JsonResponse
1921from django .shortcuts import get_object_or_404 , render
8991 Product_Type ,
9092 System_Settings ,
9193 Test ,
94+ Test_Import ,
9295 Test_Type ,
9396)
9497from dojo .product .queries import (
113116 add_external_issue ,
114117 add_field_errors_to_response ,
115118 async_delete ,
119+ build_count_subquery ,
116120 calculate_finding_age ,
117121 get_enabled_notifications_list ,
118122 get_open_findings_burndown ,
@@ -134,10 +138,15 @@ def product(request):
134138 # perform all stuff for filtering and pagination first, before annotation/prefetching
135139 # otherwise the paginator will perform all the annotations/prefetching already only to count the total number of records
136140 # see https://code.djangoproject.com/ticket/23771 and https://code.djangoproject.com/ticket/25375
141+
137142 name_words = prods .values_list ("name" , flat = True )
143+ base_findings = Finding .objects .filter (test__engagement__product_id = OuterRef ("pk" ), active = True )
138144 prods = prods .annotate (
139- findings_count = Count ("engagement__test__finding" , filter = Q (engagement__test__finding__active = True )),
145+ findings_count = Coalesce (
146+ build_count_subquery (base_findings , group_field = "test__engagement__product_id" ), Value (0 ),
147+ ),
140148 )
149+
141150 filter_string_matching = get_system_setting ("filter_string_matching" , False )
142151 filter_class = ProductFilterWithoutObjectLookups if filter_string_matching else ProductFilter
143152 prod_filter = filter_class (request .GET , queryset = prods , user = request .user )
@@ -157,48 +166,63 @@ def product(request):
157166
158167
159168def prefetch_for_product (prods ):
160- prefetched_prods = prods
161- if isinstance (prods ,
162- QuerySet ): # old code can arrive here with prods being a list because the query was already executed
163-
164- prefetched_prods = prefetched_prods .prefetch_related ("team_manager" )
165- prefetched_prods = prefetched_prods .prefetch_related ("product_manager" )
166- prefetched_prods = prefetched_prods .prefetch_related ("technical_contact" )
167-
168- prefetched_prods = prefetched_prods .annotate (
169- active_engagement_count = Count ("engagement__id" , filter = Q (engagement__active = True )))
170- prefetched_prods = prefetched_prods .annotate (
171- closed_engagement_count = Count ("engagement__id" , filter = Q (engagement__active = False )))
172- prefetched_prods = prefetched_prods .annotate (last_engagement_date = Max ("engagement__target_start" ))
173- prefetched_prods = prefetched_prods .annotate (active_finding_count = Count ("engagement__test__finding__id" ,
174- filter = Q (
175- engagement__test__finding__active = True )))
176- prefetched_prods = prefetched_prods .annotate (
177- active_verified_finding_count = Count ("engagement__test__finding__id" ,
178- filter = Q (
179- engagement__test__finding__active = True ,
180- engagement__test__finding__verified = True )))
181- prefetched_prods = prefetched_prods .prefetch_related ("jira_project_set__jira_instance" )
182- prefetched_prods = prefetched_prods .prefetch_related ("members" )
183- prefetched_prods = prefetched_prods .prefetch_related ("prod_type__members" )
184- active_endpoint_query = Endpoint .objects .filter (
185- status_endpoint__mitigated = False ,
186- status_endpoint__false_positive = False ,
187- status_endpoint__out_of_scope = False ,
188- status_endpoint__risk_accepted = False ,
189- ).distinct ()
190- prefetched_prods = prefetched_prods .prefetch_related (
191- Prefetch ("endpoint_set" , queryset = active_endpoint_query , to_attr = "active_endpoints" ))
192- prefetched_prods = prefetched_prods .prefetch_related ("tags" )
169+ # old code can arrive here with prods being a list because the query was already executed
170+ if not isinstance (prods , QuerySet ):
171+ logger .debug ("unable to prefetch because query was already executed" )
172+ return prods
193173
194- if get_system_setting ("enable_github" ):
195- prefetched_prods = prefetched_prods .prefetch_related (
196- Prefetch ("github_pkey_set" , queryset = GITHUB_PKey .objects .all ().select_related ("git_conf" ),
197- to_attr = "github_confs" ))
174+ prefetched_prods = prods .select_related ("team_manager" , "product_manager" , "technical_contact" ).prefetch_related (
175+ "tags" ,
176+ "members" ,
177+ "prod_type__members" ,
178+ "jira_project_set__jira_instance" ,
179+ )
198180
199- else :
200- logger .debug ("unable to prefetch because query was already executed" )
181+ engagements = Engagement .objects .filter (product_id = OuterRef ("pk" ))
182+ count_subquery = partial (build_count_subquery , group_field = "product_id" )
183+ prefetched_prods = prefetched_prods .annotate (
184+ active_engagement_count = Coalesce (count_subquery (engagements .filter (active = True )), Value (0 )),
185+ closed_engagement_count = Coalesce (count_subquery (engagements .filter (active = False )), Value (0 )),
186+ last_engagement_date = Subquery (
187+ engagements .order_by ("-target_start" ).values ("target_start" )[:1 ], output_field = DateField (),
188+ ),
189+ )
201190
191+ base_findings = Finding .objects .filter (test__engagement__product_id = OuterRef ("pk" ))
192+ count_subquery = partial (build_count_subquery , group_field = "test__engagement__product_id" )
193+ prefetched_prods = prefetched_prods .annotate (
194+ active_finding_count = Coalesce (count_subquery (base_findings .filter (active = True )), Value (0 )),
195+ active_verified_finding_count = Coalesce (
196+ count_subquery (base_findings .filter (active = True , verified = True )),
197+ Value (0 ),
198+ ),
199+ )
200+ prefetched_prods = prefetched_prods .annotate (
201+ total_reimport_count = Coalesce (
202+ count_subquery (
203+ Test_Import .objects .filter (test__engagement__product_id = OuterRef ("pk" ), type = Test_Import .REIMPORT_TYPE ),
204+ ),
205+ Value (0 ),
206+ ),
207+ )
208+
209+ active_endpoint_qs = Endpoint .objects .filter (
210+ status_endpoint__mitigated = False ,
211+ status_endpoint__false_positive = False ,
212+ status_endpoint__out_of_scope = False ,
213+ status_endpoint__risk_accepted = False ,
214+ ).distinct ()
215+
216+ prefetched_prods = prefetched_prods .prefetch_related (
217+ Prefetch ("endpoint_set" , queryset = active_endpoint_qs , to_attr = "active_endpoints" ),
218+ )
219+
220+ if get_system_setting ("enable_github" ):
221+ prefetched_prods = prefetched_prods .prefetch_related (
222+ Prefetch (
223+ "github_pkey_set" , queryset = GITHUB_PKey .objects .all ().select_related ("git_conf" ), to_attr = "github_confs" ,
224+ ),
225+ )
202226 return prefetched_prods
203227
204228
0 commit comments