1- import datetime
1+ import datetime , re
22from collections import defaultdict
33
44from django .contrib .sites .models import Site
55from django .db import models
66
77from ietf .group .models import Group , Role
8- from ietf .doc .models import Document , DocEvent , State , LastCallDocEvent
8+ from ietf .doc .models import Document , DocEvent , State , LastCallDocEvent , DocumentAuthor , DocAlias
99from ietf .iesg .models import TelechatDate
10- from ietf .person .models import Person
10+ from ietf .person .models import Person , Email
1111from ietf .ietfauth .utils import has_role , is_authorized_in_doc_stream
1212from ietf .review .models import ReviewRequest , ReviewRequestStateName , ReviewTypeName , Reviewer
1313from ietf .utils .mail import send_mail
@@ -275,38 +275,80 @@ def extract_revision_ordered_review_requests_for_documents(queryset, names):
275275
276276 return res
277277
278- def construct_review_request_assignment_choices (possible_emails , team , review_req = None ):
279- possible_emails = list (possible_emails )
278+ def setup_reviewer_field (field , review_req ):
279+ field .queryset = field .queryset .filter (role__name = "reviewer" , role__group = review_req .team )
280+ if review_req .reviewer :
281+ field .initial = review_req .reviewer_id
282+
283+ choices = make_assignment_choices (field .queryset , review_req )
284+ if not field .required :
285+ choices = [("" , field .empty_label )] + choices
286+
287+ field .choices = choices
288+
289+ def make_assignment_choices (email_queryset , review_req ):
290+ doc = review_req .doc
291+ team = review_req .team
292+
293+ possible_emails = list (email_queryset )
294+
295+ aliases = DocAlias .objects .filter (document = doc ).values_list ("name" , flat = True )
280296
281297 reviewers = { r .person_id : r for r in Reviewer .objects .filter (team = team , person__in = [e .person_id for e in possible_emails ]) }
282298
299+ # time since past assignment
283300 latest_assignment_for_reviewer = dict (ReviewRequest .objects .filter (
284301 reviewer__in = possible_emails ,
285302 ).values_list ("reviewer" ).annotate (models .Max ("time" )))
286303
304+ # previous review of document
305+ has_reviewed_previous = ReviewRequest .objects .filter (
306+ doc = doc ,
307+ reviewer__in = possible_emails ,
308+ state = "completed" ,
309+ )
310+
311+ if review_req .pk is not None :
312+ has_reviewed_previous = has_reviewed_previous .exclude (pk = review_req .pk )
313+
314+ has_reviewed_previous = set (has_reviewed_previous .values_list ("reviewer" , flat = True ))
315+
316+ # review indications
317+ would_like_to_review = set () # FIXME: fill in
318+
319+ # connections
320+ connections = {}
321+ # examine the closest connections last to let them override
322+ for e in Email .objects .filter (pk__in = possible_emails , person = doc .ad_id ):
323+ connections [e ] = "is associated Area Director"
324+ for r in Role .objects .filter (group = doc .group_id , email__in = possible_emails ).select_related ("name" ):
325+ connections [r .email_id ] = "is group {}" .format (r .name )
326+ if doc .shepherd_id :
327+ connections [doc .shepherd_id ] = "is shepherd of document"
328+ for e in DocumentAuthor .objects .filter (document = doc , author__in = possible_emails ).values_list ("author" , flat = True ):
329+ connections [e ] = "is author of document"
330+
287331 now = datetime .datetime .now ()
288332
289- rankings = []
333+ def add_boolean_score (scores , direction , expr , expl ):
334+ scores .append (int (bool (expr )) * direction )
335+ if expr :
336+ explanations .append (expl )
337+
338+ ranking = []
290339 for e in possible_emails :
291340 reviewer = reviewers .get (e .person_id )
292341 if not reviewer :
293342 reviewer = Reviewer ()
294343
344+ explanations = []
345+ scores = [] # build up score in separate independent components
346+
295347 days_past = None
296348 latest = latest_assignment_for_reviewer .get (e .pk )
297349 if latest is not None :
298350 days_past = (now - latest ).days - reviewer .frequency
299351
300- # FIXME:
301- # positive: (Perhaps do these separately? As initial values?)
302- # has done review of previous rev
303- # would like to review
304-
305- # blocks:
306- # connections to doc + filter_re
307- # has rejected same request/completed partial review
308- # is unavailable_until
309-
310352 if days_past is None :
311353 ready_for = "first time"
312354 else :
@@ -315,19 +357,26 @@ def construct_review_request_assignment_choices(possible_emails, team, review_re
315357 ready_for = "ready for {} {}" .format (d , "day" if d == 1 else "days" )
316358 else :
317359 d = - d
318- ready_for = "frequency exceeded - ready in {} {}" .format (d , "day" if d == 1 else "days" )
360+ ready_for = "frequency exceeded, ready in {} {}" .format (d , "day" if d == 1 else "days" )
361+
362+ explanations .append (ready_for )
363+
364+ add_boolean_score (scores , + 1 , e .pk in has_reviewed_previous , "reviewed document before" )
365+ add_boolean_score (scores , + 1 , e .pk in would_like_to_review , "wants to review document" )
366+ add_boolean_score (scores , - 1 , e .pk in connections , connections .get (e .pk ))
367+ add_boolean_score (scores , - 1 , reviewer .filter_re and any (re .search (reviewer .filter_re , n ) for n in aliases ), "filter regexp matches" )
368+ add_boolean_score (scores , - 1 , reviewer .unavailable_until and reviewer .unavailable_until > now , "unavailable until {}" .format ((reviewer .unavailable_until or now ).strftime ("%Y-%m-%d %H:%M:%S" )))
319369
320- label = "{}: {}" . format ( e . person , ready_for )
370+ scores . append ( 100000 if days_past is None else days_past )
321371
322- rank = ( - 100000 if days_past is None else - days_past , )
372+ label = "{}: {}" . format ( e . person , "; " . join ( explanations ) )
323373
324- rankings .append ({
374+ ranking .append ({
325375 "email" : e ,
326- "rank " : rank ,
376+ "scores " : scores ,
327377 "label" : label ,
328378 })
329379
330- rankings .sort (key = lambda r : r ["rank" ] )
380+ ranking .sort (key = lambda r : r ["scores" ], reverse = True )
331381
332- # FIXME: empty choices
333- return [(r ["email" ].pk , r ["label" ]) for r in rankings ]
382+ return [(r ["email" ].pk , r ["label" ]) for r in ranking ]
0 commit comments