157 lines
6.0 KiB
Python
157 lines
6.0 KiB
Python
# -*- coding: utf-8
|
|
"""
|
|
Functions to send email reminders to users.
|
|
"""
|
|
|
|
import random, time, smtplib
|
|
from datetime import datetime
|
|
from email.header import Header
|
|
from email.utils import formataddr, parseaddr
|
|
from django.core.mail import get_connection, EmailMessage
|
|
from django.contrib.auth.decorators import login_required
|
|
from django.template import loader
|
|
from django.utils import translation
|
|
from django.conf import settings
|
|
from django.http import Http404, HttpResponse
|
|
from django.shortcuts import get_object_or_404
|
|
from .models import Subject, QuestionSet, RunInfo, Questionnaire
|
|
|
|
try: from hashlib import md5
|
|
except: from md5 import md5
|
|
|
|
|
|
def encode_emailaddress(address):
|
|
"""
|
|
Encode an email address as ASCII using the Encoded-Word standard.
|
|
Needed to work around http://code.djangoproject.com/ticket/11144
|
|
"""
|
|
try: return address.encode('ascii')
|
|
except UnicodeEncodeError: pass
|
|
nm, addr = parseaddr(address)
|
|
return formataddr( (str(Header(nm, settings.DEFAULT_CHARSET)), addr) )
|
|
|
|
|
|
def _new_random(subject):
|
|
"""
|
|
Create a short unique randomized string.
|
|
Returns: subject_id + 'z' +
|
|
md5 hexdigest of subject's surname, nextrun date, and a random number
|
|
"""
|
|
return "%dz%s" % (subject.id, md5(bytes(subject.surname + str(subject.nextrun) + hex(random.randint(1,999999)), 'utf-8')).hexdigest()[:6])
|
|
|
|
|
|
def _new_runinfo(subject, questionset):
|
|
"""
|
|
Create a new RunInfo entry with a random code
|
|
|
|
If a unique subject+runid entry already exists, return that instead..
|
|
That should only occurs with manual database changes
|
|
"""
|
|
nextrun = subject.nextrun
|
|
runid = str(nextrun.year)
|
|
(run, created) = Run.objects.get_or_create(runid=runid)
|
|
(r, created) = RunInfo.objects.get_or_create(run=run, subject=subject)
|
|
if created:
|
|
r.random = _new_random(subject)
|
|
r.emailcount = 0
|
|
r.questionset = questionset
|
|
r.save()
|
|
if nextrun.month == 2 and nextrun.day == 29: # the only exception?
|
|
subject.nextrun = datetime(nextrun.year + 1, 2, 28)
|
|
else:
|
|
subject.nextrun = datetime(nextrun.year + 1, nextrun.month, nextrun.day)
|
|
subject.save()
|
|
return r
|
|
|
|
def _send_email(runinfo):
|
|
"Send the email for a specific runinfo entry"
|
|
subject = runinfo.subject
|
|
translation.activate(subject.language)
|
|
tmpl = loader.get_template(settings.QUESTIONNAIRE_EMAIL_TEMPLATE)
|
|
c = {}
|
|
c['surname'] = subject.surname
|
|
c['givenname'] = subject.givenname
|
|
c['gender'] = subject.gender
|
|
c['email'] = subject.email
|
|
c['random'] = runinfo.random
|
|
c['runid'] = runinfo.run.runid
|
|
c['created'] = runinfo.created
|
|
c['site'] = getattr(settings, 'QUESTIONNAIRE_URL', '(settings.QUESTIONNAIRE_URL not set)')
|
|
email = tmpl.render(c)
|
|
emailFrom = settings.QUESTIONNAIRE_EMAIL_FROM
|
|
emailSubject, email = email.split("\n",1) # subject must be on first line
|
|
emailSubject = emailSubject.strip()
|
|
emailFrom = emailFrom.replace("$RUNINFO", runinfo.random)
|
|
emailTo = '"%s, %s" <%s>' % (subject.surname, subject.givenname, subject.email)
|
|
|
|
emailTo = encode_emailaddress(emailTo)
|
|
emailFrom = encode_emailaddress(emailFrom)
|
|
|
|
try:
|
|
conn = get_connection()
|
|
msg = EmailMessage(emailSubject, email, emailFrom, [ emailTo ],
|
|
connection=conn)
|
|
msg.send()
|
|
runinfo.emailcount = 1 + runinfo.emailcount
|
|
runinfo.emailsent = datetime.now()
|
|
runinfo.lastemailerror = "OK, accepted by server"
|
|
runinfo.save()
|
|
return True
|
|
except smtplib.SMTPRecipientsRefused:
|
|
runinfo.lastemailerror = "SMTP Recipient Refused"
|
|
except smtplib.SMTPHeloError:
|
|
runinfo.lastemailerror = "SMTP Helo Error"
|
|
except smtplib.SMTPSenderRefused:
|
|
runinfo.lastemailerror = "SMTP Sender Refused"
|
|
except smtplib.SMTPDataError:
|
|
runinfo.lastemailerror = "SMTP Data Error"
|
|
runinfo.save()
|
|
return False
|
|
|
|
|
|
def send_emails(request=None, qname=None):
|
|
"""
|
|
1. Create a runinfo entry for each subject who is due and has state 'active'
|
|
2. Send an email for each runinfo entry whose subject receives email,
|
|
providing that the last sent email was sent more than a week ago.
|
|
|
|
This can be called either by "./manage.py questionnaire_emails" (without
|
|
request) or through the web, if settings.EMAILCODE is set and matches.
|
|
"""
|
|
if request and request.GET.get('code') != getattr(settings,'EMAILCODE', False):
|
|
raise Http404
|
|
if not qname:
|
|
qname = getattr(settings, 'QUESTIONNAIRE_DEFAULT', None)
|
|
if not qname:
|
|
raise Exception("QUESTIONNAIRE_DEFAULT not in settings")
|
|
questionnaire = Questionnaire.objects.get(name=qname)
|
|
questionset = QuestionSet.objects.filter(questionnaire__name=qname).order_by('sortid')
|
|
if not questionset:
|
|
raise Exception("No questionsets for questionnaire '%s' (in settings.py)" % qname)
|
|
return
|
|
questionset = questionset[0]
|
|
|
|
viablesubjects = Subject.objects.filter(nextrun__lte = datetime.now(), state='active')
|
|
for s in viablesubjects:
|
|
r = _new_runinfo(s, questionset)
|
|
runinfos = RunInfo.objects.filter(subject__formtype='email', questionset__questionnaire=questionnaire)
|
|
WEEKAGO = time.time() - (60 * 60 * 24 * 7) # one week ago
|
|
outlog = []
|
|
for r in runinfos:
|
|
if r.run.runid.startswith('test:'):
|
|
continue
|
|
if r.emailcount == -1:
|
|
continue
|
|
if r.emailcount == 0 or time.mktime(r.emailsent.timetuple()) < WEEKAGO:
|
|
try:
|
|
if _send_email(r):
|
|
outlog.append(u"[%s] %s, %s: OK" % (r.run.runid, r.subject.surname, r.subject.givenname))
|
|
else:
|
|
outlog.append(u"[%s] %s, %s: %s" % (r.run.runid, r.subject.surname, r.subject.givenname, r.lastemailerror))
|
|
except Exception as e:
|
|
outlog.append("Exception: [%s] %s: %s" % (r.run.runid, r.subject.surname, str(e)))
|
|
if request:
|
|
return HttpResponse("Sent Questionnaire Emails:\n "
|
|
+"\n ".join(outlog), content_type="text/plain")
|
|
return "\n".join(outlog)
|