Skip to content

Commit ea4b638

Browse files
committed
* Add new view and mail template to reminder to the nominees to accept (or decline) the nominations
* Create model managers to return nominee by nomcom, so nominees list by position belongs to a specific nomcom * Refactor forms with new managers See ietf-tools#965 ietf-tools#969 - Legacy-Id: 5487
1 parent da351ed commit ea4b638

10 files changed

Lines changed: 161 additions & 29 deletions

File tree

ietf/dbtemplate/fixtures/nomcom_templates.xml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,20 @@ $position: Position</field>
9191
</field>
9292
<field to="group.group" name="group" rel="ManyToOneRel"><None></None></field>
9393
</object>
94+
<object pk="8" model="dbtemplate.dbtemplate">
95+
<field type="CharField" name="path">/nomcom/defaults/email/nomination_reminder.txt</field>
96+
<field type="CharField" name="title">Email sent to nominees asking them to accept (or decline) the nominations.</field>
97+
<field type="TextField" name="variables">$positions: Nomination positions</field>
98+
<field to="name.dbtemplatetypename" name="type" rel="ManyToOneRel">plain</field>
99+
<field type="TextField" name="content">Hi,
100+
101+
You have been nominated for the positions: $positions
102+
103+
The NomCom would appreciate receiving an indication of whether or not you accept this nomination to stand for consideration as a candidate for these positions.
104+
105+
If you accept, you will need to fill out a questionnaire. You will receive the questionnaire by email
106+
107+
Best regards,</field>
108+
<field to="group.group" name="group" rel="ManyToOneRel"><None></None></field>
109+
</object>
94110
</django-objects>

ietf/nomcom/forms.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -216,8 +216,7 @@ def __init__(self, *args, **kwargs):
216216

217217
def clean_primary_email(self):
218218
email = self.cleaned_data['primary_email']
219-
nominees = Nominee.objects.filter(email__address=email,
220-
nominee_position__nomcom=self.nomcom)
219+
nominees = Nominee.objects.get_by_nomcom(self.nomcom).filter(email__address=email)
221220
if not nominees:
222221
msg = "Does not exist a nomiee with this email"
223222
self._errors["primary_email"] = self.error_class([msg])
@@ -228,8 +227,7 @@ def clean_secondary_emails(self):
228227
data = self.cleaned_data['secondary_emails']
229228
emails = get_list(data)
230229
for email in emails:
231-
nominees = Nominee.objects.filter(email__address=email,
232-
nominee_position__nomcom=self.nomcom)
230+
nominees = Nominee.objects.get_by_nomcom(self.nomcom).filter(email__address=email)
233231
if not nominees:
234232
msg = "Does not exist a nomiee with email %s" % email
235233
self._errors["primary_email"] = self.error_class([msg])
@@ -250,10 +248,8 @@ def save(self):
250248
primary_email = self.cleaned_data.get("primary_email")
251249
secondary_emails = get_list(self.cleaned_data.get("secondary_emails"))
252250

253-
primary_nominee = Nominee.objects.get(email__address=primary_email,
254-
nominee_position__nomcom=self.nomcom)
255-
secondary_nominees = Nominee.objects.filter(email__address__in=secondary_emails,
256-
nominee_position__nomcom=self.nomcom)
251+
primary_nominee = Nominee.objects.get_by_nomcom(self.nomcom).get(email__address=primary_email)
252+
secondary_nominees = Nominee.objects.get_by_nomcom(self.nomcom).filter(email__address__in=secondary_emails)
257253
for nominee in secondary_nominees:
258254
# move nominations
259255
nominee.nomination_set.all().update(nominee=primary_nominee)

ietf/nomcom/models.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,19 +59,31 @@ def __unicode__(self):
5959
return u"%s (%s)" % (self.candidate_name, self.candidate_email)
6060

6161

