* 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 #965 #969
 - Legacy-Id: 5487
This commit is contained in:
Emilio Jiménez 2013-03-06 16:08:21 +00:00
parent da351ed06a
commit ea4b6389d6
10 changed files with 161 additions and 29 deletions

View file

@ -91,4 +91,20 @@ $position: Position</field>
</field>
<field to="group.group" name="group" rel="ManyToOneRel"><None></None></field>
</object>
<object pk="8" model="dbtemplate.dbtemplate">
<field type="CharField" name="path">/nomcom/defaults/email/nomination_reminder.txt</field>
<field type="CharField" name="title">Email sent to nominees asking them to accept (or decline) the nominations.</field>
<field type="TextField" name="variables">$positions: Nomination positions</field>
<field to="name.dbtemplatetypename" name="type" rel="ManyToOneRel">plain</field>
<field type="TextField" name="content">Hi,
You have been nominated for the positions: $positions
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.
If you accept, you will need to fill out a questionnaire. You will receive the questionnaire by email
Best regards,</field>
<field to="group.group" name="group" rel="ManyToOneRel"><None></None></field>
</object>
</django-objects>

View file

@ -216,8 +216,7 @@ class MergeForm(BaseNomcomForm, forms.Form):
def clean_primary_email(self):
email = self.cleaned_data['primary_email']
nominees = Nominee.objects.filter(email__address=email,
nominee_position__nomcom=self.nomcom)
nominees = Nominee.objects.get_by_nomcom(self.nomcom).filter(email__address=email)
if not nominees:
msg = "Does not exist a nomiee with this email"
self._errors["primary_email"] = self.error_class([msg])
@ -228,8 +227,7 @@ class MergeForm(BaseNomcomForm, forms.Form):
data = self.cleaned_data['secondary_emails']
emails = get_list(data)
for email in emails:
nominees = Nominee.objects.filter(email__address=email,
nominee_position__nomcom=self.nomcom)
nominees = Nominee.objects.get_by_nomcom(self.nomcom).filter(email__address=email)
if not nominees:
msg = "Does not exist a nomiee with email %s" % email
self._errors["primary_email"] = self.error_class([msg])
@ -250,10 +248,8 @@ class MergeForm(BaseNomcomForm, forms.Form):
primary_email = self.cleaned_data.get("primary_email")
secondary_emails = get_list(self.cleaned_data.get("secondary_emails"))
primary_nominee = Nominee.objects.get(email__address=primary_email,
nominee_position__nomcom=self.nomcom)
secondary_nominees = Nominee.objects.filter(email__address__in=secondary_emails,
nominee_position__nomcom=self.nomcom)
primary_nominee = Nominee.objects.get_by_nomcom(self.nomcom).get(email__address=primary_email)
secondary_nominees = Nominee.objects.get_by_nomcom(self.nomcom).filter(email__address__in=secondary_emails)
for nominee in secondary_nominees:
# move nominations
nominee.nomination_set.all().update(nominee=primary_nominee)

View file

@ -59,12 +59,19 @@ class Nomination(models.Model):
return u"%s (%s)" % (self.candidate_name, self.candidate_email)
class NomineeManager(models.Manager):
def get_by_nomcom(self, nomcom):
return self.filter(nominee_position__nomcom=nomcom)
class Nominee(models.Model):
email = models.ForeignKey(Email)
nominee_position = models.ManyToManyField('Position', through='NomineePosition')
duplicated = models.ForeignKey('Nominee', blank=True, null=True)
objects = NomineeManager()
class Meta:
verbose_name_plural = 'Nominees'
@ -72,6 +79,11 @@ class Nominee(models.Model):
return u'%s' % self.email
class NomineePositionManager(models.Manager):
def get_by_nomcom(self, nomcom):
return self.filter(position__nomcom=nomcom)
class NomineePosition(models.Model):
position = models.ForeignKey('Position')
@ -83,10 +95,13 @@ class NomineePosition(models.Model):
feedback = models.ManyToManyField('Feedback', blank=True, null=True)
time = models.DateTimeField(auto_now_add=True)
objects = NomineePositionManager()
class Meta:
verbose_name = 'Nominee position'
verbose_name_plural = 'Nominee positions'
unique_together = ('position', 'nominee')
ordering = ['nominee']
def save(self, **kwargs):
if not self.pk and not self.state_id:

