@@ -705,7 +705,7 @@ class Package(PackageURLMixin):
705705 "indicate greater vulnerability risk for the package." ,
706706 )
707707
708- version_rank = models .IntegerField (
708+ version_rank = models .FloatField (
709709 help_text = "Rank of the version to support ordering by version. Rank "
710710 "zero means the rank has not been defined yet" ,
711711 default = 0 ,
@@ -749,6 +749,65 @@ class Meta:
749749 def __str__ (self ):
750750 return self .package_url
751751
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+ if self .version_rank > 0 :
763+ return self .version_rank
764+
765+ # Determine the version_class for this package's type
766+ version_class = RANGE_CLASS_BY_SCHEMES .get (self .type ).version_class
767+ if not version_class :
768+ raise ValueError (f"No version_class defined for package type { self .type } " )
769+
770+ group_packages = Package .objects .filter (
771+ type = self .type ,
772+ namespace = self .namespace ,
773+ name = self .name ,
774+ )
775+
776+ # if all packages have version rank 0
777+
778+ if all (p .version_rank == 0 for p in group_packages ):
779+ sorted_packages = sorted (group_packages , key = lambda p : version_class (p .version ))
780+ for rank , package in enumerate (sorted_packages ):
781+ package .version_rank = rank
782+ Package .objects .bulk_update (sorted_packages , fields = ["version_rank" ])
783+ return self .version_rank
784+
785+ group_packages = group_packages .exclude (version_rank = 0 )
786+ sorted_packages = sorted (group_packages , key = lambda p : version_class (p .version ))
787+ current_version = version_class (self .version )
788+
789+ lower_package , higher_package = None , None
790+ for package in sorted_packages :
791+ package_version = version_class (package .version )
792+ if package_version < current_version :
793+ lower_package = package
794+ elif package_version > current_version :
795+ higher_package = package
796+ break
797+
798+ if lower_package and higher_package :
799+ # Interpolate rank between neighbors
800+ return (lower_package .version_rank + higher_package .version_rank ) / 2
801+ elif lower_package :
802+ # If only lower neighbor exists, assign a rank slightly higher than the lower neighbor
803+ return lower_package .version_rank + 1
804+ elif higher_package :
805+ # If only higher neighbor exists, assign a rank slightly lower than the higher neighbor
806+ return higher_package .version_rank - 1
807+ else :
808+ # No neighbors with version_rank; return default rank (e.g., 0)
809+ return 0
810+
752811 @property
753812 def affected_by (self ):
754813 """
@@ -795,14 +854,6 @@ def get_details_url(self, request):
795854
796855 return reverse ("package_details" , kwargs = {"purl" : self .purl }, request = request )
797856
798- def sort_by_version (self , packages ):
799- """
800- Return a sequence of `packages` sorted by version.
801- """
802- if not packages :
803- return []
804- return sorted (packages , key = lambda x : self .version_class (x .version ))
805-
806857 @cached_property
807858 def version_class (self ):
808859 range_class = RANGE_CLASS_BY_SCHEMES .get (self .type )
@@ -837,19 +888,21 @@ def get_non_vulnerable_versions(self):
837888 Return a tuple of the next and latest non-vulnerable versions as Package instance.
838889 Return a tuple of (None, None) if there is no non-vulnerable version.
839890 """
891+ if self .version_rank == 0 :
892+ self .calculate_version_rank
840893 non_vulnerable_versions = Package .objects .get_fixed_by_package_versions (
841894 self , fix = False
842895 ).only_non_vulnerable ()
843- sorted_versions = self . sort_by_version ( non_vulnerable_versions )
896+ sorted_versions = non_vulnerable_versions
844897
845- later_non_vulnerable_versions = [
846- non_vuln_ver
847- for non_vuln_ver in sorted_versions
848- if self . version_class ( non_vuln_ver . version ) > self . current_version
849- ]
898+ later_non_vulnerable_versions = non_vulnerable_versions . filter (
899+ version_rank__gt = self . version_rank
900+ )
901+
902+ later_non_vulnerable_versions = list ( later_non_vulnerable_versions )
850903
851904 if later_non_vulnerable_versions :
852- sorted_versions = self . sort_by_version ( later_non_vulnerable_versions )
905+ sorted_versions = later_non_vulnerable_versions
853906 next_non_vulnerable = sorted_versions [0 ]
854907 latest_non_vulnerable = sorted_versions [- 1 ]
855908 return next_non_vulnerable , latest_non_vulnerable
@@ -878,6 +931,8 @@ def get_affecting_vulnerabilities(self):
878931 Return a list of vulnerabilities that affect this package together with information regarding
879932 the versions that fix the vulnerabilities.
880933 """
934+ if self .version_rank == 0 :
935+ self .calculate_version_rank
881936 package_details_vulns = []
882937
883938 fixed_by_packages = Package .objects .get_fixed_by_package_versions (self , fix = True )
@@ -901,12 +956,13 @@ def get_affecting_vulnerabilities(self):
901956 if fixed_version > self .current_version :
902957 later_fixed_packages .append (fixed_pkg )
903958
904- next_fixed_package = None
905959 next_fixed_package_vulns = []
906960
907961 sort_fixed_by_packages_by_version = []
908962 if later_fixed_packages :
909- sort_fixed_by_packages_by_version = self .sort_by_version (later_fixed_packages )
963+ sort_fixed_by_packages_by_version = sorted (
964+ later_fixed_packages , key = lambda p : p .version_rank
965+ )
910966
911967 fixed_by_pkgs = []
912968
@@ -936,6 +992,7 @@ def fixing_vulnerabilities(self):
936992 """
937993 Return a queryset of Vulnerabilities that are fixed by this package.
938994 """
995+ print ("A" )
939996 return self .fixed_by_vulnerabilities .all ()
940997
941998 @property
0 commit comments