77from django .db .models import DurationField , ExpressionWrapper , F
88from django .utils import timezone
99
10- from clubs .models import Club , ClubFair , EventShowing , Membership
10+ from clubs .models import Club , ClubFair , EventShowing , Membership , RankingWeights
11+
12+
13+ # Default weight values mirroring the historic constant scoring system
14+ DEFAULT_WEIGHTS = {
15+ "inactive_penalty" : - 1000 ,
16+ "favorites_per" : 1 / 25 ,
17+ "tags_good" : 15 ,
18+ "tags_many" : 7 ,
19+ "officer_bonus" : 15 ,
20+ "member_base" : 10 ,
21+ "member_per" : 1 / 10 ,
22+ "logo_bonus" : 15 ,
23+ "subtitle_bad" : - 10 ,
24+ "subtitle_good" : 5 ,
25+ "images_bonus" : 3 ,
26+ "desc_short" : 25 ,
27+ "desc_med" : 10 ,
28+ "desc_long" : 10 ,
29+ "fair_bonus" : 10 ,
30+ "application_bonus" : 25 ,
31+ "today_event_base" : 10 ,
32+ "today_event_good" : 10 ,
33+ "week_event_base" : 5 ,
34+ "week_event_good" : 5 ,
35+ "email_bonus" : 10 ,
36+ "social_bonus" : 10 ,
37+ "howto_penalty" : - 30 ,
38+ "outdated_penalty" : - 10 ,
39+ "testimonial_one" : 10 ,
40+ "testimonial_three" : 5 ,
41+ "random_scale" : 25 ,
42+ }
1143
1244
1345class Command (BaseCommand ):
@@ -62,6 +94,16 @@ def set_recruiting_statuses(self):
6294 def rank (self ):
6395 count = 0
6496
97+ # Retrieve ranking weights singleton
98+ weights = RankingWeights .get ()
99+
100+ def _w (key ):
101+ """Fetch weight: RankingWeights field ➔ DEFAULT_WEIGHTS ➔ 1.0."""
102+ val = getattr (weights , key , None )
103+ if val is not None :
104+ return val
105+ return DEFAULT_WEIGHTS .get (key , 1.0 )
106+
65107 clubs = Club .objects .prefetch_related (
66108 "favorite_set" ,
67109 "tags" ,
@@ -77,74 +119,74 @@ def rank(self):
77119
78120 # inactive clubs get deprioritized
79121 if not club .active :
80- ranking -= 1000
122+ ranking += _w ( "inactive_penalty" )
81123
82124 # small points for favorites
83- ranking += club .favorite_set .count () / 25
125+ ranking += club .favorite_set .count () * _w ( "favorites_per" )
84126
85127 # points for minimum amount of tags
86128 tags = club .tags .count ()
87- if tags >= 3 and tags <= 7 :
88- ranking += 15
129+ if 3 <= tags <= 7 :
130+ ranking += _w ( "tags_good" )
89131 elif tags > 7 :
90- ranking += 7
132+ ranking += _w ( "tags_many" )
91133
92134 # lots of points for officers
93135 officers = club .membership_set .filter (
94136 active = True , role__lte = Membership .ROLE_OFFICER
95137 ).count ()
96138 if officers >= 3 :
97- ranking += 15
139+ ranking += _w ( "officer_bonus" )
98140
99141 # ordinary members give even more points
100142 members = club .membership_set .filter (
101143 active = True , role__gte = Membership .ROLE_MEMBER
102144 ).count ()
103145 if members >= 3 :
104- ranking += 10
105- ranking += members / 10
146+ ranking += _w ( "member_base" )
147+ ranking += members * _w ( "member_per" )
106148
107149 # points for logo
108150 if club .image is not None :
109- ranking += 15
151+ ranking += _w ( "logo_bonus" )
110152
111153 # points for subtitle
112154 subtitle = club .subtitle .strip ()
113155 if subtitle .lower () == "your subtitle here" :
114- ranking -= 10
156+ ranking += _w ( "subtitle_bad" )
115157 elif len (subtitle ) > 3 :
116- ranking += 5
158+ ranking += _w ( "subtitle_good" )
117159
118160 # images in description? awesome
119161 if "<img" in club .description or "<iframe" in club .description :
120- ranking += 3
162+ ranking += _w ( "images_bonus" )
121163
122164 # points for longer descriptions
123165 cleaned_description = bleach .clean (
124166 club .description , tags = [], attributes = {}, styles = [], strip = True
125167 ).strip ()
126168
127169 if len (cleaned_description ) > 25 :
128- ranking += 25
170+ ranking += _w ( "desc_short" )
129171
130172 if len (cleaned_description ) > 250 :
131- ranking += 10
173+ ranking += _w ( "desc_med" )
132174
133175 if len (cleaned_description ) > 1000 :
134- ranking += 10
176+ ranking += _w ( "desc_long" )
135177
136178 # points for fair
137179 now = timezone .now ()
138180 if ClubFair .objects .filter (
139181 end_time__gte = now , participating_clubs = club
140182 ).exists ():
141- ranking += 10
183+ ranking += _w ( "fair_bonus" )
142184
143185 # points for club applications
144186 if club .clubapplication_set .filter (
145187 application_start_time__lte = now , application_end_time__gte = now
146188 ).exists ():
147- ranking += 25
189+ ranking += _w ( "application_bonus" )
148190
149191 # points for events
150192 # Get all events with showings today
@@ -172,15 +214,15 @@ def rank(self):
172214 )
173215
174216 if short_showings :
175- ranking += 10
217+ ranking += _w ( "today_event_base" )
176218 # Check for events with good descriptions and images
177219 if all (
178220 len (e .description ) >= 3
179221 and e .description not in {"Replace this description!" }
180222 and e .image is not None
181223 for e in today_events
182224 ):
183- ranking += 10
225+ ranking += _w ( "today_event_good" )
184226
185227 # Get all events with showings in the next week
186228 close_showings = EventShowing .objects .filter (
@@ -207,19 +249,19 @@ def rank(self):
207249 )
208250
209251 if short_showings :
210- ranking += 5
252+ ranking += _w ( "week_event_base" )
211253 # Check for events with good descriptions and images
212254 if all (
213255 len (e .description ) > 3
214256 and e .description not in {"Replace this description!" }
215257 and e .image is not None
216258 for e in close_events
217259 ):
218- ranking += 5
260+ ranking += _w ( "week_event_good" )
219261
220262 # points for public contact email
221263 if club .email and club .email_public :
222- ranking += 10
264+ ranking += _w ( "email_bonus" )
223265
224266 # points for social links
225267 social_fields = [
@@ -237,28 +279,28 @@ def rank(self):
237279 ]
238280 has_fields = [field for field in social_fields if field ]
239281 if len (has_fields ) >= 2 :
240- ranking += 10
282+ ranking += _w ( "social_bonus" )
241283
242284 # points for how to get involved
243285 if len (club .how_to_get_involved .strip ()) <= 3 :
244- ranking -= 30
286+ ranking += _w ( "howto_penalty" )
245287
246288 # points for updated
247289 if club .updated_at < now - datetime .timedelta (days = 30 * 8 ):
248- ranking -= 10
290+ ranking += _w ( "outdated_penalty" )
249291
250292 # points for testimonials
251293 num_testimonials = club .testimonials .count ()
252294 if num_testimonials >= 1 :
253- ranking += 10
295+ ranking += _w ( "testimonial_one" )
254296 if num_testimonials >= 3 :
255- ranking += 5
297+ ranking += _w ( "testimonial_three" )
256298
257299 # random number, mostly shuffles similar clubs with average of 25 points
258300 # but with long right tail to periodically feature less popular clubs
259301 # given ~700 active clubs, multiplier c, expected # clubs with rand > cd
260302 # is 257, 95, 35, 13, 5, 2, 1 for c = 1, 2, 3, 4, 5, 6, 7
261- ranking += np .random .standard_exponential () * 25
303+ ranking += np .random .standard_exponential () * _w ( "random_scale" )
262304
263305 club .rank = floor (ranking )
264306 club .skip_history_when_saving = True
0 commit comments