1212
1313from django .contrib .postgres .aggregates import ArrayAgg
1414from django .core .cache import cache
15+ from django .db .models import Count
1516from django .db .models import Exists
1617from django .db .models import F
1718from django .db .models import Max
3435from vulnerabilities .models import AdvisorySet
3536from vulnerabilities .models import AdvisorySetMember
3637from vulnerabilities .models import AdvisorySeverity
38+ from vulnerabilities .models import AdvisoryToDoV2
3739from vulnerabilities .models import AdvisoryV2
3840from vulnerabilities .models import AdvisoryWeakness
3941from vulnerabilities .models import ImpactedPackageAffecting
@@ -126,6 +128,8 @@ class AdvisoryV3Serializer(serializers.ModelSerializer):
126128 severities = AdvisorySeveritySerializer (many = True )
127129 advisory_uid = serializers .CharField (source = "avid" , read_only = True )
128130 related_ssvc_trees = serializers .SerializerMethodField ()
131+ todo_count = serializers .IntegerField (read_only = True )
132+ curating_advisories = serializers .SerializerMethodField ()
129133
130134 def get_related_ssvc_trees (self , obj ):
131135 seen = set ()
@@ -150,6 +154,18 @@ def get_related_ssvc_trees(self, obj):
150154
151155 return result
152156
157+ def get_curating_advisories (self , obj ):
158+ request = self .context .get ("request" )
159+ return [
160+ reverse (
161+ "advisory_details" ,
162+ kwargs = {"avid" : related_advisory .avid },
163+ request = request ,
164+ )
165+ for todo in obj .resolves_todos .all ()
166+ for related_advisory in todo .advisories .all ()
167+ ]
168+
153169 class Meta :
154170 model = AdvisoryV2
155171 fields = [
@@ -165,6 +181,9 @@ class Meta:
165181 "weighted_severity" ,
166182 "risk_score" ,
167183 "related_ssvc_trees" ,
184+ "todo_count" ,
185+ "is_curation" ,
186+ "curating_advisories" ,
168187 ]
169188
170189
@@ -380,6 +399,9 @@ class Meta:
380399 "risk_score" ,
381400 "related_ssvc_trees" ,
382401 "fixed_by_packages" ,
402+ "todo_count" ,
403+ "is_curation" ,
404+ "curating_advisories" ,
383405 ]
384406
385407
@@ -396,51 +418,67 @@ def create(self, request, *args, **kwargs):
396418
397419 purls = serializer .validated_data ["purls" ]
398420
399- latest_advisories = AdvisoryV2 .objects .latest_advisories_for_purls (
400- purls = purls
401- ).prefetch_related (
402- Prefetch (
403- "references" ,
404- queryset = AdvisoryReference .objects .only (
405- "id" ,
406- "url" ,
407- "reference_type" ,
408- "reference_id" ,
421+ latest_advisories = (
422+ AdvisoryV2 .objects .latest_advisories_for_purls (purls = purls )
423+ .annotate (
424+ todo_count = Count (
425+ "advisory_todos" ,
426+ filter = Q (advisory_todos__is_todo_stale = False ),
427+ )
428+ )
429+ .prefetch_related (
430+ Prefetch (
431+ "references" ,
432+ queryset = AdvisoryReference .objects .only (
433+ "id" ,
434+ "url" ,
435+ "reference_type" ,
436+ "reference_id" ,
437+ ),
409438 ),
410- ),
411- Prefetch (
412- "severities" ,
413- queryset = AdvisorySeverity . objects . only (
414- "id " ,
415- "url " ,
416- "value " ,
417- "scoring_system " ,
418- "scoring_elements " ,
419- "published_at" ,
439+ Prefetch (
440+ "severities" ,
441+ queryset = AdvisorySeverity . objects . only (
442+ "id" ,
443+ "url " ,
444+ "value " ,
445+ "scoring_system " ,
446+ "scoring_elements " ,
447+ "published_at " ,
448+ ) ,
420449 ),
421- ) ,
422- "weaknesses " ,
423- "aliases" ,
424- Prefetch (
425- "related_ssvcs" ,
426- queryset = SSVC . objects . only (
427- "id " ,
428- "vector " ,
429- "decision " ,
430- "options " ,
431- "source_advisory__url" ,
450+ "weaknesses" ,
451+ "aliases " ,
452+ Prefetch (
453+ "related_ssvcs" ,
454+ queryset = SSVC . objects . only (
455+ "id" ,
456+ "vector " ,
457+ "decision " ,
458+ "options " ,
459+ "source_advisory__url " ,
460+ ) ,
432461 ),
433- ),
434- Prefetch (
435- "source_ssvcs" ,
436- queryset = SSVC . objects . only (
437- "id " ,
438- "vector " ,
439- "decision " ,
440- "options " ,
441- "source_advisory__url" ,
462+ Prefetch (
463+ "source_ssvcs" ,
464+ queryset = SSVC . objects . only (
465+ "id" ,
466+ "vector " ,
467+ "decision " ,
468+ "options " ,
469+ "source_advisory__url " ,
470+ ) ,
442471 ),
443- ),
472+ Prefetch (
473+ "resolves_todos" ,
474+ queryset = AdvisoryToDoV2 .objects .prefetch_related (
475+ Prefetch (
476+ "advisories" ,
477+ queryset = AdvisoryV2 .objects .only ("avid" ),
478+ )
479+ ),
480+ ),
481+ )
444482 )
445483
446484 page = self .paginate_queryset (latest_advisories )
@@ -459,7 +497,27 @@ def get_queryset(self):
459497 if not purl :
460498 return AdvisoryV2 .objects .none ()
461499
462- return AdvisoryV2 .objects .filter (** {self .relation : purl }).latest_per_avid ()
500+ return (
501+ AdvisoryV2 .objects .filter (** {self .relation : purl })
502+ .latest_per_avid ()
503+ .annotate (
504+ todo_count = Count (
505+ "advisory_todos" ,
506+ filter = Q (advisory_todos__is_todo_stale = False ),
507+ )
508+ )
509+ .prefetch_related (
510+ Prefetch (
511+ "resolves_todos" ,
512+ queryset = AdvisoryToDoV2 .objects .prefetch_related (
513+ Prefetch (
514+ "advisories" ,
515+ queryset = AdvisoryV2 .objects .only ("avid" ),
516+ )
517+ ),
518+ ),
519+ )
520+ )
463521
464522
465523class FixingAdvisoriesViewSet (PackageAdvisoriesViewSet ):
@@ -619,6 +677,15 @@ def get_affected_advisories_bulk(packages, max_advisories, base_url, reachabilit
619677 "aliases" ,
620678 queryset = AdvisoryAlias .objects .only ("alias" ),
621679 ),
680+ Prefetch (
681+ "primary_advisory__resolves_todos" ,
682+ queryset = AdvisoryToDoV2 .objects .prefetch_related (
683+ Prefetch (
684+ "advisories" ,
685+ queryset = AdvisoryV2 .objects .only ("avid" ),
686+ )
687+ ),
688+ ),
622689 )
623690 .annotate (
624691 max_severity = Max (
@@ -627,13 +694,18 @@ def get_affected_advisories_bulk(packages, max_advisories, base_url, reachabilit
627694 max_exploitability = Max (
628695 "members__advisory__exploitability" ,
629696 ),
697+ primary_adv_todo_count = Count (
698+ "primary_advisory__advisory_todos" ,
699+ filter = Q (primary_advisory__advisory_todos__is_todo_stale = False ),
700+ ),
630701 )
631702 .only (
632703 "id" ,
633704 "package_id" ,
634705 "primary_advisory__avid" ,
635706 "primary_advisory__summary" ,
636707 "primary_advisory__advisory_id" ,
708+ "primary_advisory__is_curation" ,
637709 )
638710 )
639711
@@ -746,6 +818,12 @@ def get_affected_advisories_bulk(packages, max_advisories, base_url, reachabilit
746818
747819 aliases = [a for a in adv ._aliases_cache if a != identifier ]
748820
821+ curating_advisories = [
822+ f"{ base_url } { related_advisory .get_absolute_url ()} "
823+ for todo in primary .resolves_todos .all ()
824+ for related_advisory in todo .advisories .all ()
825+ ]
826+
749827 resource_url = None
750828 advisory_url = primary .get_absolute_url ()
751829
@@ -772,6 +850,9 @@ def get_affected_advisories_bulk(packages, max_advisories, base_url, reachabilit
772850 ),
773851 "ssvc_trees" : adv .ssvc_trees ,
774852 "resource_url" : resource_url ,
853+ "todo_count" : adv .primary_adv_todo_count ,
854+ "is_curation" : primary .is_curation ,
855+ "curating_advisories" : curating_advisories ,
775856 }
776857 )
777858
@@ -809,25 +890,43 @@ def get_affected_advisories_bulk(packages, max_advisories, base_url, reachabilit
809890 if not allowed_package_ids :
810891 return result
811892
812- advisories = AdvisoryV2 .objects .filter (
813- id__in = allowed_advisory_ids ,
814- ).prefetch_related (
815- "aliases" ,
816- Prefetch (
817- "related_ssvcs" ,
818- queryset = (
819- SSVC .objects .select_related ("source_advisory" )
820- .only (
821- "id" ,
822- "decision" ,
823- "options" ,
824- "vector" ,
825- "source_advisory__url" ,
826- )
827- .distinct ("source_advisory__url" )
893+ advisories = (
894+ AdvisoryV2 .objects .filter (
895+ id__in = allowed_advisory_ids ,
896+ )
897+ .annotate (
898+ todo_count = Count (
899+ "advisory_todos" ,
900+ filter = Q (advisory_todos__is_todo_stale = False ),
901+ )
902+ )
903+ .prefetch_related (
904+ "aliases" ,
905+ Prefetch (
906+ "related_ssvcs" ,
907+ queryset = (
908+ SSVC .objects .select_related ("source_advisory" )
909+ .only (
910+ "id" ,
911+ "decision" ,
912+ "options" ,
913+ "vector" ,
914+ "source_advisory__url" ,
915+ )
916+ .distinct ("source_advisory__url" )
917+ ),
918+ to_attr = "prefetched_ssvc_trees" ,
828919 ),
829- to_attr = "prefetched_ssvc_trees" ,
830- ),
920+ Prefetch (
921+ "resolves_todos" ,
922+ queryset = AdvisoryToDoV2 .objects .prefetch_related (
923+ Prefetch (
924+ "advisories" ,
925+ queryset = AdvisoryV2 .objects .only ("avid" ),
926+ )
927+ ),
928+ ),
929+ )
831930 )
832931
833932 advisory_by_id = {advisory .id : advisory for advisory in advisories }
@@ -851,6 +950,11 @@ def get_affected_advisories_bulk(packages, max_advisories, base_url, reachabilit
851950 identifier = advisory .advisory_id .split ("/" )[- 1 ]
852951
853952 aliases = [alias .alias for alias in advisory .aliases .all () if alias .alias != identifier ]
953+ curating_advisories = [
954+ f"{ base_url } { related_advisory .get_absolute_url ()} "
955+ for todo in advisory .resolves_todos .all ()
956+ for related_advisory in todo .advisories .all ()
957+ ]
854958
855959 resource_url = None
856960 advisory_url = advisory .get_absolute_url ()
@@ -880,6 +984,9 @@ def get_affected_advisories_bulk(packages, max_advisories, base_url, reachabilit
880984 for ssvc in advisory .prefetched_ssvc_trees
881985 ],
882986 "resource_url" : resource_url ,
987+ "todo_count" : advisory .todo_count ,
988+ "is_curation" : advisory .is_curation ,
989+ "curating_advisories" : curating_advisories ,
883990 }
884991 )
885992
0 commit comments