View file

@ -106,11 +106,16 @@ def nomcom_test_data():
cert_file, privatekey_file = generate_cert()
nomcom.public_key.save('cert', File(open(cert_file.name, 'r')))
secretariat, created = Group.objects.get_or_create(name=u'IETF Secretariat',
acronym="secretariat",
state_id="active",
type_id="ietf",
parent=None)
try:
secretariat = Group.objects.get(acronym="secretariat")
secretariat.name = u'Secretariat'
secretariat.save()
except Group.DoesNotExist:
secretariat, created = Group.objects.get_or_create(name=u'Secretariat',
acronym="secretariat",
state_id="active",
type_id="ietf",
parent=None)
# users
for user in USERS:
u, created = User.objects.get_or_create(username=user)

View file

@ -7,6 +7,7 @@ urlpatterns = patterns('ietf.nomcom.views',
url(r'^(?P<year>\d{4})/private/key/$', 'private_key', name='nomcom_private_key'),
url(r'^(?P<year>\d{4})/private/nominate/$', 'private_nominate', name='nomcom_private_nominate'),
url(r'^(?P<year>\d{4})/private/merge/$', 'private_merge', name='nomcom_private_merge'),
url(r'^(?P<year>\d{4})/private/send-reminder-mail/$', 'send_reminder_mail', name='nomcom_send_reminder_mail'),
url(r'^(?P<year>\d{4})/private/edit-members/$', EditMembersFormPreview(EditMembersForm), name='nomcom_edit_members'),
url(r'^(?P<year>\d{4})/private/edit-chair/$', EditChairFormPreview(EditChairForm), name='nomcom_edit_chair'),
url(r'^(?P<year>\d{4})/private/edit-publickey/$', 'edit_publickey', name='nomcom_edit_publickey'),

View file

@ -14,7 +14,12 @@ HOME_TEMPLATE = 'home.rst'
INEXISTENT_PERSON_TEMPLATE = 'email/inexistent_person.txt'
NOMINEE_EMAIL_TEMPLATE = 'email/new_nominee.txt'
NOMINATION_EMAIL_TEMPLATE = 'email/new_nomination.txt'
DEFAULT_NOMCOM_TEMPLATES = [HOME_TEMPLATE, INEXISTENT_PERSON_TEMPLATE, NOMINATION_EMAIL_TEMPLATE, NOMINEE_EMAIL_TEMPLATE]
NOMINEE_REMINDER_TEMPLATE = 'email/nomination_reminder.txt'
DEFAULT_NOMCOM_TEMPLATES = [HOME_TEMPLATE,
INEXISTENT_PERSON_TEMPLATE,
NOMINEE_EMAIL_TEMPLATE,
NOMINATION_EMAIL_TEMPLATE,
NOMINEE_REMINDER_TEMPLATE]
def get_nomcom_by_year(year):

View file

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.core.urlresolvers import reverse
from django.http import HttpResponse, Http404, HttpResponseRedirect
@ -8,16 +9,18 @@ from django.template.loader import render_to_string
from django.utils import simplejson
from django.db.models import Count
from ietf.utils.mail import send_mail
from ietf.dbtemplate.models import DBTemplate
from ietf.dbtemplate.views import template_edit
from ietf.name.models import NomineePositionState
from ietf.nomcom.decorators import member_required, private_key_required
from ietf.nomcom.forms import (EditPublicKeyForm, NominateForm, MergeForm,
NomComTemplateForm, PositionForm, PrivateKeyForm)
from ietf.nomcom.models import Position, NomineePosition
from ietf.nomcom.models import Position, NomineePosition, Nominee
from ietf.nomcom.utils import (get_nomcom_by_year, HOME_TEMPLATE,
retrieve_nomcom_private_key,
store_nomcom_private_key)
store_nomcom_private_key, NOMINEE_REMINDER_TEMPLATE)
def index(request, year):
@ -56,20 +59,22 @@ def private_key(request, year):
@member_required(role='member')
def private_index(request, year):
nomcom = get_nomcom_by_year(year)
all_nominee_positions = NomineePosition.objects.get_by_nomcom(nomcom)
is_chair = nomcom.group.is_chair(request.user)
message = None
if is_chair and request.method == 'POST':
action = request.POST.get('action')
nominations_to_modify = request.POST.getlist('selected')
if nominations_to_modify:
nominations = all_nominee_positions.filter(id__in=nominations_to_modify)
if action == "set_as_accepted":
NomineePosition.objects.filter(id__in=nominations_to_modify).update(state='accepted')
nominations.update(state='accepted')
message = ('success', 'The selected nominations has been set as accepted')
elif action == "set_as_declined":
NomineePosition.objects.filter(id__in=nominations_to_modify).update(state='declined')
nominations.update(state='declined')
message = ('success', 'The selected nominations has been set as declined')
elif action == "set_as_pending":
NomineePosition.objects.filter(id__in=nominations_to_modify).update(state='pending')
nominations.update(state='pending')
message = ('success', 'The selected nominations has been set as pending')
else:
message = ('warning', "Please, select some nominations to work with")
@ -87,21 +92,21 @@ def private_index(request, year):
if selected_position:
filters['position__id'] = selected_position
nominee_positions = NomineePosition.objects.all()
nominee_positions = all_nominee_positions
if filters:
nominee_positions = nominee_positions.filter(**filters)
stats = NomineePosition.objects.values('position__name').annotate(total=Count('position'))
stats = all_nominee_positions.values('position__name').annotate(total=Count('position'))
states = list(NomineePositionState.objects.values('slug', 'name')) + [{'slug': u'questionnaire', 'name': u'Questionnaire'}]
positions = NomineePosition.objects.values('position__name', 'position__id').distinct()
positions = all_nominee_positions.values('position__name', 'position__id').distinct()
for s in stats:
for state in states:
if state['slug'] == 'questionnaire':
s[state['slug']] = NomineePosition.objects.filter(position__name=s['position__name'],
questionnaires__isnull=False).count()
s[state['slug']] = all_nominee_positions.filter(position__name=s['position__name'],
questionnaires__isnull=False).count()
else:
s[state['slug']] = NomineePosition.objects.filter(position__name=s['position__name'],
state=state['slug']).count()
s[state['slug']] = all_nominee_positions.filter(position__name=s['position__name'],
state=state['slug']).count()
return render_to_response('nomcom/private_index.html',
{'nomcom': nomcom,
@ -117,6 +122,39 @@ def private_index(request, year):
'message': message}, RequestContext(request))
@member_required(role='chair')
def send_reminder_mail(request, year):
nomcom = get_nomcom_by_year(year)
nominees = Nominee.objects.get_by_nomcom(nomcom).filter(nomineeposition__state='pending').distinct()
nomcom_template_path = '/nomcom/%s/' % nomcom.group.acronym
mail_path = nomcom_template_path + NOMINEE_REMINDER_TEMPLATE
mail_template = DBTemplate.objects.filter(group=nomcom.group, path=mail_path)
mail_template = mail_template and mail_template[0] or None
message = None
if request.method == 'POST':
selected_nominees = request.POST.getlist('selected')
selected_nominees = nominees.filter(id__in=selected_nominees)
if selected_nominees:
subject = 'IETF Nomination Information'
from_email = settings.NOMCOM_FROM_EMAIL
for nominee in nominees:
to_email = nominee.email.address
positions = ', '.join([nominee_position.position.name for nominee_position in nominee.nomineeposition_set.all()
if nominee_position.state.slug == "pending"])
context = {'positions': positions}
send_mail(None, to_email, from_email, subject, mail_path, context)
message = ('success', 'An query has been sent to each person, asking them to accept (or decline) the nominations')
else:
message = ('warning', "Please, select some nominee")
return render_to_response('nomcom/send_reminder_mail.html',
{'nomcom': nomcom,
'year': year,
'nominees': nominees,
'mail_template': mail_template,
'message': message}, RequestContext(request))
@member_required(role='chair')
def private_merge(request, year):
nomcom = get_nomcom_by_year(year)

