@@ -705,6 +705,12 @@ class Package(PackageURLMixin):
705705 "indicate greater vulnerability risk for the package." ,
706706 )
707707
708+ version_rank = models .IntegerField (
709+ help_text = "Rank of the version to support ordering by version. Rank "
710+ "zero means the rank has not been defined yet" ,
711+ default = 0 ,
712+ )
713+
708714 objects = PackageQuerySet .as_manager ()
709715
710716 def save (self , * args , ** kwargs ):
@@ -738,11 +744,34 @@ def purl(self):
738744
739745 class Meta :
740746 unique_together = ["type" , "namespace" , "name" , "version" , "qualifiers" , "subpath" ]
741- ordering = ["type" , "namespace" , "name" , "version" , "qualifiers" , "subpath" ]
747+ ordering = ["type" , "namespace" , "name" , "version_rank" , " version" , "qualifiers" , "subpath" ]
742748
743749 def __str__ (self ):
744750 return self .package_url
745751
752+ @property
753+ def calculate_version_rank (self ):
754+ """
755+ Calculate and return the `version_rank` for a package that does not have one.
756+ If this package already has a `version_rank`, return it.
757+
758+ The calculated rank will be interpolated between two packages that have
759+ `version_rank` values and are closest to this package in terms of version order.
760+ """
761+
762+ group_packages = Package .objects .filter (
763+ type = self .type ,
764+ namespace = self .namespace ,
765+ name = self .name ,
766+ )
767+
768+ if any (p .version_rank == 0 for p in group_packages ):
769+ sorted_packages = sorted (group_packages , key = lambda p : self .version_class (p .version ))
770+ for rank , package in enumerate (sorted_packages , start = 1 ):
771+ package .version_rank = rank
772+ Package .objects .bulk_update (sorted_packages , fields = ["version_rank" ])
773+ return self .version_rank
774+
746775 @property
747776 def affected_by (self ):
748777 """
@@ -789,14 +818,6 @@ def get_details_url(self, request):
789818
790819 return reverse ("package_details" , kwargs = {"purl" : self .purl }, request = request )
791820
792- def sort_by_version (self , packages ):
793- """
794- Return a sequence of `packages` sorted by version.
795- """
796- if not packages :
797- return []
798- return sorted (packages , key = lambda x : self .version_class (x .version ))
799-
800821 @cached_property
801822 def version_class (self ):
802823 range_class = RANGE_CLASS_BY_SCHEMES .get (self .type )
@@ -831,19 +852,20 @@ def get_non_vulnerable_versions(self):
831852 Return a tuple of the next and latest non-vulnerable versions as Package instance.
832853 Return a tuple of (None, None) if there is no non-vulnerable version.
833854 """
855+ if self .version_rank == 0 :
856+ self .calculate_version_rank
834857 non_vulnerable_versions = Package .objects .get_fixed_by_package_versions (
835858 self , fix = False
836859 ).only_non_vulnerable ()
837- sorted_versions = self .sort_by_version (non_vulnerable_versions )
838860
839- later_non_vulnerable_versions = [
840- non_vuln_ver
841- for non_vuln_ver in sorted_versions
842- if self . version_class ( non_vuln_ver . version ) > self . current_version
843- ]
861+ later_non_vulnerable_versions = non_vulnerable_versions . filter (
862+ version_rank__gt = self . version_rank
863+ )
864+
865+ later_non_vulnerable_versions = list ( later_non_vulnerable_versions )
844866
845867 if later_non_vulnerable_versions :
846- sorted_versions = self . sort_by_version ( later_non_vulnerable_versions )
868+ sorted_versions = later_non_vulnerable_versions
847869 next_non_vulnerable = sorted_versions [0 ]
848870 latest_non_vulnerable = sorted_versions [- 1 ]
849871 return next_non_vulnerable , latest_non_vulnerable
@@ -872,6 +894,8 @@ def get_affecting_vulnerabilities(self):
872894 Return a list of vulnerabilities that affect this package together with information regarding
873895 the versions that fix the vulnerabilities.
874896 """
897+ if self .version_rank == 0 :
898+ self .calculate_version_rank
875899 package_details_vulns = []
876900
877901 fixed_by_packages = Package .objects .get_fixed_by_package_versions (self , fix = True )
@@ -895,12 +919,13 @@ def get_affecting_vulnerabilities(self):
895919 if fixed_version > self .current_version :
896920 later_fixed_packages .append (fixed_pkg )
897921
898- next_fixed_package = None
899922 next_fixed_package_vulns = []
900923
901924 sort_fixed_by_packages_by_version = []
902925 if later_fixed_packages :
903- sort_fixed_by_packages_by_version = self .sort_by_version (later_fixed_packages )
926+ sort_fixed_by_packages_by_version = sorted (
927+ later_fixed_packages , key = lambda p : p .version_rank
928+ )
904929
905930 fixed_by_pkgs = []
906931
@@ -930,6 +955,7 @@ def fixing_vulnerabilities(self):
930955 """
931956 Return a queryset of Vulnerabilities that are fixed by this package.
932957 """
958+ print ("A" )
933959 return self .fixed_by_vulnerabilities .all ()
934960
935961 @property
0 commit comments