Skip to content

Commit aadcf2d

Browse files
committed
Added selecting existing people to the workflow for chair classification of nominations. Renamed some utility functions to be slightly more self-documenting.
- Legacy-Id: 10620
1 parent fa53de0 commit aadcf2d

4 files changed

Lines changed: 115 additions & 44 deletions

File tree

ietf/nomcom/forms.py

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,16 @@
1616
Position, Feedback, ReminderDates )
1717
from ietf.nomcom.utils import (NOMINATION_RECEIPT_TEMPLATE, FEEDBACK_RECEIPT_TEMPLATE,
1818
get_user_email, validate_private_key, validate_public_key,
19-
get_or_create_nominee, get_or_create_nominee_by_person,
19+
make_nomineeposition, make_nomineeposition_for_newperson,
2020
create_feedback_email)
2121
from ietf.person.models import Email
2222
from ietf.person.fields import SearchableEmailField
2323
from ietf.utils.fields import MultiEmailField
2424
from ietf.utils.mail import send_mail
2525
from ietf.mailtrigger.utils import gather_address_lists
2626

27+
import debug # pyflakes:ignore
28+
2729

2830
ROLODEX_URL = getattr(settings, 'ROLODEX_URL', None)
2931

@@ -361,7 +363,7 @@ def save(self, commit=True):
361363
if nominator_email:
362364
emails = Email.objects.filter(address=nominator_email)
363365
author = emails and emails[0] or None
364-
nominee = get_or_create_nominee_by_person (self.nomcom, searched_email.person, position, author)
366+
nominee = make_nomineeposition(self.nomcom, searched_email.person, position, author)
365367

