diff --git a/ietf/dbtemplate/fixtures/nomcom_templates.xml b/ietf/dbtemplate/fixtures/nomcom_templates.xml index bd15def85..1059d7073 100644 --- a/ietf/dbtemplate/fixtures/nomcom_templates.xml +++ b/ietf/dbtemplate/fixtures/nomcom_templates.xml @@ -96,8 +96,8 @@ $position: Position - - /nomcom/defaults/email/nomination_reminder.txt + + /nomcom/defaults/email/nomination_accept_reminder.txt Email sent to nominees asking them to accept (or decline) the nominations. $positions: Nomination positions plain @@ -114,7 +114,7 @@ If you accept, you will need to fill out a questionnaire. Best regards, - + /nomcom/defaults/email/nomination_receipt.txt Email sent to nominator to get a confirmation mail containing feedback in cleartext $nominee: Full name of the nominee @@ -137,7 +137,7 @@ $comments Thank you, - + /nomcom/defaults/email/feedback_receipt.txt Email sent to feedback author to get a confirmation mail containing feedback in cleartext $nominee: Full name of the nominee @@ -158,4 +158,18 @@ $comments Thank you, + + /nomcom/defaults/email/questionnaire_reminder.txt + Email sent to nominees reminding them to complete a questionnaire + $positions: Nomination positions + plain + +Thank you for accepting your nomination for the position of $position. + +Please remember to complete and return the questionnaire for this position at your earliest opportunity. +The questionaire is repeated below for your convenience. + +-------- + + diff --git a/ietf/nomcom/management/commands/send_reminders.py b/ietf/nomcom/management/commands/send_reminders.py index 8a0d55529..7d1350bc5 100644 --- a/ietf/nomcom/management/commands/send_reminders.py +++ b/ietf/nomcom/management/commands/send_reminders.py @@ -1,48 +1,35 @@ import datetime import syslog from optparse import make_option +from django.core.management.base import BaseCommand +from ietf.nomcom.models import NomCom, NomineePosition +from nomcom.utils import send_accept_reminder_to_nominee,send_questionnaire_reminder_to_nominee -from django.core.management.base import BaseCommand, CommandError - -from ietf.nomcom.models import Nominee, NomCom -from nomcom.utils import send_reminder_to_nominee +def log(message): + syslog.syslog(message) +def is_time_to_send(nomcom,send_date,nomination_date): + if nomcom.reminder_interval: + days_passed = (send_date - nomination_date).days + return days_passed > 0 and days_passed % nomcom.reminder_interval == 0 + else: + return bool(nomcom.reminderdates_set.filter(date=send_date)) class Command(BaseCommand): - help = (u"Send reminders to nominees") - option_list = BaseCommand.option_list + ( - make_option('--nomcom-year', dest='year', help='NomCom year'),) + help = (u"Send acceptance and questionnaire reminders to nominees") 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() - - if nomcom.reminder_interval: - nominees = Nominee.objects.get_by_nomcom(nomcom).not_duplicated().filter(nomineeposition__state='pending').distinct() - for nominee in nominees: - for nominee_position in nominee.nomineeposition_set.all(): - nomination_date = nominee_position.time.date() - if not (today - nomination_date).days <= 0: - if (today - nomination_date).days % nomcom.reminder_interval == 0: - send_reminder_to_nominee(nominee_position) - syslog.syslog(u"Sent reminder to %s" % nominee_position.nominee.email.address) - print u"Sent reminder to %s" % nominee_position.nominee.email.address - 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: - for nominee_position in nominee.nomineeposition_set.pending(): - send_reminder_to_nominee(nominee_position) - syslog.syslog(u"Sent reminder to %s" % nominee_position.nominee.email.address) - print u"Sent reminder (by dates) to %s" % nominee_position.nominee.email.address + for nomcom in NomCom.objects.filter(group__state__slug='active'): + for state in ('pending','accepted'): + for nominee_position in NomineePosition.objects.filter(nominee__nomcom=nomcom, + state=state, + nominee__duplicated__isnull=True): + if is_time_to_send(nomcom, datetime.date.today(), nominee_position.time.date()): + if state=='pending': + send_accept_reminder_to_nominee(nominee_position) + log('Sent accept reminder to %s' % nominee_position.nominee.email.address) + elif state=='accepted': + send_questionnaire_reminder_to_nominee(nominee_position) + log('Sent questionnaire reminder to %s' % nominee_position.nominee.email.address) + else: + pass diff --git a/ietf/nomcom/migrations/0004_split_reminder_template.py b/ietf/nomcom/migrations/0004_split_reminder_template.py new file mode 100644 index 000000000..65cb0bce9 --- /dev/null +++ b/ietf/nomcom/migrations/0004_split_reminder_template.py @@ -0,0 +1,329 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import DataMigration +from django.db import models + +import textwrap +from ietf.dbtemplate.models import DBTemplate +from ietf.name.models import DBTemplateTypeName +from ietf.group.models import Group + +new_template_content = textwrap.dedent("""\ + Thank you for accepting your nomination for the position of $position. + + Please remember to complete and return the questionnaire for this position at your earliest opportunity. + The questionaire is repeated below for your convenience. + + -------- + +""") + +class Migration(DataMigration): + + def forwards(self, orm): + for group_name in ['defaults','nomcom2013']: + template = DBTemplate.objects.get(path='/nomcom/%s/email/nomination_reminder.txt'%group_name) + template.path='/nomcom/%s/email/nomination_accept_reminder.txt'%group_name + template.title='Email sent to nominees reminding them to accept (or decline) the nominations.' + template.save() + # variables below are copied from the current reminder template. It's not clear this value is what it should be + DBTemplate.objects.create(path='/nomcom/%s/email/questionnaire_reminder.txt'%group_name, + title='Email sent to nominees reminding them to submit completed questionnaires.', + variables='$positions: Nomination positions', + type=DBTemplateTypeName.objects.get(slug='plain'), + content=new_template_content, + group= None if group_name=='defaults' else Group.objects.get(acronym='nomcom2013'), + ) + + + def backwards(self, orm): + for group_name in ['defaults','nomcom2013']: + template = DBTemplate.objects.get(path='/nomcom/%s/email/nomination_accept_reminder.txt'%group_name) + template.path='/nomcom/%s/email/nomination_reminder.txt'%group_name + template.title='Email sent to nominees asking them to accept (or decline) the nominations.' + template.save() + DBTemplate.objects.filter(path='/nomcom/%s/email/questionnaire_reminder.txt'%group_name).delete() + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'dbtemplate.dbtemplate': { + 'Meta': {'object_name': 'DBTemplate'}, + 'content': ('django.db.models.fields.TextField', [], {}), + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']", 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DBTemplateTypeName']"}), + 'variables': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}) + }, + 'doc.document': { + 'Meta': {'object_name': 'Document'}, + 'abstract': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'ad': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'ad_document_set'", 'null': 'True', 'to': "orm['person.Person']"}), + 'authors': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['person.Email']", 'symmetrical': 'False', 'through': "orm['doc.DocumentAuthor']", 'blank': 'True'}), + 'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'external_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}), + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']", 'null': 'True', 'blank': 'True'}), + 'intended_std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.IntendedStdLevelName']", 'null': 'True', 'blank': 'True'}), + 'internal_comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'primary_key': 'True'}), + 'note': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'notify': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '1', 'blank': 'True'}), + 'pages': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'rev': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}), + 'shepherd': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'shepherd_document_set'", 'null': 'True', 'to': "orm['person.Person']"}), + 'states': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}), + 'std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.StdLevelName']", 'null': 'True', 'blank': 'True'}), + 'stream': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.StreamName']", 'null': 'True', 'blank': 'True'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['name.DocTagName']", 'null': 'True', 'blank': 'True'}), + 'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocTypeName']", 'null': 'True', 'blank': 'True'}) + }, + 'doc.documentauthor': { + 'Meta': {'ordering': "['document', 'order']", 'object_name': 'DocumentAuthor'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Email']"}), + 'document': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.Document']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '1'}) + }, + 'doc.state': { + 'Meta': {'ordering': "['type', 'order']", 'object_name': 'State'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'next_states': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'previous_states'", 'blank': 'True', 'to': "orm['doc.State']"}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}), + 'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.StateType']"}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'doc.statetype': { + 'Meta': {'object_name': 'StateType'}, + 'label': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '30', 'primary_key': 'True'}) + }, + 'group.group': { + 'Meta': {'object_name': 'Group'}, + 'acronym': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '40', 'db_index': 'True'}), + 'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']", 'null': 'True', 'blank': 'True'}), + 'charter': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'chartered_group'", 'unique': 'True', 'null': 'True', 'to': "orm['doc.Document']"}), + 'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'list_archive': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'list_email': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'list_subscribe': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']", 'null': 'True', 'blank': 'True'}), + 'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupStateName']", 'null': 'True'}), + 'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupTypeName']", 'null': 'True'}), + 'unused_states': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}), + 'unused_tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['name.DocTagName']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'name.dbtemplatetypename': { + 'Meta': {'ordering': "['order']", 'object_name': 'DBTemplateTypeName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.doctagname': { + 'Meta': {'ordering': "['order']", 'object_name': 'DocTagName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.doctypename': { + 'Meta': {'ordering': "['order']", 'object_name': 'DocTypeName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.feedbacktype': { + 'Meta': {'ordering': "['order']", 'object_name': 'FeedbackType'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.groupstatename': { + 'Meta': {'ordering': "['order']", 'object_name': 'GroupStateName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.grouptypename': { + 'Meta': {'ordering': "['order']", 'object_name': 'GroupTypeName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.intendedstdlevelname': { + 'Meta': {'ordering': "['order']", 'object_name': 'IntendedStdLevelName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.nomineepositionstate': { + 'Meta': {'ordering': "['order']", 'object_name': 'NomineePositionState'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.stdlevelname': { + 'Meta': {'ordering': "['order']", 'object_name': 'StdLevelName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.streamname': { + 'Meta': {'ordering': "['order']", 'object_name': 'StreamName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'nomcom.feedback': { + 'Meta': {'ordering': "['time']", 'object_name': 'Feedback'}, + 'author': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'comments': ('ietf.nomcom.fields.EncryptedTextField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'nomcom': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['nomcom.NomCom']"}), + 'nominees': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['nomcom.Nominee']", 'null': 'True', 'blank': 'True'}), + 'positions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['nomcom.Position']", 'null': 'True', 'blank': 'True'}), + 'subject': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.FeedbackType']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) + }, + 'nomcom.nomcom': { + 'Meta': {'object_name': 'NomCom'}, + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'initial_text': ('django.db.models.fields.TextField', [], {'blank': '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': { + 'Meta': {'object_name': 'Nomination'}, + 'candidate_email': ('django.db.models.fields.EmailField', [], {'max_length': '255'}), + 'candidate_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'candidate_phone': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'comments': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['nomcom.Feedback']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'nominator_email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'nominee': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['nomcom.Nominee']"}), + 'position': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['nomcom.Position']"}), + 'time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'nomcom.nominee': { + 'Meta': {'unique_together': "(('email', 'nomcom'),)", 'object_name': 'Nominee'}, + 'duplicated': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['nomcom.Nominee']", 'null': 'True', 'blank': 'True'}), + 'email': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Email']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'nomcom': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['nomcom.NomCom']"}), + 'nominee_position': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['nomcom.Position']", 'through': "orm['nomcom.NomineePosition']", 'symmetrical': 'False'}) + }, + 'nomcom.nomineeposition': { + 'Meta': {'ordering': "['nominee']", 'unique_together': "(('position', 'nominee'),)", 'object_name': 'NomineePosition'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'nominee': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['nomcom.Nominee']"}), + 'position': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['nomcom.Position']"}), + 'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.NomineePositionState']"}), + 'time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}) + }, + 'nomcom.position': { + 'Meta': {'object_name': 'Position'}, + 'description': ('django.db.models.fields.TextField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'incumbent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Email']"}), + 'is_open': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'nomcom': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['nomcom.NomCom']"}), + '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'}), + 'address': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}), + 'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']", 'null': 'True'}), + 'time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}) + }, + 'person.person': { + 'Meta': {'object_name': 'Person'}, + 'address': ('django.db.models.fields.TextField', [], {'max_length': '255', 'blank': 'True'}), + 'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'ascii': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'ascii_short': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'null': 'True', 'blank': 'True'}) + } + } + + complete_apps = ['nomcom'] diff --git a/ietf/nomcom/migrations/0005_default_template_edits.py b/ietf/nomcom/migrations/0005_default_template_edits.py new file mode 100644 index 000000000..c98528604 --- /dev/null +++ b/ietf/nomcom/migrations/0005_default_template_edits.py @@ -0,0 +1,379 @@ +# encoding: utf-8 +import datetime +import textwrap +from south.db import db +from south.v2 import DataMigration +from django.db import models +from ietf.dbtemplate.models import DBTemplate + + +class Migration(DataMigration): + + def forwards(self, orm): + + for template in DBTemplate.objects.filter(title__startswith="Questionnaire sent to the nomine"): + template.title = self.change_nomine_to_nominee(template.title) + template.save() + + direct_substitutions = { + + 'Email sent to nominator to get a confirmation mail containing feedback in cleartext' + :'Email sent confirming nomination with provided comments in cleartext', + + 'Email sent to feedback author to get a confirmation mail containing feedback in cleartext' + :'Email sent confirming feedback with provided comments in cleartext', + + 'Header of the email that contains the questionnaire sent to the nomine' + :'Header of the email that contains the questionnaire sent to the nominee', + + } + + for str in direct_substitutions: + DBTemplate.objects.filter(title=str).update(title=direct_substitutions[str]) + + template = DBTemplate.objects.get(group=None,title__startswith="Email sent to nominees when") + template.content = textwrap.dedent("""\ + You have been nominated for the position of $position. + + Please follow one of the links below to accept or decline this nomination. + + Accept: http://$domain$accept_url + + Decline: http://$domain$decline_url + + If you accept, you will receive a questionnaire for the position in a subsequent email. + + Best Regards, + """) + template.save() + + template = DBTemplate.objects.get(group=None,title__startswith="Email sent to nominators and secretariat") + template.content = textwrap.dedent("""\ + A new nomination has been received. + + Nominator: $nominator ($nominator_email) + Nominee: $nominee ($nominee_email) + Position: $position + """) + template.save() + + + template = DBTemplate.objects.get(group=None,title__startswith="Header of the email that contains") + template.content = textwrap.dedent("""\ + Hi $nominee, this is the questionnaire for the position $position. + Please follow the directions in the questionnaire closely - you may see that some changes have been made from previous years, so please take note. + + We look forward to reading your questionnaire response! If you have any administrative questions, please send mail to nomcom-chair@ietf.org. Thank you! + """) + template.save() + + template = DBTemplate.objects.get(group=None,title__startswith="Email sent to nominees reminding them to accept ") + template.content = textwrap.dedent("""\ + This is a reminder that you have been nominated for the position of $position. + The Nomcom would like to know if you are willing to be considered. + + Please follow one of the links below to accept or decline this nomination. + + Accept: http://$domain$accept_url + + Decline: http://$domain$decline_url + + If you accept, you will receive a questionnaire for the position in a subsequent email. + + Best Regards, + """) + template.save() + + + def backwards(self, orm): + pass + + def change_nomine_to_nominee(self,string): + p=string.find('[') + if p<0: + return string+"e" + else: + return string[:p]+"e "+string[p:] + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'dbtemplate.dbtemplate': { + 'Meta': {'object_name': 'DBTemplate'}, + 'content': ('django.db.models.fields.TextField', [], {}), + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']", 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DBTemplateTypeName']"}), + 'variables': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}) + }, + 'doc.document': { + 'Meta': {'object_name': 'Document'}, + 'abstract': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'ad': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'ad_document_set'", 'null': 'True', 'to': "orm['person.Person']"}), + 'authors': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['person.Email']", 'symmetrical': 'False', 'through': "orm['doc.DocumentAuthor']", 'blank': 'True'}), + 'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'external_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}), + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']", 'null': 'True', 'blank': 'True'}), + 'intended_std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.IntendedStdLevelName']", 'null': 'True', 'blank': 'True'}), + 'internal_comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'primary_key': 'True'}), + 'note': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'notify': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '1', 'blank': 'True'}), + 'pages': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'rev': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}), + 'shepherd': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'shepherd_document_set'", 'null': 'True', 'to': "orm['person.Person']"}), + 'states': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}), + 'std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.StdLevelName']", 'null': 'True', 'blank': 'True'}), + 'stream': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.StreamName']", 'null': 'True', 'blank': 'True'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['name.DocTagName']", 'null': 'True', 'blank': 'True'}), + 'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocTypeName']", 'null': 'True', 'blank': 'True'}) + }, + 'doc.documentauthor': { + 'Meta': {'ordering': "['document', 'order']", 'object_name': 'DocumentAuthor'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Email']"}), + 'document': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.Document']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '1'}) + }, + 'doc.state': { + 'Meta': {'ordering': "['type', 'order']", 'object_name': 'State'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'next_states': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'previous_states'", 'blank': 'True', 'to': "orm['doc.State']"}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}), + 'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.StateType']"}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'doc.statetype': { + 'Meta': {'object_name': 'StateType'}, + 'label': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '30', 'primary_key': 'True'}) + }, + 'group.group': { + 'Meta': {'object_name': 'Group'}, + 'acronym': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '40', 'db_index': 'True'}), + 'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']", 'null': 'True', 'blank': 'True'}), + 'charter': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'chartered_group'", 'unique': 'True', 'null': 'True', 'to': "orm['doc.Document']"}), + 'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'list_archive': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'list_email': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'list_subscribe': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']", 'null': 'True', 'blank': 'True'}), + 'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupStateName']", 'null': 'True'}), + 'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupTypeName']", 'null': 'True'}), + 'unused_states': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}), + 'unused_tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['name.DocTagName']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'name.dbtemplatetypename': { + 'Meta': {'ordering': "['order']", 'object_name': 'DBTemplateTypeName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.doctagname': { + 'Meta': {'ordering': "['order']", 'object_name': 'DocTagName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.doctypename': { + 'Meta': {'ordering': "['order']", 'object_name': 'DocTypeName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.feedbacktype': { + 'Meta': {'ordering': "['order']", 'object_name': 'FeedbackType'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.groupstatename': { + 'Meta': {'ordering': "['order']", 'object_name': 'GroupStateName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.grouptypename': { + 'Meta': {'ordering': "['order']", 'object_name': 'GroupTypeName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.intendedstdlevelname': { + 'Meta': {'ordering': "['order']", 'object_name': 'IntendedStdLevelName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.nomineepositionstate': { + 'Meta': {'ordering': "['order']", 'object_name': 'NomineePositionState'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.stdlevelname': { + 'Meta': {'ordering': "['order']", 'object_name': 'StdLevelName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.streamname': { + 'Meta': {'ordering': "['order']", 'object_name': 'StreamName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'nomcom.feedback': { + 'Meta': {'ordering': "['time']", 'object_name': 'Feedback'}, + 'author': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'comments': ('ietf.nomcom.fields.EncryptedTextField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'nomcom': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['nomcom.NomCom']"}), + 'nominees': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['nomcom.Nominee']", 'null': 'True', 'blank': 'True'}), + 'positions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['nomcom.Position']", 'null': 'True', 'blank': 'True'}), + 'subject': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.FeedbackType']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) + }, + 'nomcom.nomcom': { + 'Meta': {'object_name': 'NomCom'}, + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'initial_text': ('django.db.models.fields.TextField', [], {'blank': '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': { + 'Meta': {'object_name': 'Nomination'}, + 'candidate_email': ('django.db.models.fields.EmailField', [], {'max_length': '255'}), + 'candidate_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'candidate_phone': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'comments': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['nomcom.Feedback']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'nominator_email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'nominee': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['nomcom.Nominee']"}), + 'position': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['nomcom.Position']"}), + 'time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'nomcom.nominee': { + 'Meta': {'unique_together': "(('email', 'nomcom'),)", 'object_name': 'Nominee'}, + 'duplicated': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['nomcom.Nominee']", 'null': 'True', 'blank': 'True'}), + 'email': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Email']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'nomcom': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['nomcom.NomCom']"}), + 'nominee_position': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['nomcom.Position']", 'through': "orm['nomcom.NomineePosition']", 'symmetrical': 'False'}) + }, + 'nomcom.nomineeposition': { + 'Meta': {'ordering': "['nominee']", 'unique_together': "(('position', 'nominee'),)", 'object_name': 'NomineePosition'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'nominee': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['nomcom.Nominee']"}), + 'position': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['nomcom.Position']"}), + 'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.NomineePositionState']"}), + 'time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}) + }, + 'nomcom.position': { + 'Meta': {'object_name': 'Position'}, + 'description': ('django.db.models.fields.TextField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'incumbent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Email']"}), + 'is_open': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'nomcom': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['nomcom.NomCom']"}), + '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'}), + 'address': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}), + 'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']", 'null': 'True'}), + 'time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}) + }, + 'person.person': { + 'Meta': {'object_name': 'Person'}, + 'address': ('django.db.models.fields.TextField', [], {'max_length': '255', 'blank': 'True'}), + 'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'ascii': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'ascii_short': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'null': 'True', 'blank': 'True'}) + } + } + + complete_apps = ['nomcom'] diff --git a/ietf/nomcom/tests.py b/ietf/nomcom/tests.py index f5f9f4428..3a10ad102 100644 --- a/ietf/nomcom/tests.py +++ b/ietf/nomcom/tests.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- import os import tempfile +import datetime from ietf.utils import TestCase from django.db import IntegrityError @@ -9,6 +10,7 @@ from django.core.files import File from django.contrib.formtools.preview import security_hash from ietf.utils.test_utils import login_testing_unauthorized +from ietf.utils.mail import outbox from ietf.person.models import Email, Person @@ -20,7 +22,8 @@ from ietf.nomcom.models import NomineePosition, Position, Nominee, \ NomineePositionState, Feedback, FeedbackType, \ Nomination from ietf.nomcom.forms import EditChairForm, EditMembersForm -from ietf.nomcom.utils import get_nomcom_by_year +from ietf.nomcom.utils import get_nomcom_by_year, get_or_create_nominee +from ietf.nomcom.management.commands.send_reminders import Command, is_time_to_send class NomcomViewsTest(TestCase): @@ -653,3 +656,63 @@ class FeedbackTest(TestCase): os.unlink(self.privatekey_file.name) os.unlink(self.cert_file.name) + +class ReminderCommandTest(TestCase): + perma_fixtures = ['names', 'nomcom_templates'] + + def setUp(self): + nomcom_test_data() + self.nomcom = get_nomcom_by_year(NOMCOM_YEAR) + + gen = Position.objects.get(nomcom=self.nomcom,name='GEN') + rai = Position.objects.get(nomcom=self.nomcom,name='RAI') + iab = Position.objects.get(nomcom=self.nomcom,name='IAB') + + today = datetime.date.today() + t_minus_3 = today - datetime.timedelta(days=3) + t_minus_4 = today - datetime.timedelta(days=4) + n = get_or_create_nominee(self.nomcom,"Nominee 1","nominee1@example.org",gen,None) + np = n.nomineeposition_set.get(position=gen) + np.time = t_minus_3 + np.save() + n = get_or_create_nominee(self.nomcom,"Nominee 1","nominee1@example.org",iab,None) + np = n.nomineeposition_set.get(position=iab) + np.state = NomineePositionState.objects.get(slug='accepted') + np.time = t_minus_3 + np.save() + n = get_or_create_nominee(self.nomcom,"Nominee 2","nominee2@example.org",rai,None) + np = n.nomineeposition_set.get(position=rai) + np.time = t_minus_4 + np.save() + + def test_is_time_to_send(self): + self.nomcom.reminder_interval = 4 + today = datetime.date.today() + self.assertTrue(is_time_to_send(self.nomcom,today+datetime.timedelta(days=4),today)) + for delta in range(4): + self.assertFalse(is_time_to_send(self.nomcom,today+datetime.timedelta(days=delta),today)) + self.nomcom.reminder_interval = None + self.assertFalse(is_time_to_send(self.nomcom,today,today)) + self.nomcom.reminderdates_set.create(date=today) + self.assertTrue(is_time_to_send(self.nomcom,today,today)) + + def test_command(self): + c = Command() + messages_before=len(outbox) + self.nomcom.reminder_interval = 3 + self.nomcom.save() + c.handle(None,None) + self.assertEqual(len(outbox), messages_before + 2) + self.assertTrue('nominee1@example.org' in outbox[-1]['To']) + self.assertTrue('please complete' in outbox[-1]['Subject']) + self.assertTrue('nominee1@example.org' in outbox[-2]['To']) + self.assertTrue('please accept' in outbox[-2]['Subject']) + messages_before=len(outbox) + self.nomcom.reminder_interval = 4 + self.nomcom.save() + c.handle(None,None) + self.assertEqual(len(outbox), messages_before + 1) + self.assertTrue('nominee2@example.org' in outbox[-1]['To']) + self.assertTrue('please accept' in outbox[-1]['Subject']) + + diff --git a/ietf/nomcom/urls.py b/ietf/nomcom/urls.py index 8e3ccd308..4bfdbfca0 100644 --- a/ietf/nomcom/urls.py +++ b/ietf/nomcom/urls.py @@ -1,5 +1,6 @@ from django.conf.urls.defaults import patterns, url -from django.views.generic.simple import direct_to_template +from django.views.generic.simple import direct_to_template, redirect_to +from ietf.utils.lazy import reverse_lazy from ietf.nomcom.forms import EditChairForm, EditChairFormPreview, \ EditMembersForm, EditMembersFormPreview @@ -17,7 +18,8 @@ urlpatterns = patterns('ietf.nomcom.views', url(r'^(?P\d{4})/private/view-feedback/nominee/(?P\d+)$', 'view_feedback_nominee', name='nomcom_view_feedback_nominee'), url(r'^(?P\d{4})/private/edit/nominee/(?P\d+)$', 'edit_nominee', name='nomcom_edit_nominee'), url(r'^(?P\d{4})/private/merge/$', 'private_merge', name='nomcom_private_merge'), - url(r'^(?P\d{4})/private/send-reminder-mail/$', 'send_reminder_mail', name='nomcom_send_reminder_mail'), +# url(r'^(?P\d{4})/private/send-reminder-mail/$', redirect_to, { 'url': reverse_lazy('nomcom_send_reminder_mail',kwargs={'year':year,'type':'accept'})}), + url(r'^(?P\d{4})/private/send-reminder-mail/(?P\w+)/$', 'send_reminder_mail', name='nomcom_send_reminder_mail'), url(r'^(?P\d{4})/private/edit-members/$', EditMembersFormPreview(EditMembersForm), name='nomcom_edit_members'), url(r'^(?P\d{4})/private/edit-chair/$', EditChairFormPreview(EditChairForm), name='nomcom_edit_chair'), url(r'^(?P\d{4})/private/edit-nomcom/$', 'edit_nomcom', name='nomcom_edit_nomcom'), diff --git a/ietf/nomcom/utils.py b/ietf/nomcom/utils.py index 869925b27..4361cacd6 100644 --- a/ietf/nomcom/utils.py +++ b/ietf/nomcom/utils.py @@ -32,7 +32,8 @@ 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' -NOMINEE_REMINDER_TEMPLATE = 'email/nomination_reminder.txt' +NOMINEE_ACCEPT_REMINDER_TEMPLATE = 'email/nomination_accept_reminder.txt' +NOMINEE_QUESTIONNAIRE_REMINDER_TEMPLATE = 'email/questionnaire_reminder.txt' NOMINATION_RECEIPT_TEMPLATE = 'email/nomination_receipt.txt' FEEDBACK_RECEIPT_TEMPLATE = 'email/feedback_receipt.txt' @@ -40,7 +41,8 @@ DEFAULT_NOMCOM_TEMPLATES = [HOME_TEMPLATE, INEXISTENT_PERSON_TEMPLATE, NOMINEE_EMAIL_TEMPLATE, NOMINATION_EMAIL_TEMPLATE, - NOMINEE_REMINDER_TEMPLATE, + NOMINEE_ACCEPT_REMINDER_TEMPLATE, + NOMINEE_QUESTIONNAIRE_REMINDER_TEMPLATE, NOMINATION_RECEIPT_TEMPLATE, FEEDBACK_RECEIPT_TEMPLATE] @@ -107,7 +109,7 @@ def initialize_questionnaire_for_position(position): content=header_template.content) questionnaire = DBTemplate.objects.create( group=position.nomcom.group, - title=template.title + '[%s]' % position.name, + title=template.title + ' [%s]' % position.name, path='/nomcom/' + position.nomcom.group.acronym + '/' + str(position.id) + '/' + QUESTIONNAIRE_TEMPLATE, variables=template.variables, type_id=template.type_id, @@ -191,15 +193,15 @@ def validate_public_key(public_key): return (not error, error) -def send_reminder_to_nominee(nominee_position): +def send_accept_reminder_to_nominee(nominee_position): today = datetime.date.today().strftime('%Y%m%d') - subject = 'IETF Nomination Information' + subject = 'Reminder: please accept (or decline) your nomination.' from_email = settings.NOMCOM_FROM_EMAIL domain = Site.objects.get_current().domain position = nominee_position.position nomcom = position.nomcom nomcom_template_path = '/nomcom/%s/' % nomcom.group.acronym - mail_path = nomcom_template_path + NOMINEE_REMINDER_TEMPLATE + mail_path = nomcom_template_path + NOMINEE_ACCEPT_REMINDER_TEMPLATE nominee = nominee_position.nominee to_email = nominee.email.address @@ -229,11 +231,42 @@ def send_reminder_to_nominee(nominee_position): body += '\n\n%s' % render_to_string(path, context) send_mail_text(None, to_email, from_email, subject, body) +def send_questionnaire_reminder_to_nominee(nominee_position): + today = datetime.date.today().strftime('%Y%m%d') + subject = 'Reminder: please complete the Nomcom questionnaires for your nomination.' + from_email = settings.NOMCOM_FROM_EMAIL + domain = Site.objects.get_current().domain + position = nominee_position.position + nomcom = position.nomcom + nomcom_template_path = '/nomcom/%s/' % nomcom.group.acronym + mail_path = nomcom_template_path + NOMINEE_QUESTIONNAIRE_REMINDER_TEMPLATE + nominee = nominee_position.nominee + to_email = nominee.email.address -def send_reminder_to_nominees(nominees): - for nominee in nominees: - for nominee_position in nominee.nomineeposition_set.pending(): - send_reminder_to_nominee(nominee_position) + hash = get_hash_nominee_position(today, nominee_position.id) + + context = {'nominee': nominee, + 'position': position, + 'domain': domain, + } + body = render_to_string(mail_path, context) + path = '%s%d/%s' % (nomcom_template_path, position.id, QUESTIONNAIRE_TEMPLATE) + body += '\n\n%s' % render_to_string(path, context) + send_mail_text(None, to_email, from_email, subject, body) + +def send_reminder_to_nominees(nominees,type): + addrs = [] + if type=='accept': + for nominee in nominees: + for nominee_position in nominee.nomineeposition_set.pending(): + send_accept_reminder_to_nominee(nominee_position) + addrs.append(nominee_position.nominee.email.address) + elif type=='questionnaire': + for nominee in nominees: + for nominee_position in nominee.nomineeposition_set.accepted(): + send_questionnaire_reminder_to_nominee(nominee_position) + addrs.append(nominee_position.nominee.email.address) + return addrs def get_or_create_nominee(nomcom, candidate_name, candidate_email, position, author): diff --git a/ietf/nomcom/views.py b/ietf/nomcom/views.py index f0ad78ea1..0c01d1b95 100644 --- a/ietf/nomcom/views.py +++ b/ietf/nomcom/views.py @@ -31,11 +31,9 @@ from ietf.nomcom.forms import (NominateForm, FeedbackForm, QuestionnaireForm, from ietf.nomcom.models import Position, NomineePosition, Nominee, Feedback, NomCom, ReminderDates from ietf.nomcom.utils import (get_nomcom_by_year, store_nomcom_private_key, get_hash_nominee_position, send_reminder_to_nominees, - HOME_TEMPLATE, NOMINEE_REMINDER_TEMPLATE) + HOME_TEMPLATE, NOMINEE_ACCEPT_REMINDER_TEMPLATE,NOMINEE_QUESTIONNAIRE_REMINDER_TEMPLATE) from ietf.ietfauth.utils import role_required -import debug - def index(request): nomcom_list = Group.objects.filter(type__slug='nomcom').order_by('acronym') for nomcom in nomcom_list: @@ -166,11 +164,27 @@ def private_index(request, year): @role_required("Nomcom Chair", "Nomcom Advisor") -def send_reminder_mail(request, year): +def send_reminder_mail(request, year, type): nomcom = get_nomcom_by_year(year) - 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 + + if type=='accept': + interesting_state = 'pending' + mail_path = nomcom_template_path + NOMINEE_ACCEPT_REMINDER_TEMPLATE + reminder_description = 'accept (or decline) a nomination' + selected_tab = 'send_accept_reminder' + elif type=='questionnaire': + interesting_state = 'accepted' + mail_path = nomcom_template_path + NOMINEE_QUESTIONNAIRE_REMINDER_TEMPLATE + reminder_description = 'complete the questionnaire for a nominated position' + selected_tab = 'send_questionnaire_reminder' + else: + raise Http404 + + nominees = Nominee.objects.get_by_nomcom(nomcom).not_duplicated().filter(nomineeposition__state=interesting_state).distinct() + annotated_nominees = list(nominees) + for nominee in annotated_nominees: + nominee.interesting_positions = [x.position.name for x in nominee.nomineeposition_set.all() if x.state.slug==interesting_state] mail_template = DBTemplate.objects.filter(group=nomcom.group, path=mail_path) mail_template = mail_template and mail_template[0] or None message = None @@ -179,16 +193,21 @@ def send_reminder_mail(request, year): selected_nominees = request.POST.getlist('selected') selected_nominees = nominees.filter(id__in=selected_nominees) if selected_nominees: - send_reminder_to_nominees(selected_nominees) - message = ('success', 'An query has been sent to each person, asking them to accept (or decline) the nominations') + addrs = send_reminder_to_nominees(selected_nominees,type) + if addrs: + message = ('success', 'A copy of "%s" has been sent to %s'%(mail_template.title,", ".join(addrs))) + else: + message = {'warning', 'No messages were sent.'} else: - message = ('warning', "Please, select some nominee") + message = ('warning', "Please, select at least one nominee") return render_to_response('nomcom/send_reminder_mail.html', {'nomcom': nomcom, 'year': year, - 'nominees': nominees, + 'nominees': annotated_nominees, 'mail_template': mail_template, - 'selected': 'send_reminder_mail', + 'selected': selected_tab, + 'reminder_description': reminder_description, + 'state_description': NomineePositionState.objects.get(slug=interesting_state).name, 'message': message}, RequestContext(request)) diff --git a/ietf/templates/nomcom/nomcom_private_base.html b/ietf/templates/nomcom/nomcom_private_base.html index 8573fa715..6b5a20451 100644 --- a/ietf/templates/nomcom/nomcom_private_base.html +++ b/ietf/templates/nomcom/nomcom_private_base.html @@ -19,7 +19,8 @@ {% if user|is_chair:year %} | {% if selected == "feedback_pending" %}Pending Feedback{% else %}Pending Feedback{% endif %} | {% if selected == "feedback_email" %}Enter Email Feedback{% else %}Enter Email Feedback{% endif %} | - {% if selected == "send_reminder_mail" %}Send Reminder{% else %}Send Reminder{% endif %} + {% if selected == "send_accept_reminder" %}Send Accept Reminder{% else %}Send Accept Reminder{% endif %} | + {% if selected == "send_questionnaire_reminder" %}Send Questionnaire Reminder{% else %}Send Questionnaire Reminder{% endif %} {% endif %} {% if user|is_chair:year %} diff --git a/ietf/templates/nomcom/private_index.html b/ietf/templates/nomcom/private_index.html index 65b6973c1..5815ec168 100644 --- a/ietf/templates/nomcom/private_index.html +++ b/ietf/templates/nomcom/private_index.html @@ -33,8 +33,6 @@