62+
class NomineeManager(models.Manager):
63+
def get_by_nomcom(self, nomcom):
64+
return self.filter(nominee_position__nomcom=nomcom)
65+
66+
6267
class Nominee(models.Model):
6368

6469
email = models.ForeignKey(Email)
6570
nominee_position = models.ManyToManyField('Position', through='NomineePosition')
6671
duplicated = models.ForeignKey('Nominee', blank=True, null=True)
6772

73+
objects = NomineeManager()
74+
6875
class Meta:
6976
verbose_name_plural = 'Nominees'
7077

7178
def __unicode__(self):
7279
return u'%s' % self.email
7380

7481

82+
class NomineePositionManager(models.Manager):
83+
def get_by_nomcom(self, nomcom):
84+
return self.filter(position__nomcom=nomcom)
85+
86+
7587
class NomineePosition(models.Model):
7688

7789
position = models.ForeignKey('Position')
@@ -83,10 +95,13 @@ class NomineePosition(models.Model):
8395
feedback = models.ManyToManyField('Feedback', blank=True, null=True)
8496
time = models.DateTimeField(auto_now_add=True)
8597

98+
objects = NomineePositionManager()
99+
86100
class Meta:
87101
verbose_name = 'Nominee position'
88102
verbose_name_plural = 'Nominee positions'
89103
unique_together = ('position', 'nominee')
104+
ordering = ['nominee']
90105

91106
def save(self, **kwargs):
92107
if not self.pk and not self.state_id:

ietf/nomcom/test_data.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,16 @@ def nomcom_test_data():
106106
cert_file, privatekey_file = generate_cert()
107107
nomcom.public_key.save('cert', File(open(cert_file.name, 'r')))
108108

109-
secretariat, created = Group.objects.get_or_create(name=u'IETF Secretariat',
110-
acronym="secretariat",
111-
state_id="active",
112-
type_id="ietf",
113-
parent=None)
109+
try:
110+
secretariat = Group.objects.get(acronym="secretariat")
111+
secretariat.name = u'Secretariat'
112+
secretariat.save()
113+
except Group.DoesNotExist:
114+
secretariat, created = Group.objects.get_or_create(name=u'Secretariat',
115+
acronym="secretariat",
116+
state_id="active",
117+
type_id="ietf",
118+
parent=None)
114119
# users
115120
for user in USERS:
116121
u, created = User.objects.get_or_create(username=user)

ietf/nomcom/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
url(r'^(?P<year>\d{4})/private/key/$', 'private_key', name='nomcom_private_key'),
88
url(r'^(?P<year>\d{4})/private/nominate/$', 'private_nominate', name='nomcom_private_nominate'),
99
url(r'^(?P<year>\d{4})/private/merge/$', 'private_merge', name='nomcom_private_merge'),
10+
url(r'^(?P<year>\d{4})/private/send-reminder-mail/$', 'send_reminder_mail', name='nomcom_send_reminder_mail'),
1011
url(r'^(?P<year>\d{4})/private/edit-members/$', EditMembersFormPreview(EditMembersForm), name='nomcom_edit_members'),
1112
url(r'^(?P<year>\d{4})/private/edit-chair/$', EditChairFormPreview(EditChairForm), name='nomcom_edit_chair'),
1213
url(r'^(?P<year>\d{4})/private/edit-publickey/$', 'edit_publickey', name='nomcom_edit_publickey'),

ietf/nomcom/utils.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@
1414
INEXISTENT_PERSON_TEMPLATE = 'email/inexistent_person.txt'
1515
NOMINEE_EMAIL_TEMPLATE = 'email/new_nominee.txt'
1616
NOMINATION_EMAIL_TEMPLATE = 'email/new_nomination.txt'
17-
DEFAULT_NOMCOM_TEMPLATES = [HOME_TEMPLATE, INEXISTENT_PERSON_TEMPLATE, NOMINATION_EMAIL_TEMPLATE, NOMINEE_EMAIL_TEMPLATE]
17+
NOMINEE_REMINDER_TEMPLATE = 'email/nomination_reminder.txt'
18+
DEFAULT_NOMCOM_TEMPLATES = [HOME_TEMPLATE,
19+
INEXISTENT_PERSON_TEMPLATE,
20+
NOMINEE_EMAIL_TEMPLATE,
21+
NOMINATION_EMAIL_TEMPLATE,
22+
NOMINEE_REMINDER_TEMPLATE]
1823