366368
# Complete nomination data
367369
feedback = Feedback.objects.create(nomcom=self.nomcom,
@@ -471,7 +473,7 @@ def save(self, commit=True):
471473
author = emails and emails[0] or None
472474
## This is where it should change - validation of the email field should fail if the email exists
473475
## The function should become make_nominee_from_newperson)
474-
nominee = get_or_create_nominee(self.nomcom, candidate_name, candidate_email, position, author)
476+
nominee = make_nomineeposition_for_newperson(self.nomcom, candidate_name, candidate_email, position, author)
475477

476478
# Complete nomination data
477479
feedback = Feedback.objects.create(nomcom=self.nomcom,
@@ -734,13 +736,34 @@ def set_nomcom(self, nomcom, user, instances=None):
734736
help_text='Hold down "Control", or "Command" on a Mac, to select more than one.')
735737
else:
736738
self.fields['position'] = forms.ModelChoiceField(queryset=Position.objects.get_by_nomcom(self.nomcom).opened(), label="Position")
737-
self.fields['candidate_name'] = forms.CharField(label="Candidate name")
738-
self.fields['candidate_email'] = forms.EmailField(label="Candidate email")
739+
self.fields['searched_email'] = SearchableEmailField(only_users=False,help_text="Try to find the candidate you are classifying with this field first. Only use the name and email fields below if this search does not find the candidate.",label="Candidate",required=False)
740+
self.fields['candidate_name'] = forms.CharField(label="Candidate name",help_text="Only fill in this name field if the search doesn't find the person you are classifying",required=False)
741+
self.fields['candidate_email'] = forms.EmailField(label="Candidate email",help_text="Only fill in this email field if the search doesn't find the person you are classifying",required=False)
739742
self.fields['candidate_phone'] = forms.CharField(label="Candidate phone", required=False)
740743

744+
def clean(self):
745+
cleaned_data = super(MutableFeedbackForm,self).clean()
746+
if self.feedback_type.slug == 'nomina':
747+
searched_email = self.cleaned_data.get('searched_email')
748+
candidate_name = self.cleaned_data.get('candidate_name')
749+
if candidate_name:
750+
candidate_name = candidate_name.strip()
751+
candidate_email = self.cleaned_data.get('candidate_email')
752+
if candidate_email:
753+
candidate_email = candidate_email.strip()
754+
755+
if not any([ searched_email and not candidate_name and not candidate_email,
756+
not searched_email and candidate_name and candidate_email,
757+
]):
758+
raise forms.ValidationError("You must identify either an existing person (by searching with the candidate field) and leave the name and email fields blank, or leave the search field blank and provide both a name and email address.")
759+
if candidate_email and Email.objects.filter(address=candidate_email).exists():
760+
raise forms.ValidationError("%s already exists in the datatracker. Please search within the candidate field to find it and leave both the name and email fields blank." % candidate_email)
761+
return cleaned_data
762+
741763
def save(self, commit=True):
742764
feedback = super(MutableFeedbackForm, self).save(commit=False)
743765
if self.instance.type.slug == 'nomina':
766+
searched_email = self.cleaned_data['searched_email']
744767
candidate_email = self.cleaned_data['candidate_email']
745768
candidate_name = self.cleaned_data['candidate_name']
746769
candidate_phone = self.cleaned_data['candidate_phone']
@@ -752,7 +775,10 @@ def save(self, commit=True):
752775
emails = Email.objects.filter(address=nominator_email)
753776
author = emails and emails[0] or None
754777

755-
nominee = get_or_create_nominee(self.nomcom, candidate_name, candidate_email, position, author)
778+
if searched_email:
779+
nominee = make_nomineeposition(self.nomcom, searched_email.person, position, author)
780+
else:
781+
nominee = make_nomineeposition_for_newperson(self.nomcom, candidate_name, candidate_email, position, author)
756782
feedback.nominees.add(nominee)
757783
feedback.positions.add(position)
758784
Nomination.objects.create(

ietf/nomcom/tests.py

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
NomineePositionStateName, Feedback, FeedbackTypeName, \
2929
Nomination, FeedbackLastSeen
3030
from ietf.nomcom.forms import EditMembersForm, EditMembersFormPreview
31-
from ietf.nomcom.utils import get_nomcom_by_year, get_or_create_nominee, get_hash_nominee_position
31+
from ietf.nomcom.utils import get_nomcom_by_year, make_nomineeposition, get_hash_nominee_position
3232
from ietf.nomcom.management.commands.send_reminders import Command, is_time_to_send
3333

3434
from ietf.nomcom.factories import NomComFactory, FeedbackFactory, \
@@ -951,20 +951,22 @@ def setUp(self):
951951
today = datetime.date.today()
952952
t_minus_3 = today - datetime.timedelta(days=3)
953953
t_minus_4 = today - datetime.timedelta(days=4)
954-
n = get_or_create_nominee(self.nomcom,"Nominee 1","nominee1@example.org",gen,None)
954+
e1 = EmailFactory(address="nominee1@example.org",person=PersonFactory(name=u"Nominee 1"))
955+
e2 = EmailFactory(address="nominee2@example.org",person=PersonFactory(name=u"Nominee 2"))
956+
n = make_nomineeposition(self.nomcom,e1.person,gen,None)
955957
np = n.nomineeposition_set.get(position=gen)
956958
np.time = t_minus_3
957959
np.save()
958-
n = get_or_create_nominee(self.nomcom,"Nominee 1","nominee1@example.org",iab,None)
960+
n = make_nomineeposition(self.nomcom,e1.person,iab,None)
959961
np = n.nomineeposition_set.get(position=iab)
960962
np.state = NomineePositionStateName.objects.get(slug='accepted')
961963
np.time = t_minus_3
962964
np.save()
963-
n = get_or_create_nominee(self.nomcom,"Nominee 2","nominee2@example.org",rai,None)
965+
n = make_nomineeposition(self.nomcom,e2.person,rai,None)
964966
np = n.nomineeposition_set.get(position=rai)
965967
np.time = t_minus_4
966968
np.save()
967-
n = get_or_create_nominee(self.nomcom,"Nominee 2","nominee2@example.org",gen,None)
969+
n = make_nomineeposition(self.nomcom,e2.person,gen,None)
968970
np = n.nomineeposition_set.get(position=gen)
969971
np.state = NomineePositionStateName.objects.get(slug='accepted')
970972
np.time = t_minus_4
@@ -1445,14 +1447,51 @@ def test_simple_feedback_pending(self):
14451447
'form-0-id': fb.id,
14461448
'form-0-type': 'nomina',
14471449
'form-0-position': position.id,
1448-
'form-0-candidate_name' : nominee.name(),
1449-
'form-0-candidate_email' : nominee.email.address,
1450+
'form-0-searched_email' : nominee.email.address,
14501451
})
14511452
self.assertEqual(response.status_code,302)
14521453
fb = Feedback.objects.get(id=fb.id)
14531454
self.assertEqual(fb.type_id,'nomina')
14541455
self.assertEqual(nominee.feedback_set.count(),fb_count_before+1)
14551456

