docbow/docbow_project/docbow/notification.py

208 lines
7.5 KiB
Python

import importlib
import logging
from django.conf import settings
from django.template.loader import render_to_string, TemplateDoesNotExist
from django.core.mail import EmailMultiAlternatives
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import force_text
from django_journal import journal as django_journal
from docbow_project.docbow import models
from docbow_project.docbow import app_settings
logger = logging.getLogger(__name__)
class BaseNotifier(object):
def __init__(self):
# accumulate preferences of users first
self.filter = set()
for np in models.NotificationPreference.objects.filter(kind=self.key, value=False):
self.filter.add((np.user.id, np.filetype.id))
def skip(self, notification):
'''Verify if notification should be skipped'''
if notification.document and notification.user:
if (notification.user.id, notification.document.filetype.id) in self.filter:
return True
return False
def generate_part(self, template_path, notification):
template_path = template_path.format(kind=notification.kind)
try:
ctx = {
'notification': notification,
'settings': settings,
}
if isinstance(notification.ctx, dict):
ctx.update(notification.ctx)
return render_to_string(template_path, ctx)
except TemplateDoesNotExist:
return None
def process(sef, notification):
pass
def finish(self):
pass
class MailNotifier(BaseNotifier):
description = _('Email')
key = 'email'
subject_template = 'docbow/email-notification_{kind}_subject.txt'
body_template = 'docbow/email-notification_{kind}_body.txt'
html_body_template = 'docbow/email-notification_{kind}_body.html'
def process(self, notification):
emails = []
if notification.user:
user = notification.user
if user.email:
emails.append(user.email)
if app_settings.PERSONAL_EMAIL:
try:
personal_email = user.docbowprofile.personal_email
if personal_email:
emails.append(personal_email)
except models.DocbowProfile.DoesNotExist:
pass
if not emails and notification.ctx and 'to' in notification.ctx:
emails.extend(notification.ctx['to'])
emails = [email for email in emails if email]
if not emails:
return
headers = {}
if notification.ctx and 'reply_to' in notification.ctx:
headers['Reply-To'] = notification.ctx['reply_to']
to = set(emails)
subject = self.generate_part(self.subject_template, notification)
subject = subject.replace('\n', '').replace('\r', '')
body = self.generate_part(self.body_template, notification)
html_body = self.generate_part(self.html_body_template, notification)
mail = EmailMultiAlternatives(to=list(to), subject=subject, body=body, headers=headers)
if html_body:
mail.attach_alternative(html_body, 'text/html')
mail.send(fail_silently=False)
django_journal.record(
'mail-notify',
'mail notification {notification} ' 'sent for document {document} to {to} of user {recipient}',
notification=notification,
recipient=notification.user,
document=notification.document,
to=','.join(to),
)
class SMSNotifier(BaseNotifier):
'''Variable in sms notification:
- document
- settings
'''
description = _('SMS')
key = 'sms'
body_template = 'docbow/sms-notification_{kind}_body.txt'
def __init__(self):
super(SMSNotifier, self).__init__()
self.mobile_phones = dict()
def process(self, notification):
if not app_settings.MOBILE_PHONE:
return
try:
profile = models.DocbowProfile.objects.get(user=notification.user)
except models.DocbowProfile.DoesNotExist:
return
if not profile.mobile_phone:
return
self.mobile_phones.setdefault((notification.document, notification.kind), []).append(
(notification.user, profile.mobile_phone)
)
@classmethod
def get_carrier(cls):
return resolve_class(settings.DOCBOW_SMS_CARRIER_CLASS)()
def finish(self):
if not self.mobile_phones:
return
exc = None
for key, value in self.mobile_phones.items():
try:
document, kind = key
notification = models.Notification(document=document, kind=kind)
body = self.generate_part(self.body_template, notification)
sms_carrier = self.get_carrier()
for user, phone_number in value:
django_journal.record(
'sms-notify',
'sms notification for document {document} to user '
'{recipient} with phone number {phone_number}',
document=document,
recipient=user,
phone_number=phone_number,
)
sms_carrier.send_sms([v[1] for v in value], body)
except Exception as e:
exc = e
if exc:
raise exc
def resolve_class(class_path):
module, cls_name = class_path.rsplit('.', 1)
module = importlib.import_module(module)
return getattr(module, cls_name)
def get_notifiers():
notification_classes = getattr(
settings, 'DOCBOW_NOTIFIERS', ['docbow_project.docbow.notification.MailNotifier']
)
return [resolve_class(class_path)() for class_path in notification_classes]
def process_notifications():
notifiers = get_notifiers()
for notification in models.Notification.objects.order_by('id').select_for_update().filter(done=False):
for notifier in notifiers:
failures = []
if not notifier.skip(notification):
try:
notifier.process(notification)
except Exception as e:
failures.append(
u'Exception %r when handling with notifier %r' % (force_text(e), notifier.__class__)
)
logger.exception(
'Exception when handling notification %r with notifier %r', notification, notifier
)
django_journal.error_record(
'error',
'notification {notification} failed for ' 'notifier {notifier}',
notification=notification,
notifier=notifier.__class__,
)
notification.done = True
if len(failures) > 0:
notification.failure = ','.join(failures)
notification.save()
for notifier in notifiers:
try:
notifier.finish()
except Exception as e:
logger.exception(
'Exception when finishing handling ' 'notification %r with notifier %r',
notification,
notifier,
)
django_journal.error_record(
'error',
'unable to finish sending ' 'notification with notifier {notifier}, error: {error}',
notifier=notifier.__class__,
error=force_text(e),
)