11
11
12
12
logger = logging .getLogger (__name__ )
13
13
14
+ # Year validation constants
15
+ MIN_VALID_YEAR = 1900
16
+ MAX_VALID_YEAR = 2100
17
+
14
18
15
19
class Command (BaseCommand ):
16
20
help = "Import awards from the OWASP awards YAML file"
@@ -19,55 +23,112 @@ def handle(self, *args, **kwargs) -> None:
19
23
"""Handle the command execution."""
20
24
self .stdout .write ("Syncing OWASP awards..." )
21
25
22
- data = yaml .safe_load (
23
- get_repository_file_content (
24
- "https://raw.githubusercontent.com/OWASP/owasp.github.io/main/_data/awards.yml"
26
+ url = "https://raw.githubusercontent.com/OWASP/owasp.github.io/main/_data/awards.yml"
27
+ raw = get_repository_file_content (url )
28
+ if not raw :
29
+ self .stderr .write (self .style .WARNING ("No awards data fetched; aborting." ))
30
+ return
31
+ try :
32
+ data = yaml .safe_load (raw ) or []
33
+ except yaml .YAMLError as e :
34
+ self .stderr .write (self .style .ERROR (f"Failed to parse awards YAML: { e } " ))
35
+ return
36
+ if not isinstance (data , list ):
37
+ self .stderr .write (
38
+ self .style .WARNING ("Unexpected awards YAML structure; expected a list." )
25
39
)
26
- )
40
+ return
27
41
28
42
awards_to_save = []
43
+ skipped_count = 0
29
44
for item in data :
30
45
if item .get ("type" ) == "award" :
31
46
winners = item .get ("winners" , [])
32
47
for winner in winners :
33
48
award = self ._create_or_update_award (item , winner )
34
49
if award :
35
50
awards_to_save .append (award )
51
+ else :
52
+ skipped_count += 1
36
53
37
- Award .bulk_save (awards_to_save )
54
+ Award .bulk_save (awards_to_save , fields = ( "category" , "description" , "year" , "user" ) )
38
55
self .stdout .write (self .style .SUCCESS (f"Successfully synced { len (awards_to_save )} awards" ))
56
+ if skipped_count :
57
+ self .stdout .write (
58
+ self .style .WARNING (f"Skipped { skipped_count } awards due to invalid data" )
59
+ )
39
60
40
61
def _create_or_update_award (self , award_data , winner_data ):
41
62
"""Create or update award instance."""
42
- name = f"{ award_data ['title' ]} - { winner_data ['name' ]} ({ award_data ['year' ]} )"
63
+ # Safely extract values with defaults
64
+ title = award_data .get ("title" , "" )
65
+ category = award_data .get ("category" , "" )
66
+
67
+ # Validate and parse year
68
+ try :
69
+ year = int (award_data .get ("year" , 0 ))
70
+ if year <= 0 or year < MIN_VALID_YEAR or year > MAX_VALID_YEAR :
71
+ logger .warning ("Invalid year %s for award %s, skipping" , year , title )
72
+ return None
73
+ except (ValueError , TypeError ):
74
+ logger .warning (
75
+ "Could not parse year %s for award %s, skipping" , award_data .get ("year" ), title
76
+ )
77
+ return None
78
+
79
+ # Handle winner_data being string or dict
80
+ if isinstance (winner_data , str ):
81
+ winner_name = winner_data
82
+ winner_info = ""
83
+ else :
84
+ # Prefer explicit GitHub login over name
85
+ login = winner_data .get ("login" ) or winner_data .get ("github" )
86
+ if login :
87
+ login = login .lstrip ("@" )
88
+ # Skip bot accounts
89
+ if "bot" in login .lower () or login .lower ().endswith ("[bot]" ):
90
+ logger .warning ("Skipping bot account: %s" , login )
91
+ return None
92
+ winner_name = login
93
+ else :
94
+ winner_name = winner_data .get ("name" , "" )
95
+ winner_info = winner_data .get ("info" , "" )
96
+
97
+ name = f"{ title } - { winner_name } ({ year } )"
43
98
44
99
try :
45
100
award = Award .objects .get (name = name )
46
101
except Award .DoesNotExist :
47
102
award = Award (name = name )
48
103
49
- award .category = award_data . get ( " category" , "" )
50
- award .description = winner_data . get ( "info" , "" )
51
- award .year = award_data . get ( " year" , 0 )
104
+ award .category = category
105
+ award .description = winner_info
106
+ award .year = year
52
107
53
- # Try to match user by name
54
- user = self ._match_user (winner_data ["name" ])
55
- if user :
56
- award .user = user
57
- else :
58
- logger .warning ("Could not match user for award winner: %s" , winner_data ["name" ])
108
+ # Only set user if not already reviewed
109
+ if not (award .user and award .is_reviewed ):
110
+ user = self ._match_user (winner_name )
111
+ if user :
112
+ award .user = user
113
+ else :
114
+ logger .warning ("Could not match user for award winner: %s" , winner_name )
59
115
60
116
return award
61
117
62
118
def _match_user (self , winner_name ):
63
119
"""Try to match award winner with existing user."""
64
- # Try exact name match first
65
- user = User .objects .filter (name__iexact = winner_name ).first ()
66
- if user :
67
- return user
120
+ winner_name = winner_name .strip ()
68
121
69
- # Try login match (GitHub username)
70
- user = User .objects .filter (login__iexact = winner_name ).first ()
122
+ # Check if it looks like a GitHub handle
123
+ if winner_name .startswith ("@" ) or (" " not in winner_name and winner_name ):
124
+ # Strip leading @ and try login match first
125
+ login_name = winner_name .lstrip ("@" )
126
+ user = User .objects .filter (login__iexact = login_name ).first ()
127
+ if user :
128
+ return user
129
+
130
+ # Try exact name match
131
+ user = User .objects .filter (name__iexact = winner_name ).first ()
71
132
if user :
72
133
return user
73
134
0 commit comments