diff --git a/ietf/nomcom/forms.py b/ietf/nomcom/forms.py index dfb842626..d1447f21a 100644 --- a/ietf/nomcom/forms.py +++ b/ietf/nomcom/forms.py @@ -51,7 +51,7 @@ class PositionNomineeField(forms.ChoiceField): positions = Position.objects.get_by_nomcom(self.nomcom).opened().order_by('name') results = [] for position in positions: - nominees = [('%s_%s' % (position.id, i.id), unicode(i)) for i in Nominee.objects.get_by_nomcom(self.nomcom).filter(nominee_position=position)] + nominees = [('%s_%s' % (position.id, i.id), unicode(i)) for i in Nominee.objects.get_by_nomcom(self.nomcom).not_duplicated().filter(nominee_position=position)] if nominees: results.append((position.name, nominees)) kwargs['choices'] = results @@ -247,11 +247,11 @@ class EditChairFormPreview(FormPreview): class EditNomcomForm(BaseNomcomForm, forms.ModelForm): - fieldsets = [('Edit nomcom', ('public_key', 'send_questionnaire'))] + fieldsets = [('Edit nomcom', ('public_key', 'send_questionnaire', 'reminder_interval'))] class Meta: model = NomCom - fields = ('public_key', 'send_questionnaire') + fields = ('public_key', 'send_questionnaire', 'reminder_interval') class MergeForm(BaseNomcomForm, forms.Form): @@ -268,7 +268,7 @@ class MergeForm(BaseNomcomForm, forms.Form): def clean_primary_email(self): email = self.cleaned_data['primary_email'] - nominees = Nominee.objects.get_by_nomcom(self.nomcom).filter(email__address=email) + nominees = Nominee.objects.get_by_nomcom(self.nomcom).not_duplicated().filter(email__address=email) if not nominees: msg = "Does not exist a nomiee with this email" self._errors["primary_email"] = self.error_class([msg]) @@ -279,7 +279,7 @@ class MergeForm(BaseNomcomForm, forms.Form): data = self.cleaned_data['secondary_emails'] emails = get_list(data) for email in emails: - nominees = Nominee.objects.get_by_nomcom(self.nomcom).filter(email__address=email) + nominees = Nominee.objects.get_by_nomcom(self.nomcom).not_duplicated().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]) @@ -736,15 +736,23 @@ class PrivateKeyForm(BaseNomcomForm, forms.Form): class PendingFeedbackForm(BaseNomcomForm, forms.ModelForm): - + class Meta: model = Feedback fields = ('author', 'type', 'nominee') + def __init__(self, *args, **kwargs): + super(PendingFeedbackForm, self).__init__(*args, **kwargs) + self.fields['type'].queryset = FeedbackType.objects.exclude(slug='nomina') + + def set_nomcom(self, nomcom, user): self.nomcom = nomcom self.user = user - self.fields['nominee'] = MultiplePositionNomineeField(nomcom=self.nomcom, required=True, widget=forms.SelectMultiple) + self.fields['nominee'] = MultiplePositionNomineeField(nomcom=self.nomcom, + required=True, + widget=forms.SelectMultiple, + help_text='Hold down "Control", or "Command" on a Mac, to select more than one.') def save(self, commit=True): feedback = super(PendingFeedbackForm, self).save(commit=False) diff --git a/ietf/nomcom/management/commands/feedback_email.py b/ietf/nomcom/management/commands/feedback_email.py index e4413105b..af9d6ee56 100644 --- a/ietf/nomcom/management/commands/feedback_email.py +++ b/ietf/nomcom/management/commands/feedback_email.py @@ -9,7 +9,7 @@ from ietf.nomcom.models import Nominee, NomCom, Feedback class Command(BaseCommand): - help = (u"Send a remind to each SDO Liaison Manager to update the list of persons authorized to send liaison statements on behalf of his SDO") + help = (u"Registry feedback from email. Usage: feeback_email --nomcom-year --email-file ") option_list = BaseCommand.option_list + ( make_option('--nomcom-year', dest='year', help='NomCom year'), make_option('--email-file', dest='email', help='Feedback email'), @@ -35,7 +35,7 @@ class Command(BaseCommand): nomcom = NomCom.objects.get(group__acronym__icontains=year, group__state__slug='active') except NomCom.DoesNotExist: - raise CommandError('NomCom %s does not exist' % year) + raise CommandError("NomCom %s does not exist or it isn't active" % year) by, subject, body = parse_email(msg) name, addr = parseaddr(by) diff --git a/ietf/nomcom/management/commands/send_reminders.py b/ietf/nomcom/management/commands/send_reminders.py new file mode 100644 index 000000000..40e8dff1e --- /dev/null +++ b/ietf/nomcom/management/commands/send_reminders.py @@ -0,0 +1,65 @@ +import datetime +import syslog +from optparse import make_option + +from django.core.management.base import BaseCommand, CommandError +from django.conf import settings + +from ietf.utils.mail import send_mail + +from ietf.dbtemplate.models import DBTemplate + +from ietf.nomcom.models import Nominee, NomCom +from ietf.nomcom.utils import NOMINEE_REMINDER_TEMPLATE + + +class Command(BaseCommand): + help = (u"Send reminders to nominees") + option_list = BaseCommand.option_list + ( + make_option('--nomcom-year', dest='year', help='NomCom year'), + ) + + def handle(self, *args, **options): + year = options.get('year', None) + help_message = 'Usage: send_reminders --nomcom-year ' + + if not year: + raise CommandError(help_message) + + try: + nomcom = NomCom.objects.get(group__acronym__icontains=year, + group__state__slug='active') + except NomCom.DoesNotExist: + raise CommandError("NomCom %s does not exist or it isn't active" % year) + + today = datetime.date.today() + 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 + subject = 'IETF Nomination Information' + from_email = settings.NOMCOM_FROM_EMAIL + + if nomcom.reminder_interval: + nominees = Nominee.objects.get_by_nomcom(nomcom).not_duplicated().filter(nomineeposition__state='pending').distinct() + for nominee in nominees: + positions = [] + for np in nominee.nomineeposition_set.all(): + nomination_date = np.time.date() + if not (today - nomination_date).days <= 0: + if (today - nomination_date).days % nomcom.reminder_interval == 0: + positions.append(np.position) + if positions: + to_email = nominee.email.address + context = {'positions': ', '.join([p.name for p in positions])} + send_mail(None, to_email, from_email, subject, mail_path, context) + syslog.syslog(u"Sent reminder to %s" % to_email) + else: + if nomcom.reminderdates_set.filter(date=today): + nominees = Nominee.objects.get_by_nomcom(nomcom).not_duplicated().filter(nomineeposition__state='pending').distinct() + for nominee in nominees: + to_email = nominee.email.address + positions = ', '.join([nominee_position.position.name for nominee_position in nominee.nomineeposition_set.pending()]) + context = {'positions': positions} + send_mail(None, to_email, from_email, subject, mail_path, context) + syslog.syslog(u"Sent reminder to %s" % to_email) diff --git a/ietf/nomcom/migrations/0001_initial.py b/ietf/nomcom/migrations/0001_initial.py index 3c278bb66..d170b54a2 100644 --- a/ietf/nomcom/migrations/0001_initial.py +++ b/ietf/nomcom/migrations/0001_initial.py @@ -8,12 +8,21 @@ class Migration(SchemaMigration): def forwards(self, orm): + # Adding model 'ReminderDates' + db.create_table('nomcom_reminderdates', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('date', self.gf('django.db.models.fields.DateField')()), + ('nomcom', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['nomcom.NomCom'])), + )) + db.send_create_signal('nomcom', ['ReminderDates']) + # Adding model 'NomCom' db.create_table('nomcom_nomcom', ( ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), ('public_key', self.gf('django.db.models.fields.files.FileField')(max_length=100, null=True, blank=True)), ('group', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['group.Group'])), ('send_questionnaire', self.gf('django.db.models.fields.BooleanField')(default=False)), + ('reminder_interval', self.gf('django.db.models.fields.PositiveIntegerField')(null=True, blank=True)), )) db.send_create_signal('nomcom', ['NomCom']) @@ -108,6 +117,9 @@ class Migration(SchemaMigration): # Removing unique constraint on 'Nominee', fields ['email', 'nomcom'] db.delete_unique('nomcom_nominee', ['email_id', 'nomcom_id']) + # Deleting model 'ReminderDates' + db.delete_table('nomcom_reminderdates') + # Deleting model 'NomCom' db.delete_table('nomcom_nomcom') @@ -365,6 +377,7 @@ class Migration(SchemaMigration): 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']"}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'public_key': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'reminder_interval': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), 'send_questionnaire': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) }, 'nomcom.nomination': { @@ -408,6 +421,12 @@ class Migration(SchemaMigration): 'questionnaire': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'questionnaire'", 'null': 'True', 'to': "orm['dbtemplate.DBTemplate']"}), 'requirement': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'requirement'", 'null': 'True', 'to': "orm['dbtemplate.DBTemplate']"}) }, + 'nomcom.reminderdates': { + 'Meta': {'object_name': 'ReminderDates'}, + 'date': ('django.db.models.fields.DateField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'nomcom': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['nomcom.NomCom']"}) + }, 'person.email': { 'Meta': {'object_name': 'Email'}, 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), diff --git a/ietf/nomcom/models.py b/ietf/nomcom/models.py index ff10c7c32..b860aa548 100644 --- a/ietf/nomcom/models.py +++ b/ietf/nomcom/models.py @@ -27,6 +27,11 @@ def upload_path_handler(instance, filename): return os.path.join(instance.group.acronym, 'public.cert') +class ReminderDates(models.Model): + date = models.DateField() + nomcom = models.ForeignKey('NomCom') + + class NomCom(models.Model): public_key = models.FileField(storage=FileSystemStorage(location=settings.PUBLIC_KEYS_URL), upload_to=upload_path_handler, blank=True, null=True) @@ -34,6 +39,10 @@ class NomCom(models.Model): group = models.ForeignKey(Group) send_questionnaire = models.BooleanField(verbose_name='Send automatically questionnaires"', help_text='If you check this box, questionnaires are sent automatically after nominations') + reminder_interval = models.PositiveIntegerField(help_text='If the nomcom user the interval field then a cron command will \ + send reminders to the nominees who have not responded using \ + the following formula: (today - nomination_date) % interval == 0', + blank=True, null=True) class Meta: verbose_name_plural = 'NomComs' diff --git a/ietf/nomcom/views.py b/ietf/nomcom/views.py index 583fc56ac..d6dd4815f 100644 --- a/ietf/nomcom/views.py +++ b/ietf/nomcom/views.py @@ -11,7 +11,7 @@ from django.template import RequestContext from django.template.loader import render_to_string from django.utils import simplejson from django.db.models import Count, Q -from django.forms.models import modelformset_factory +from django.forms.models import modelformset_factory, inlineformset_factory from ietf.utils.mail import send_mail @@ -23,7 +23,7 @@ from ietf.nomcom.decorators import member_required, private_key_required from ietf.nomcom.forms import (NominateForm, FeedbackForm, QuestionnaireForm, MergeForm, NomComTemplateForm, PositionForm, PrivateKeyForm, EditNomcomForm, PendingFeedbackForm) -from ietf.nomcom.models import Position, NomineePosition, Nominee, Feedback, NomCom +from ietf.nomcom.models import Position, NomineePosition, Nominee, Feedback, NomCom, ReminderDates from ietf.nomcom.utils import (get_nomcom_by_year, HOME_TEMPLATE, store_nomcom_private_key, get_hash_nominee_position, NOMINEE_REMINDER_TEMPLATE) @@ -129,7 +129,7 @@ def private_index(request, year): @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() + nominees = Nominee.objects.get_by_nomcom(nomcom).not_duplicated().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) @@ -442,18 +442,25 @@ def edit_nomcom(request, year): nomcom = get_nomcom_by_year(year) message = ('warning', 'Previous data will remain encrypted with the old key') + + ReminderDateInlineFormSet = inlineformset_factory(NomCom, ReminderDates) if request.method == 'POST': + formset = ReminderDateInlineFormSet(request.POST, instance=nomcom) form = EditNomcomForm(request.POST, request.FILES, instance=nomcom) - if form.is_valid(): + if form.is_valid() and formset.is_valid(): form.save() + formset.save() + formset = ReminderDateInlineFormSet(instance=nomcom) message = ('success', 'The nomcom has been changed') else: + formset = ReminderDateInlineFormSet(instance=nomcom) form = EditNomcomForm(instance=nomcom) return render_to_response('nomcom/edit_nomcom.html', {'form': form, + 'formset': formset, 'nomcom': nomcom, 'message': message, 'year': year, diff --git a/ietf/templates/nomcom/edit_nomcom.html b/ietf/templates/nomcom/edit_nomcom.html index bc918d28c..977a23778 100644 --- a/ietf/templates/nomcom/edit_nomcom.html +++ b/ietf/templates/nomcom/edit_nomcom.html @@ -15,6 +15,42 @@ {{ form }}
+ +

Reminder Dates

+ +

If the "reminder interval" field of nomcom isn't filled, the following dates will be used to send reminders.

+ +

The valid format: YYYY-MM-DD

+ + {{ formset.management_form }} + {% for form in formset.forms %} + {% if form.errors %}
Please correct the following errors
{% endif %} +
+
+
+ {% for field in form %} +
+ +
+
{{ field.help_text }}
+ {{ field }} + {{ field.errors }} +
+
+
+ {% endfor %} +
+
+
+
+ {% endfor %} +