1457+
# Classify a newperson
1458+
fb = FeedbackFactory(nomcom=self.nc,type_id=None)
1459+
position = self.nc.position_set.first()
1460+
response = self.client.post(url, {'form-TOTAL_FORMS':1,
1461+
'form-INITIAL_FORMS':1,
1462+
'end':'Save feedback',
1463+
'form-0-id': fb.id,
1464+
'form-0-type': 'nomina',
1465+
'form-0-position': position.id,
1466+
'form-0-candidate_email' : 'newperson@example.com',
1467+
'form-0-candidate_name' : 'New Person',
1468+
})
1469+
self.assertEqual(response.status_code,302)
1470+
fb = Feedback.objects.get(id=fb.id)
1471+
self.assertEqual(fb.type_id,'nomina')
1472+
self.assertTrue(fb.nominees.filter(person__name='New Person').exists())
1473+
1474+
# check for failure when trying to add a newperson that already exists
1475+
1476+
fb = FeedbackFactory(nomcom=self.nc,type_id=None)
1477+
position = self.nc.position_set.all()[1]
1478+
nominee = self.nc.nominee_set.get(person__email__address='newperson@example.com')
1479+
fb_count_before = nominee.feedback_set.count()
1480+
response = self.client.post(url, {'form-TOTAL_FORMS':1,
1481+
'form-INITIAL_FORMS':1,
1482+
'end':'Save feedback',
1483+
'form-0-id': fb.id,
1484+
'form-0-type': 'nomina',
1485+
'form-0-position': position.id,
1486+
'form-0-candidate_email' : 'newperson@example.com',
1487+
'form-0-candidate_name' : 'New Person',
1488+
})
1489+
self.assertEqual(response.status_code,200)
1490+
self.assertTrue('already exists' in unicontent(response))
1491+
fb = Feedback.objects.get(id=fb.id)
1492+
self.assertEqual(fb.type_id,None)
1493+
self.assertEqual(nominee.feedback_set.count(),fb_count_before)
1494+
14561495
fb = FeedbackFactory(nomcom=self.nc,type_id=None)
14571496
np = NomineePosition.objects.filter(position__nomcom = self.nc,state='accepted').first()
14581497
fb_count_before = np.nominee.feedback_set.count()
@@ -1461,7 +1500,7 @@ def test_simple_feedback_pending(self):
14611500
'end':'Save feedback',
14621501
'form-0-id': fb.id,
14631502
'form-0-type': 'questio',
1464-
'form-0-nominee': '%s_%s'%(np.position.id,np.nominee.id),
1503+
'form-0-nominee' : '%s_%s'%(np.position.id,np.nominee.id),
14651504
})
14661505
self.assertEqual(response.status_code,302)
14671506
fb = Feedback.objects.get(id=fb.id)
@@ -1504,23 +1543,23 @@ def test_complicated_feedback_pending(self):
15041543
self.assertEqual(q('input[name=\"form-0-type\"]').attr['value'],'nomina')
15051544
self.assertEqual(q('input[name=\"extra_ids\"]').attr['value'],'%s:comment' % fb2.id)
15061545

1507-
# Second formset
1546+
# Second formset
15081547
response = self.client.post(url, {'form-TOTAL_FORMS':1,
15091548
'form-INITIAL_FORMS':1,
15101549
'end':'Save feedback',
15111550
'form-0-id': fb1.id,
15121551
'form-0-type': 'nomina',
15131552
'form-0-position': new_position_for_nominee.id,
1514-
'form-0-candidate_name' : nominee.name(),
1515-
'form-0-candidate_email' : nominee.email.address,
1553+
'form-0-candidate_name' : 'Totally New Person',
1554+
'form-0-candidate_email': 'totallynew@example.org',
15161555
'extra_ids': '%s:comment' % fb2.id,
15171556
})
15181557
self.assertEqual(response.status_code,200) # Notice that this is also is not a 302
1519-
fb1 = Feedback.objects.get(id=fb1.id)
1520-
self.assertEqual(fb1.type_id,'nomina')
15211558
q = PyQuery(response.content)
15221559
self.assertEqual(q('input[name=\"form-0-type\"]').attr['value'],'comment')
15231560
self.assertFalse(q('input[name=\"extra_ids\"]'))
1561+
fb1 = Feedback.objects.get(id=fb1.id)
1562+
self.assertEqual(fb1.type_id,'nomina')
15241563