1924

2025
def get_nomcom_by_year(year):

ietf/nomcom/views.py

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# -*- coding: utf-8 -*-
2+
from django.conf import settings
23
from django.contrib.auth.decorators import login_required
34
from django.core.urlresolvers import reverse
45
from django.http import HttpResponse, Http404, HttpResponseRedirect
@@ -8,16 +9,18 @@
89
from django.utils import simplejson
910
from django.db.models import Count
1011

12+
from ietf.utils.mail import send_mail
13+
1114
from ietf.dbtemplate.models import DBTemplate
1215
from ietf.dbtemplate.views import template_edit
1316
from ietf.name.models import NomineePositionState
1417
from ietf.nomcom.decorators import member_required, private_key_required
1518
from ietf.nomcom.forms import (EditPublicKeyForm, NominateForm, MergeForm,
1619
NomComTemplateForm, PositionForm, PrivateKeyForm)
17-
from ietf.nomcom.models import Position, NomineePosition
20+
from ietf.nomcom.models import Position, NomineePosition, Nominee
1821
from ietf.nomcom.utils import (get_nomcom_by_year, HOME_TEMPLATE,
1922
retrieve_nomcom_private_key,
20-
store_nomcom_private_key)
23+
store_nomcom_private_key, NOMINEE_REMINDER_TEMPLATE)
2124

2225

2326
def index(request, year):
@@ -56,20 +59,22 @@ def private_key(request, year):
5659
@member_required(role='member')
5760
def private_index(request, year):
5861
nomcom = get_nomcom_by_year(year)
62+
all_nominee_positions = NomineePosition.objects.get_by_nomcom(nomcom)
5963
is_chair = nomcom.group.is_chair(request.user)
6064
message = None
6165
if is_chair and request.method == 'POST':
6266
action = request.POST.get('action')
6367
nominations_to_modify = request.POST.getlist('selected')
6468
if nominations_to_modify:
69+
nominations = all_nominee_positions.filter(id__in=nominations_to_modify)
6570
if action == "set_as_accepted":
66-
NomineePosition.objects.filter(id__in=nominations_to_modify).update(state='accepted')
71+
nominations.update(state='accepted')
6772
message = ('success', 'The selected nominations has been set as accepted')
6873
elif action == "set_as_declined":
69-
NomineePosition.objects.filter(id__in=nominations_to_modify).update(state='declined')
74+
nominations.update(state='declined')
7075
message = ('success', 'The selected nominations has been set as declined')
7176
elif action == "set_as_pending":
72-
NomineePosition.objects.filter(id__in=nominations_to_modify).update(state='pending')
77+
nominations.update(state='pending')
7378
message = ('success', 'The selected nominations has been set as pending')
7479
else:
7580
message = ('warning', "Please, select some nominations to work with")
@@ -87,21 +92,21 @@ def private_index(request, year):
8792
if selected_position:
8893
filters['position__id'] = selected_position
8994

90-
nominee_positions = NomineePosition.objects.all()
95+
nominee_positions = all_nominee_positions
9196
if filters:
9297
nominee_positions = nominee_positions.filter(**filters)
9398