List of nominees by position

The following is a list of registered nominees. -{% if is_chair %}(You can request confirmation from nominees if they haven't -replied to the nomination notification they have received.){% endif %}

diff --git a/ietf/templates/nomcom/send_reminder_mail.html b/ietf/templates/nomcom/send_reminder_mail.html index 456a32b6c..eea18e111 100644 --- a/ietf/templates/nomcom/send_reminder_mail.html +++ b/ietf/templates/nomcom/send_reminder_mail.html @@ -1,18 +1,15 @@ {% extends "nomcom/nomcom_private_base.html" %} +{% load ietf_filters %} -{% block subtitle %} - Send reminder emails {% endblock %} +{% block subtitle %} - Send reminder messages {% endblock %} {% block nomcom_content %} -

Request nomination acceptance from the listed nominees

+

Send a message to the selected nominees reminding them to {{reminder_description}}

-

-An query will be sent to each person, asking them to accept (or decline) the nomination. +

These are the nominees that are in the '{{state_description}}' state for the listed positions.

-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. -

+

The message that will be sent is shown below the list of nominees.

Nominees who have not responded

@@ -31,7 +28,7 @@ The message that will be sent is shown below the address entry form. {{ nominee }} - {% for nominee_position in nominee.nomineeposition_set.all %}{% ifequal nominee_position.state.slug "pending" %} {{ nominee_position.position.name }}, {% endifequal %}{% endfor %} + {{nominee.interesting_positions|join:", "}} {% endfor %} @@ -46,6 +43,6 @@ The message that will be sent is shown below the address entry form.

The message that will be sent is as follows: {% if mail_template %}(Edit the message){% endif %}

-{% include mail_template.path %} +
{{ mail_template.content|wrap_text:80|escape }}
-{% endblock %} \ No newline at end of file +{% endblock %}