@@ -3367,6 +3367,138 @@ def get_aliases(self):
33673367 alias = get_aliases
33683368
33693369
3370+ class AdvisoryHistoryDiff (models .Model ):
3371+ """
3372+ Stores the pre-computed diff between an AdvisoryV2 snapshot (advisory_after)
3373+ and its immediate predecessor (advisory_before).
3374+ Created for every snapshot.
3375+ """
3376+
3377+ # Points to the older snapshot in the comparison
3378+ advisory_before = models .OneToOneField (
3379+ AdvisoryV2 ,
3380+ on_delete = models .SET_NULL ,
3381+ related_name = "history_diff_before" ,
3382+ null = True , # The very first snapshot won't have a predecessor
3383+ blank = True ,
3384+ )
3385+
3386+ # Points to the newer snapshot in the comparison
3387+ advisory_after = models .OneToOneField (
3388+ AdvisoryV2 , on_delete = models .SET_NULL , related_name = "history_diff" , null = True , blank = True
3389+ )
3390+
3391+ summary_added = models .TextField (blank = True , null = True )
3392+ summary_removed = models .TextField (blank = True , null = True )
3393+
3394+ url_added = models .URLField (blank = True , null = True )
3395+ url_removed = models .URLField (blank = True , null = True )
3396+
3397+ added_severities = models .ManyToManyField (
3398+ "AdvisorySeverity" , related_name = "added_in_history" , blank = True
3399+ )
3400+ removed_severities = models .ManyToManyField (
3401+ "AdvisorySeverity" , related_name = "removed_in_history" , blank = True
3402+ )
3403+
3404+ added_impacted_packages = models .ManyToManyField (
3405+ "ImpactedPackage" , related_name = "added_in_history" , blank = True
3406+ )
3407+ removed_impacted_packages = models .ManyToManyField (
3408+ "ImpactedPackage" , related_name = "removed_in_history" , blank = True
3409+ )
3410+
3411+ added_references = models .ManyToManyField (
3412+ "AdvisoryReference" , related_name = "added_in_history" , blank = True
3413+ )
3414+ removed_references = models .ManyToManyField (
3415+ "AdvisoryReference" , related_name = "removed_in_history" , blank = True
3416+ )
3417+
3418+ added_aliases = models .ManyToManyField (
3419+ "AdvisoryAlias" , related_name = "added_in_history" , blank = True
3420+ )
3421+ removed_aliases = models .ManyToManyField (
3422+ "AdvisoryAlias" , related_name = "removed_in_history" , blank = True
3423+ )
3424+
3425+ added_weaknesses = models .ManyToManyField (
3426+ "AdvisoryWeakness" , related_name = "added_in_history" , blank = True
3427+ )
3428+ removed_weaknesses = models .ManyToManyField (
3429+ "AdvisoryWeakness" , related_name = "removed_in_history" , blank = True
3430+ )
3431+
3432+ added_patches = models .ManyToManyField ("Patch" , related_name = "added_in_history" , blank = True )
3433+ removed_patches = models .ManyToManyField ("Patch" , related_name = "removed_in_history" , blank = True )
3434+
3435+ def to_diff_dict (self ) -> dict :
3436+ """
3437+ Serialize the relational diff back to the dict format consumed by
3438+ format_diff_for_ui and the existing template.
3439+ """
3440+ diff = {}
3441+
3442+ if self .summary_added is not None or self .summary_removed is not None :
3443+ diff ["summary" ] = {"old" : self .summary_removed , "new" : self .summary_added }
3444+
3445+ if self .url_added is not None or self .url_removed is not None :
3446+ diff ["url" ] = {"old" : self .url_removed , "new" : self .url_added }
3447+
3448+ def _serialize_history_diff (key , added_qs , removed_qs , serializer ):
3449+ added = list (added_qs )
3450+ removed = list (removed_qs )
3451+ if added or removed :
3452+ diff [key ] = {
3453+ "added" : [serializer (obj ) for obj in added ],
3454+ "removed" : [serializer (obj ) for obj in removed ],
3455+ }
3456+
3457+ _serialize_history_diff (
3458+ "severities" ,
3459+ self .added_severities .all (),
3460+ self .removed_severities .all (),
3461+ lambda s : s .to_dict (),
3462+ )
3463+
3464+ _serialize_history_diff (
3465+ "affected_packages" ,
3466+ self .added_impacted_packages .all (),
3467+ self .removed_impacted_packages .all (),
3468+ lambda p : p .to_dict (),
3469+ )
3470+
3471+ _serialize_history_diff (
3472+ "references" ,
3473+ self .added_references .all (),
3474+ self .removed_references .all (),
3475+ lambda r : r .to_dict (),
3476+ )
3477+
3478+ _serialize_history_diff (
3479+ "aliases" ,
3480+ self .added_aliases .all (),
3481+ self .removed_aliases .all (),
3482+ lambda a : a .alias ,
3483+ )
3484+
3485+ _serialize_history_diff (
3486+ "weaknesses" ,
3487+ self .added_weaknesses .all (),
3488+ self .removed_weaknesses .all (),
3489+ lambda w : w .cwe_id ,
3490+ )
3491+
3492+ _serialize_history_diff (
3493+ "patches" ,
3494+ self .added_patches .all (),
3495+ self .removed_patches .all (),
3496+ lambda p : p .to_dict (),
3497+ )
3498+
3499+ return diff
3500+
3501+
33703502class ImpactedPackage (models .Model ):
33713503 """
33723504 Represents a single impact for an advisory, including affected range and fixed version and
0 commit comments