442 lines
18 KiB
Python
442 lines
18 KiB
Python
import datetime
|
|
import hashlib
|
|
import os
|
|
import re
|
|
import tempfile
|
|
|
|
from email.header import decode_header
|
|
from email.iterators import typed_subpart_iterator
|
|
from email import message_from_string
|
|
|
|
from django.conf import settings
|
|
from django.contrib.sites.models import Site
|
|
from django.core.exceptions import ObjectDoesNotExist
|
|
from django.core.urlresolvers import reverse
|
|
from django.template.loader import render_to_string
|
|
from django.shortcuts import get_object_or_404
|
|
from django.utils.encoding import smart_str
|
|
|
|
from ietf.dbtemplate.models import DBTemplate
|
|
from ietf.person.models import Email, Person
|
|
from ietf.mailtrigger.utils import gather_address_lists
|
|
from ietf.utils.pipe import pipe
|
|
from ietf.utils import unaccent
|
|
from ietf.utils.mail import send_mail_text, send_mail
|
|
from ietf.utils.log import log
|
|
|
|
import debug # pyflakes:ignore
|
|
|
|
MAIN_NOMCOM_TEMPLATE_PATH = '/nomcom/defaults/'
|
|
QUESTIONNAIRE_TEMPLATE = 'position/questionnaire.txt'
|
|
HEADER_QUESTIONNAIRE_TEMPLATE = 'position/header_questionnaire.txt'
|
|
REQUIREMENTS_TEMPLATE = 'position/requirements'
|
|
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_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'
|
|
|
|
DEFAULT_NOMCOM_TEMPLATES = [HOME_TEMPLATE,
|
|
INEXISTENT_PERSON_TEMPLATE,
|
|
NOMINEE_EMAIL_TEMPLATE,
|
|
NOMINATION_EMAIL_TEMPLATE,
|
|
NOMINEE_ACCEPT_REMINDER_TEMPLATE,
|
|
NOMINEE_QUESTIONNAIRE_REMINDER_TEMPLATE,
|
|
NOMINATION_RECEIPT_TEMPLATE,
|
|
FEEDBACK_RECEIPT_TEMPLATE]
|
|
|
|
|
|
def get_nomcom_by_year(year):
|
|
from ietf.nomcom.models import NomCom
|
|
return get_object_or_404(NomCom,
|
|
group__acronym__icontains=year,
|
|
)
|
|
|
|
|
|
def get_year_by_nomcom(nomcom):
|
|
acronym = nomcom.group.acronym
|
|
m = re.search('(?P<year>\d\d\d\d)', acronym)
|
|
return m.group(0)
|
|
|
|
|
|
def get_user_email(user):
|
|
# a user object already has an email field, but we don't want to
|
|
# overwrite anything that might be there, and we don't know that
|
|
# what's there is the right thing, so we cache the lookup results in a
|
|
# separate attribute
|
|
if not hasattr(user, "_email_cache"):
|
|
user._email_cache = None
|
|
if hasattr(user, "person"):
|
|
emails = user.person.email_set.filter(active=True).order_by('-time')
|
|
if emails:
|
|
user._email_cache = emails[0]
|
|
for email in emails:
|
|
if email.address == user.username:
|
|
user._email_cache = email
|
|
else:
|
|
try:
|
|
user._email_cache = Email.objects.get(address=user.username)
|
|
except ObjectDoesNotExist:
|
|
pass
|
|
return user._email_cache
|
|
|
|
def get_hash_nominee_position(date, nominee_position_id):
|
|
return hashlib.md5('%s%s%s' % (settings.SECRET_KEY, date, nominee_position_id)).hexdigest()
|
|
|
|
|
|
def initialize_templates_for_group(group):
|
|
for template_name in DEFAULT_NOMCOM_TEMPLATES:
|
|
template_path = MAIN_NOMCOM_TEMPLATE_PATH + template_name
|
|
template = DBTemplate.objects.get(path=template_path)
|
|
DBTemplate.objects.create(
|
|
group=group.group,
|
|
title=template.title,
|
|
path='/nomcom/' + group.group.acronym + '/' + template_name,
|
|
variables=template.variables,
|
|
type_id=template.type_id,
|
|
content=template.content)
|
|
|
|
|
|
def initialize_questionnaire_for_position(position):
|
|
questionnaire_path = MAIN_NOMCOM_TEMPLATE_PATH + QUESTIONNAIRE_TEMPLATE
|
|
header_questionnaire_path = MAIN_NOMCOM_TEMPLATE_PATH + HEADER_QUESTIONNAIRE_TEMPLATE
|
|
template = DBTemplate.objects.get(path=questionnaire_path)
|
|
header_template = DBTemplate.objects.get(path=header_questionnaire_path)
|
|
DBTemplate.objects.create(
|
|
group=position.nomcom.group,
|
|
title=header_template.title + ' [%s]' % position.name,
|
|
path='/nomcom/' + position.nomcom.group.acronym + '/' + str(position.id) + '/' + HEADER_QUESTIONNAIRE_TEMPLATE,
|
|
variables=header_template.variables,
|
|
type_id=header_template.type_id,
|
|
content=header_template.content)
|
|
questionnaire = DBTemplate.objects.create(
|
|
group=position.nomcom.group,
|
|
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,
|
|
content=template.content)
|
|
return questionnaire
|
|
|
|
|
|
def initialize_requirements_for_position(position):
|
|
requirements_path = MAIN_NOMCOM_TEMPLATE_PATH + REQUIREMENTS_TEMPLATE
|
|
template = DBTemplate.objects.get(path=requirements_path)
|
|
return DBTemplate.objects.create(
|
|
group=position.nomcom.group,
|
|
title=template.title + ' [%s]' % position.name,
|
|
path='/nomcom/' + position.nomcom.group.acronym + '/' + str(position.id) + '/' + REQUIREMENTS_TEMPLATE,
|
|
variables=template.variables,
|
|
type_id=template.type_id,
|
|
content=template.content)
|
|
|
|
|
|
def delete_nomcom_templates(nomcom):
|
|
nomcom_template_path = '/nomcom/' + nomcom.group.acronym
|
|
DBTemplate.objects.filter(path__contains=nomcom_template_path).delete()
|
|
|
|
|
|
def retrieve_nomcom_private_key(request, year):
|
|
private_key = request.session.get('NOMCOM_PRIVATE_KEY_%s' % year, None)
|
|
|
|
if not private_key:
|
|
return private_key
|
|
|
|
command = "%s bf -d -in /dev/stdin -k \"%s\" -a"
|
|
code, out, error = pipe(command % (settings.OPENSSL_COMMAND,
|
|
settings.SECRET_KEY), private_key)
|
|
if code != 0:
|
|
log("openssl error: %s:\n Error %s: %s" %(command, code, error))
|
|
return out
|
|
|
|
|
|
def store_nomcom_private_key(request, year, private_key):
|
|
if not private_key:
|
|
request.session['NOMCOM_PRIVATE_KEY_%s' % year] = ''
|
|
else:
|
|
command = "%s bf -e -in /dev/stdin -k \"%s\" -a"
|
|
code, out, error = pipe(command % (settings.OPENSSL_COMMAND,
|
|
settings.SECRET_KEY), private_key)
|
|
if code != 0:
|
|
log("openssl error: %s:\n Error %s: %s" %(command, code, error))
|
|
if error:
|
|
out = ''
|
|
request.session['NOMCOM_PRIVATE_KEY_%s' % year] = out
|
|
|
|
|
|
def validate_private_key(key):
|
|
key_file = tempfile.NamedTemporaryFile(delete=False)
|
|
key_file.write(key)
|
|
key_file.close()
|
|
|
|
command = "%s rsa -in %s -check -noout"
|
|
code, out, error = pipe(command % (settings.OPENSSL_COMMAND,
|
|
key_file.name))
|
|
if code != 0:
|
|
log("openssl error: %s:\n Error %s: %s" %(command, code, error))
|
|
|
|
os.unlink(key_file.name)
|
|
return (not error, error)
|
|
|
|
|
|
def validate_public_key(public_key):
|
|
key_file = tempfile.NamedTemporaryFile(delete=False)
|
|
for chunk in public_key.chunks():
|
|
key_file.write(chunk)
|
|
key_file.close()
|
|
|
|
command = "%s x509 -in %s -noout"
|
|
code, out, error = pipe(command % (settings.OPENSSL_COMMAND,
|
|
key_file.name))
|
|
if code != 0:
|
|
log("openssl error: %s:\n Error %s: %s" %(command, code, error))
|
|
|
|
os.unlink(key_file.name)
|
|
return (not error, error)
|
|
|
|
|
|
def send_accept_reminder_to_nominee(nominee_position):
|
|
today = datetime.date.today().strftime('%Y%m%d')
|
|
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_ACCEPT_REMINDER_TEMPLATE
|
|
nominee = nominee_position.nominee
|
|
(to_email, cc) = gather_address_lists('nomination_accept_reminder',nominee=nominee.email.address)
|
|
|
|
hash = get_hash_nominee_position(today, nominee_position.id)
|
|
accept_url = reverse('nomcom_process_nomination_status',
|
|
None,
|
|
args=(get_year_by_nomcom(nomcom),
|
|
nominee_position.id,
|
|
'accepted',
|
|
today,
|
|
hash))
|
|
decline_url = reverse('nomcom_process_nomination_status',
|
|
None,
|
|
args=(get_year_by_nomcom(nomcom),
|
|
nominee_position.id,
|
|
'declined',
|
|
today,
|
|
hash))
|
|
|
|
context = {'nominee': nominee,
|
|
'position': position,
|
|
'domain': domain,
|
|
'accept_url': accept_url,
|
|
'decline_url': decline_url}
|
|
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, cc=cc)
|
|
|
|
def send_questionnaire_reminder_to_nominee(nominee_position):
|
|
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,cc) = gather_address_lists('nomcom_questionnaire_reminder',nominee=nominee.email.address)
|
|
|
|
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, cc=cc)
|
|
|
|
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().without_questionnaire_response():
|
|
send_questionnaire_reminder_to_nominee(nominee_position)
|
|
addrs.append(nominee_position.nominee.email.address)
|
|
return addrs
|
|
|
|
|
|
def make_nomineeposition(nomcom, candidate, position, author):
|
|
from ietf.nomcom.models import Nominee, NomineePosition
|
|
|
|
nomcom_template_path = '/nomcom/%s/' % nomcom.group.acronym
|
|
|
|
# Add the nomination for a particular position
|
|
nominee, created = Nominee.objects.get_or_create(person=candidate,email=candidate.email(), nomcom=nomcom)
|
|
while nominee.duplicated:
|
|
nominee = nominee.duplicated
|
|
nominee_position, nominee_position_created = NomineePosition.objects.get_or_create(position=position, nominee=nominee)
|
|
|
|
if nominee_position_created:
|
|
# send email to nominee
|
|
subject = 'IETF Nomination Information'
|
|
from_email = settings.NOMCOM_FROM_EMAIL
|
|
(to_email, cc) = gather_address_lists('nomination_new_nominee',nominee=nominee.email.address)
|
|
domain = Site.objects.get_current().domain
|
|
today = datetime.date.today().strftime('%Y%m%d')
|
|
hash = get_hash_nominee_position(today, nominee_position.id)
|
|
accept_url = reverse('nomcom_process_nomination_status',
|
|
None,
|
|
args=(get_year_by_nomcom(nomcom),
|
|
nominee_position.id,
|
|
'accepted',
|
|
today,
|
|
hash))
|
|
decline_url = reverse('nomcom_process_nomination_status',
|
|
None,
|
|
args=(get_year_by_nomcom(nomcom),
|
|
nominee_position.id,
|
|
'declined',
|
|
today,
|
|
hash))
|
|
|
|
context = {'nominee': nominee.person.name,
|
|
'position': position.name,
|
|
'domain': domain,
|
|
'accept_url': accept_url,
|
|
'decline_url': decline_url}
|
|
|
|
path = nomcom_template_path + NOMINEE_EMAIL_TEMPLATE
|
|
send_mail(None, to_email, from_email, subject, path, context, cc=cc)
|
|
|
|
# send email to nominee with questionnaire
|
|
if nomcom.send_questionnaire:
|
|
subject = '%s Questionnaire' % position
|
|
from_email = settings.NOMCOM_FROM_EMAIL
|
|
(to_email, cc) = gather_address_lists('nomcom_questionnaire',nominee=nominee.email.address)
|
|
context = {'nominee': nominee.person.name,
|
|
'position': position.name}
|
|
path = '%s%d/%s' % (nomcom_template_path,
|
|
position.id, HEADER_QUESTIONNAIRE_TEMPLATE)
|
|
body = render_to_string(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, cc=cc)
|
|
|
|
# send emails to nomcom chair
|
|
subject = 'Nomination Information'
|
|
from_email = settings.NOMCOM_FROM_EMAIL
|
|
(to_email, cc) = gather_address_lists('nomination_received',nomcom=nomcom)
|
|
context = {'nominee': nominee.person.name,
|
|
'nominee_email': nominee.email.address,
|
|
'position': position.name}
|
|
|
|
if author:
|
|
context.update({'nominator': author.person.name,
|
|
'nominator_email': author.address})
|
|
else:
|
|
context.update({'nominator': 'Anonymous',
|
|
'nominator_email': ''})
|
|
|
|
path = nomcom_template_path + NOMINATION_EMAIL_TEMPLATE
|
|
send_mail(None, to_email, from_email, subject, path, context, cc=cc)
|
|
|
|
return nominee
|
|
|
|
def make_nomineeposition_for_newperson(nomcom, candidate_name, candidate_email, position, author):
|
|
|
|
# This is expected to fail if called with an existing email address
|
|
email = Email.objects.create(address=candidate_email)
|
|
person = Person.objects.create(name=candidate_name,
|
|
ascii=unaccent.asciify(candidate_name),
|
|
address=candidate_email)
|
|
email.person = person
|
|
email.save()
|
|
|
|
# send email to secretariat and nomcomchair to warn about the new person
|
|
subject = 'New person is created'
|
|
from_email = settings.NOMCOM_FROM_EMAIL
|
|
(to_email, cc) = gather_address_lists('nomination_created_person',nomcom=nomcom)
|
|
context = {'email': email.address,
|
|
'fullname': email.person.name,
|
|
'person_id': email.person.id}
|
|
nomcom_template_path = '/nomcom/%s/' % nomcom.group.acronym
|
|
path = nomcom_template_path + INEXISTENT_PERSON_TEMPLATE
|
|
send_mail(None, to_email, from_email, subject, path, context, cc=cc)
|
|
|
|
return make_nomineeposition(nomcom, email.person, position, author)
|
|
|
|
def getheader(header_text, default="ascii"):
|
|
"""Decode the specified header"""
|
|
|
|
headers = decode_header(header_text)
|
|
header_sections = [unicode(text, charset or default)
|
|
for text, charset in headers]
|
|
return u"".join(header_sections)
|
|
|
|
|
|
def get_charset(message, default="ascii"):
|
|
"""Get the message charset"""
|
|
|
|
if message.get_content_charset():
|
|
return message.get_content_charset()
|
|
|
|
if message.get_charset():
|
|
return message.get_charset()
|
|
|
|
return default
|
|
|
|
|
|
def get_body(message):
|
|
"""Get the body of the email message"""
|
|
|
|
if message.is_multipart():
|
|
# get the plain text version only
|
|
text_parts = [part for part in typed_subpart_iterator(message,
|
|
'text',
|
|
'plain')]
|
|
body = []
|
|
for part in text_parts:
|
|
charset = get_charset(part, get_charset(message))
|
|
body.append(unicode(part.get_payload(decode=True),
|
|
charset,
|
|
"replace"))
|
|
|
|
return u"\n".join(body).strip()
|
|
|
|
else: # if it is not multipart, the payload will be a string
|
|
# representing the message body
|
|
body = unicode(message.get_payload(decode=True),
|
|
get_charset(message),
|
|
"replace")
|
|
return body.strip()
|
|
|
|
|
|
def parse_email(text):
|
|
if isinstance(text, unicode):
|
|
text = smart_str(text)
|
|
msg = message_from_string(text)
|
|
|
|
body = get_body(msg)
|
|
subject = getheader(msg['Subject'])
|
|
return msg['From'], subject, body
|
|
|
|
|
|
def create_feedback_email(nomcom, msg):
|
|
from ietf.nomcom.models import Feedback
|
|
by, subject, body = parse_email(msg)
|
|
#name, addr = parseaddr(by)
|
|
|
|
feedback = Feedback(nomcom=nomcom,
|
|
author=by,
|
|
subject=subject or '',
|
|
comments=body)
|
|
feedback.save()
|
|
return feedback
|