15251564
# Exercising the resulting third formset is identical to the simple test above
15261565
# that categorizes a single thing as a comment. Note that it returns a 302.

ietf/nomcom/utils.py

Lines changed: 20 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ def send_reminder_to_nominees(nominees,type):
271271
return addrs
272272

273273

274-
def get_or_create_nominee_by_person(nomcom, candidate, position, author):
274+
def make_nomineeposition(nomcom, candidate, position, author):
275275
from ietf.nomcom.models import Nominee, NomineePosition
276276

277277
nomcom_template_path = '/nomcom/%s/' % nomcom.group.acronym
@@ -349,33 +349,28 @@ def get_or_create_nominee_by_person(nomcom, candidate, position, author):
349349

350350
return nominee
351351

352-
def get_or_create_nominee(nomcom, candidate_name, candidate_email, position, author):
352+
def make_nomineeposition_for_newperson(nomcom, candidate_name, candidate_email, position, author):
353353

354-
## TODO: Assert here that there is no matching email or person, and change the code
355-
## to not possibly stomp on existing things
354+
# This is expected to fail if called with an existing email address
355+
email = Email.objects.create(address=candidate_email)
356+
person = Person.objects.create(name=candidate_name,
357+
ascii=unaccent.asciify(candidate_name),
358+
address=candidate_email)
359+
email.person = person
360+
email.save()
356361

357-
# Create person and email if candidate email does't exist and send email
358-
email, created_email = Email.objects.get_or_create(address=candidate_email)
359-
if created_email:
360-
person = Person.objects.create(name=candidate_name,
361-
ascii=unaccent.asciify(candidate_name),
362-
address=candidate_email)
363-
email.person = person
364-
email.save()
365-
366-
# send email to secretariat and nomcomchair to warn about the new person
367-
subject = 'New person is created'
368-
from_email = settings.NOMCOM_FROM_EMAIL
369-
(to_email, cc) = gather_address_lists('nomination_created_person',nomcom=nomcom)
370-
context = {'email': email.address,
371-
'fullname': email.person.name,
372-
'person_id': email.person.id}
373-
nomcom_template_path = '/nomcom/%s/' % nomcom.group.acronym
374-
path = nomcom_template_path + INEXISTENT_PERSON_TEMPLATE
375-
send_mail(None, to_email, from_email, subject, path, context, cc=cc)
376-
377-
return get_or_create_nominee_by_person(nomcom, email.person, position, author)
362+
# send email to secretariat and nomcomchair to warn about the new person
363+
subject = 'New person is created'
364+
from_email = settings.NOMCOM_FROM_EMAIL
365+
(to_email, cc) = gather_address_lists('nomination_created_person',nomcom=nomcom)
366+
context = {'email': email.address,
367+
'fullname': email.person.name,
368+
'person_id': email.person.id}
369+
nomcom_template_path = '/nomcom/%s/' % nomcom.group.acronym
370+
path = nomcom_template_path + INEXISTENT_PERSON_TEMPLATE
371+
send_mail(None, to_email, from_email, subject, path, context, cc=cc)
378372

373+
return make_nomineeposition(nomcom, email.person, position, author)
379374

380375
def getheader(header_text, default="ascii"):
381376
"""Decode the specified header"""

ietf/templates/nomcom/view_feedback_pending.html

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,14 @@
33
{% load origin %}
44

55
{% load bootstrap3 %}
6+
{% load staticfiles %}
67
{% load nomcom_tags %}
78

9+
{% block pagehead %}
10+
<link rel="stylesheet" href="{% static 'select2/select2.css' %}">
11+
<link rel="stylesheet" href="{% static 'select2-bootstrap-css/select2-bootstrap.min.css' %}">
12+
{% endblock %}
13+
814
{% block subtitle %} - Feeback pending{% endblock %}
915

1016
{% block morecss %}
@@ -155,3 +161,8 @@ <h4 class="modal-title" id="label{{ form.instance.id }}">
155161
{% endif %}
156162

157163
{% endblock %}
164+
165+
{% block js %}
166+
<script src="{% static 'select2/select2.min.js' %}"></script>
167+
<script src="{% static 'ietf/js/select2-field.js' %}"></script>
168+
{% endblock %}

0 commit comments

Comments
 (0)