* 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:
parent
da351ed06a
commit
ea4b6389d6
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 %} |
|
||||
|
|
|
@ -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:">✓,No,No" }}
|
||||
</tr>
|
||||
|
|
51
ietf/templates/nomcom/send_reminder_mail.html
Normal file
51
ietf/templates/nomcom/send_reminder_mail.html
Normal 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>✓</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 %}
|
Loading…
Reference in a new issue