View file

@ -12,6 +12,7 @@
{% if selected == "private_key" %}<span class="selected">Private key</span>{% else %}<a href="{% url nomcom_private_key year %}">Private key</a>{% endif %}
{% if user|is_chair:year %} |
{% 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 %} |
{% 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 %} |
{% if selected == "edit_members" %}<span class="selected">Nomcom members</span>{% else %}<a href="{% url nomcom_edit_members year %}">Nomcom members</a>{% endif %} |
{% if selected == "edit_publickey" %}<span class="selected">Public key</span>{% else %}<a href="{% url nomcom_edit_publickey year %}">Public key</a>{% endif %} |
{% if selected == "edit_templates" %}<span class="selected">Templates</span>{% else %}<a href="{% url nomcom_list_templates year %}">Templates</a>{% endif %} |

View file

@ -67,8 +67,12 @@
<h2>List of nominees</h2>
<p>The following is a list of registered nominees.
{% if is_chair %}(You can <a href="{% url nomcom_send_reminder_mail year %}">request confirmation</a> from nominees if they haven't
replied to the nomination notification they have received.){% endif %}</p>
{% if is_chair %}
<form id="batch-action-form" method="post" action="">
<form id="batch-action-form" method="post" action="">{% csrf_token %}
{% if message %}
<div class="info-message-{{ message.0 }}">{{ message.1 }}</div>
{% endif %}
@ -96,10 +100,10 @@
{% for np in nominee_positions %}
<tr class="{{ forloop.counter|divisibleby:2|yesno:"oddrow,evenrow" }}">
{% if is_chair %}
<td><input class="batch-select" type="checkbox" value="{{ np.id }}" name="selected"{% if charge.is_selected %} checked="checked"{% endif %} /></td>
<td><input class="batch-select" type="checkbox" value="{{ np.id }}" name="selected"/></td>
{% endif %}
<td>{{ np.nominee }}</td>
<td>{{ np.position }}</td>
<td>{{ np.position.name }}</td>
<td>{{ np.state }}</td>
<td>{{ np.questionnaires.all|yesno:">&#x2713;,No,No" }}
</tr>

