207 lines
7.4 KiB
Python
207 lines
7.4 KiB
Python
import importlib
|
|
import logging
|
|
|
|
from django.conf import settings
|
|
from django.core.mail import EmailMultiAlternatives
|
|
from django.template.loader import TemplateDoesNotExist, render_to_string
|
|
from django.utils.encoding import force_str
|
|
from django.utils.translation import gettext_lazy as _
|
|
from django_journal import journal as django_journal
|
|
|
|
from docbow_project.docbow import app_settings, models
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class BaseNotifier:
|
|
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().__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(
|
|
'Exception %r when handling with notifier %r' % (force_str(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_str(e),
|
|
)
|