94-
stats = NomineePosition.objects.values('position__name').annotate(total=Count('position'))
99+
stats = all_nominee_positions.values('position__name').annotate(total=Count('position'))
95100
states = list(NomineePositionState.objects.values('slug', 'name')) + [{'slug': u'questionnaire', 'name': u'Questionnaire'}]
96-
positions = NomineePosition.objects.values('position__name', 'position__id').distinct()
101+
positions = all_nominee_positions.values('position__name', 'position__id').distinct()
97102
for s in stats:
98103
for state in states:
99104
if state['slug'] == 'questionnaire':
100-
s[state['slug']] = NomineePosition.objects.filter(position__name=s['position__name'],
101-
questionnaires__isnull=False).count()
105+
s[state['slug']] = all_nominee_positions.filter(position__name=s['position__name'],
106+
questionnaires__isnull=False).count()
102107
else:
103-
s[state['slug']] = NomineePosition.objects.filter(position__name=s['position__name'],
104-
state=state['slug']).count()
108+
s[state['slug']] = all_nominee_positions.filter(position__name=s['position__name'],
109+
state=state['slug']).count()
105110

106111
return render_to_response('nomcom/private_index.html',
107112
{'nomcom': nomcom,
@@ -117,6 +122,39 @@ def private_index(request, year):
117122
'message': message}, RequestContext(request))
118123

119124

125+
@member_required(role='chair')
126+
def send_reminder_mail(request, year):
127+
nomcom = get_nomcom_by_year(year)
128+
nominees = Nominee.objects.get_by_nomcom(nomcom).filter(nomineeposition__state='pending').distinct()
129+
nomcom_template_path = '/nomcom/%s/' % nomcom.group.acronym
130+
mail_path = nomcom_template_path + NOMINEE_REMINDER_TEMPLATE
131+
mail_template = DBTemplate.objects.filter(group=nomcom.group, path=mail_path)
132+
mail_template = mail_template and mail_template[0] or None
133+
message = None
134+
135+
if request.method == 'POST':
136+
selected_nominees = request.POST.getlist('selected')
137+
selected_nominees = nominees.filter(id__in=selected_nominees)
138+
if selected_nominees:
139+
subject = 'IETF Nomination Information'
140+
from_email = settings.NOMCOM_FROM_EMAIL
141+
for nominee in nominees:
142+
to_email = nominee.email.address
143+
positions = ', '.join([nominee_position.position.name for nominee_position in nominee.nomineeposition_set.all()
144+
if nominee_position.state.slug == "pending"])
145+
context = {'positions': positions}
146+
send_mail(None, to_email, from_email, subject, mail_path, context)
147+
message = ('success', 'An query has been sent to each person, asking them to accept (or decline) the nominations')
148+
else:
149+
message = ('warning', "Please, select some nominee")
150+
return render_to_response('nomcom/send_reminder_mail.html',
151+
{'nomcom': nomcom,
152+
'year': year,
153+
'nominees': nominees,
154+
'mail_template': mail_template,
155+
'message': message}, RequestContext(request))
156+
157+
120158
@member_required(role='chair')
121159
def private_merge(request, year):
122160
nomcom = get_nomcom_by_year(year)

ietf/templates/nomcom/nomcom_private_base.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ <h1>Nomcom {{ year }} Private Area</h1>
1212
{% if selected == "private_key" %}<span class="selected">Private key</span>{% else %}<a href="{% url nomcom_private_key year %}">Private key</a>{% endif %}
1313
{% if user|is_chair:year %} |
1414
{% if selected == "merge" %}<span class="selected">Merge nominee email addr</span>{% else %}<a href="{% url nomcom_private_merge year %}">Merge nominee email addr</a>{% endif %} |
15+
{% if selected == "send_reminder_mail" %}<span class="selected">Send Reminder Mail</span>{% else %}<a href="{% url nomcom_send_reminder_mail year %}">Send reminder mail</a>{% endif %} |
1516
{% if selected == "edit_members" %}<span class="selected">Nomcom members</span>{% else %}<a href="{% url nomcom_edit_members year %}">Nomcom members</a>{% endif %} |
1617
{% if selected == "edit_publickey" %}<span class="selected">Public key</span>{% else %}<a href="{% url nomcom_edit_publickey year %}">Public key</a>{% endif %} |
1718
{% if selected == "edit_templates" %}<span class="selected">Templates</span>{% else %}<a href="{% url nomcom_list_templates year %}">Templates</a>{% endif %} |

