@@ -4038,103 +4038,6 @@ def as_spdx(self):
40384038 )
40394039
40404040
4041- class DiscoveredPackageScore (UUIDPKModel , PackageScoreMixin ):
4042- def __str__ (self ):
4043- return self .score or str (self .uuid )
4044-
4045- discovered_package = models .ForeignKey (
4046- DiscoveredPackage ,
4047- related_name = "discovered_packages_score" ,
4048- help_text = _ ("The package for which the score is given" ),
4049- on_delete = models .CASCADE ,
4050- editable = False ,
4051- blank = True ,
4052- null = True ,
4053- )
4054-
4055- def parse_score_date (date_str , formats = None ):
4056- """
4057- Parse a date string into a timezone-aware datetime object,
4058- or return None if parsing fails.
4059- """
4060- if not formats :
4061- formats = ["%Y-%m-%d" , "%Y-%m-%dT%H:%M:%SZ" ]
4062-
4063- if date_str :
4064- for fmt in formats :
4065- try :
4066- naive_datetime = datetime .strptime (date_str , fmt )
4067- return timezone .make_aware (
4068- naive_datetime , timezone .get_current_timezone ()
4069- )
4070- except ValueError :
4071- continue
4072-
4073- # Return None if date_str is None or parsing fails
4074- return None
4075-
4076- @classmethod
4077- @transaction .atomic ()
4078- def create_from_scorecard_data (
4079- cls , discovered_package , scorecard_data , scoring_tool = None
4080- ):
4081- """Create ScoreCard object from scorecard data and discovered package"""
4082- final_data = {
4083- "score" : scorecard_data .score ,
4084- "scoring_tool_version" : scorecard_data .scoring_tool_version ,
4085- "scoring_tool_documentation_url" : (
4086- scorecard_data .scoring_tool_documentation_url
4087- ),
4088- "score_date" : cls .parse_score_date (scorecard_data .score_date ),
4089- }
4090-
4091- scorecard_object = cls .objects .create (
4092- ** final_data ,
4093- discovered_package = discovered_package ,
4094- scoring_tool = scoring_tool ,
4095- )
4096-
4097- for check in scorecard_data .checks :
4098- ScorecardCheck .create_from_data (package_score = scorecard_object , check = check )
4099-
4100- return scorecard_object
4101-
4102- @classmethod
4103- def create_from_package_and_scorecard (cls , scorecard_data , package ):
4104- score_object = cls .create_from_scorecard_data (
4105- discovered_package = package ,
4106- scorecard_data = scorecard_data ,
4107- scoring_tool = "ossf-scorecard" ,
4108- )
4109- return score_object
4110-
4111-
4112- class ScorecardCheck (UUIDPKModel , ScorecardChecksMixin ):
4113- def __str__ (self ):
4114- return self .check_score or str (self .uuid )
4115-
4116- for_package_score = models .ForeignKey (
4117- DiscoveredPackageScore ,
4118- related_name = "discovered_packages_score_checks" ,
4119- help_text = _ ("The checks for which the score is given" ),
4120- on_delete = models .CASCADE ,
4121- editable = False ,
4122- blank = True ,
4123- null = True ,
4124- )
4125-
4126- @classmethod
4127- def create_from_data (cls , package_score , check ):
4128- """Create a ScorecardCheck instance from provided data."""
4129- return cls .objects .create (
4130- check_name = check .check_name ,
4131- check_score = check .check_score ,
4132- reason = check .reason or "" ,
4133- details = check .details or [],
4134- for_package_score = package_score ,
4135- )
4136-
4137-
41384041def normalize_package_url_data (purl_mapping , ignore_nulls = False ):
41394042 """
41404043 Normalize a mapping of purl data so database queries with
@@ -4376,3 +4279,122 @@ def create_auth_token(sender, instance=None, created=False, **kwargs):
43764279 """Create an API key token on user creation, using the signal system."""
43774280 if created :
43784281 Token .objects .create (user_id = instance .pk )
4282+
4283+
4284+ class DiscoveredPackageScore (UUIDPKModel , PackageScoreMixin ):
4285+ """Represents a security or quality score for a DiscoveredPackage."""
4286+
4287+ discovered_package = models .ForeignKey (
4288+ DiscoveredPackage ,
4289+ related_name = "discovered_packages_score" ,
4290+ help_text = _ ("The package for which the score is given" ),
4291+ on_delete = models .CASCADE ,
4292+ editable = False ,
4293+ )
4294+
4295+ class Meta :
4296+ verbose_name = "discovered package score"
4297+ verbose_name_plural = "discovered package scores"
4298+ ordering = ["-score" ]
4299+ indexes = [
4300+ models .Index (fields = ["score" ]),
4301+ models .Index (fields = ["scoring_tool_version" ]),
4302+ ]
4303+
4304+ def __str__ (self ):
4305+ return self .score or str (self .uuid )
4306+
4307+ @classmethod
4308+ def parse_score_date (cls , date_str , formats = None ):
4309+ """
4310+ Parse a date string into a timezone-aware datetime object,
4311+ or return None if parsing fails.
4312+ """
4313+ if not formats :
4314+ formats = ["%Y-%m-%d" , "%Y-%m-%dT%H:%M:%SZ" ]
4315+
4316+ if date_str :
4317+ for fmt in formats :
4318+ try :
4319+ naive_datetime = datetime .strptime (date_str , fmt )
4320+ return timezone .make_aware (
4321+ naive_datetime , timezone .get_current_timezone ()
4322+ )
4323+ except ValueError :
4324+ continue
4325+
4326+ # Return None if date_str is None or parsing fails
4327+ return None
4328+
4329+ @classmethod
4330+ @transaction .atomic ()
4331+ def create_from_scorecard_data (
4332+ cls , discovered_package , scorecard_data , scoring_tool = None
4333+ ):
4334+ """Create ScoreCard object from scorecard data and discovered package"""
4335+ final_data = {
4336+ "score" : scorecard_data .score ,
4337+ "scoring_tool_version" : scorecard_data .scoring_tool_version ,
4338+ "scoring_tool_documentation_url" : (
4339+ scorecard_data .scoring_tool_documentation_url
4340+ ),
4341+ "score_date" : cls .parse_score_date (scorecard_data .score_date ),
4342+ }
4343+
4344+ scorecard_object = cls .objects .create (
4345+ ** final_data ,
4346+ discovered_package = discovered_package ,
4347+ scoring_tool = scoring_tool ,
4348+ )
4349+
4350+ for check in scorecard_data .checks :
4351+ ScorecardCheck .create_from_data (package_score = scorecard_object , check = check )
4352+
4353+ return scorecard_object
4354+
4355+ @classmethod
4356+ def create_from_package_and_scorecard (cls , scorecard_data , package ):
4357+ score_object = cls .create_from_scorecard_data (
4358+ discovered_package = package ,
4359+ scorecard_data = scorecard_data ,
4360+ scoring_tool = "ossf-scorecard" ,
4361+ )
4362+ return score_object
4363+
4364+
4365+ class ScorecardCheck (UUIDPKModel , ScorecardChecksMixin ):
4366+ """
4367+ Represents an individual check within a Scorecard evaluation for a
4368+ DiscoveredPackageScore.
4369+ """
4370+
4371+ for_package_score = models .ForeignKey (
4372+ DiscoveredPackageScore ,
4373+ related_name = "discovered_packages_score_checks" ,
4374+ help_text = _ ("The checks for which the score is given" ),
4375+ on_delete = models .CASCADE ,
4376+ editable = False ,
4377+ )
4378+
4379+ class Meta :
4380+ verbose_name = "scorecard check"
4381+ verbose_name_plural = "scorecard checks"
4382+ ordering = ["-check_score" ]
4383+ indexes = [
4384+ models .Index (fields = ["check_score" ]),
4385+ models .Index (fields = ["check_name" ]),
4386+ ]
4387+
4388+ def __str__ (self ):
4389+ return self .check_score or str (self .uuid )
4390+
4391+ @classmethod
4392+ def create_from_data (cls , package_score , check ):
4393+ """Create a ScorecardCheck instance from provided data."""
4394+ return cls .objects .create (
4395+ check_name = check .check_name ,
4396+ check_score = check .check_score ,
4397+ reason = check .reason or "" ,
4398+ details = check .details or [],
4399+ for_package_score = package_score ,
4400+ )
0 commit comments