View file

@ -0,0 +1,51 @@
{% extends "nomcom/nomcom_private_base.html" %}
{% block subtitle %} - Send reminder emails {% endblock %}
{% block nomcom_content %}
<h2>Request nomination acceptance from the listed nominees</h2>
<p>
An query will be sent to each person, asking them to accept (or decline) the nomination.
The list has been pre-populated with the selected list of nominees
The message that will be sent is shown below the address entry form.
</p>
<form method="post" action="">
<h3>Nominees who have not responded</h2>
{% if message %}
<div class="info-message-{{ message.0 }}">{{ message.1 }}</div>
{% endif %}
<table class="ietf-table ietf-doctable">
<tr>
<th>&#x2713;</th>
<th>Nominees</th>
<th>Positions</th>
</tr>
{% for nominee in nominees %}
<tr class="{{ forloop.counter|divisibleby:2|yesno:"oddrow,evenrow" }}">
<td><input class="batch-select" type="checkbox" value="{{ nominee.id }}" name="selected" checked="checked"/></td>
<td>{{ nominee.email }}</td>
<td>{% for nominee_position in nominee.nomineeposition_set.all %}{% ifequal nominee_position.state.slug "pending" %} {{ nominee_position.position.name }}, {% endifequal %}{% endfor %}</td>
</tr>
{% endfor %}
</table>
<div style="padding: 8px 0 8px 0;"></div>
<div class="submitrow">
<input type="submit" name="submit" value="Submit request"/>
</div>
</form>
<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>
{% include mail_template.path %}
{% endblock %}