ietf/templates/nomcom/private_index.html

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,12 @@ <h2>Select Filters</h2>
6767

6868
<h2>List of nominees</h2>
6969

70+
<p>The following is a list of registered nominees.
71+
{% if is_chair %}(You can <a href="{% url nomcom_send_reminder_mail year %}">request confirmation</a> from nominees if they haven't
72+
replied to the nomination notification they have received.){% endif %}</p>
73+
7074
{% if is_chair %}
71-
<form id="batch-action-form" method="post" action="">
75+
<form id="batch-action-form" method="post" action="">{% csrf_token %}
7276
{% if message %}
7377
<div class="info-message-{{ message.0 }}">{{ message.1 }}</div>
7478
{% endif %}
@@ -96,10 +100,10 @@ <h2>List of nominees</h2>
96100
{% for np in nominee_positions %}
97101
<tr class="{{ forloop.counter|divisibleby:2|yesno:"oddrow,evenrow" }}">
98102
{% if is_chair %}
99-
<td><input class="batch-select" type="checkbox" value="{{ np.id }}" name="selected"{% if charge.is_selected %} checked="checked"{% endif %} /></td>
103+
<td><input class="batch-select" type="checkbox" value="{{ np.id }}" name="selected"/></td>
100104
{% endif %}
101105
<td>{{ np.nominee }}</td>
102-
<td>{{ np.position }}</td>
106+
<td>{{ np.position.name }}</td>
103107
<td>{{ np.state }}</td>
104108
<td>{{ np.questionnaires.all|yesno:">&#x2713;,No,No" }}
105109
</tr>
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
{% extends "nomcom/nomcom_private_base.html" %}
2+
3+
{% block subtitle %} - Send reminder emails {% endblock %}
4+
5+
{% block nomcom_content %}
6+
7+
<h2>Request nomination acceptance from the listed nominees</h2>
8+
9+
<p>
10+
An query will be sent to each person, asking them to accept (or decline) the nomination.
11+
12+
The list has been pre-populated with the selected list of nominees
13+
14+
The message that will be sent is shown below the address entry form.
15+
</p>
16+
17+
<form method="post" action="">
18+
<h3>Nominees who have not responded</h2>
19+
20+
{% if message %}
21+
<div class="info-message-{{ message.0 }}">{{ message.1 }}</div>
22+
{% endif %}
23+
24+
<table class="ietf-table ietf-doctable">
25+
<tr>
26+
<th>&#x2713;</th>
27+
<th>Nominees</th>
28+
<th>Positions</th>
29+
</tr>
30+
{% for nominee in nominees %}
31+
<tr class="{{ forloop.counter|divisibleby:2|yesno:"oddrow,evenrow" }}">
32+
<td><input class="batch-select" type="checkbox" value="{{ nominee.id }}" name="selected" checked="checked"/></td>
33+
<td>{{ nominee.email }}</td>
34+
<td>{% for nominee_position in nominee.nomineeposition_set.all %}{% ifequal nominee_position.state.slug "pending" %} {{ nominee_position.position.name }}, {% endifequal %}{% endfor %}</td>
35+
</tr>
36+
{% endfor %}
37+
</table>
38+
39+
<div style="padding: 8px 0 8px 0;"></div>
40+
41+
<div class="submitrow">
42+
<input type="submit" name="submit" value="Submit request"/>
43+
</div>
44+
45+
</form>
46+
47+
<p>The message that will be sent is as follows: {% if mail_template %}<a href="{% url nomcom_edit_template year mail_template.id %}">(Edit the message)</a>{% endif %}</p>
48+
49+
{% include mail_template.path %}
50+
51+
{% endblock %}

0 commit comments

Comments
 (0)