Commit 40f1342b authored by Dylann Cordel's avatar Dylann Cordel

send email and postman messages

parent 51984145
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.core import mail
from admin_message.models import RecipientMixin
from collections import OrderedDict
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group, User
from django.core import mail
def send_messages(sender, subject, recipients, txt, html=None):
......@@ -29,25 +32,40 @@ def clean_recipient_name(name):
return name.replace('"', '').strip()
def get_final_recipients_from_misc(obj):
def get_senders(user):
"""
Returns an OrderedDict of available identities to use as "sender".
Email must be used as key and display name as value
"""
senders = OrderedDict()
if user:
senders[user.email] = "%s %s" % (user.first_name, user.last_name)
return senders
def get_default_message(user):
return ''
def get_final_recipients(obj):
"""
Returns a list of valid email recipient:
`"display name" <email>`
or just `email` if there is not any display name available
Returns a OrderedDict of valid email recipient:
email as key and
`"display name" <email>` or just `email` as value if there
is not any display name available
"""
user_model = get_user_model()
recipients = []
if isinstance(obj, user_model):
recipients = OrderedDict()
if isinstance(obj, (user_model, User)):
email_field = (user_model.get_email_field_name()
if hasattr(user_model, 'get_email_field_name')
else 'email')
email = getattr(obj, email_field)
if email
if hasattr(obj, 'get_full_name'):
full_name = RecipientMixin.clean_recipient_name(obj.get_full_name())
if full_name:
return recipients.append('"%s" <%s>' % (full_name, email))
return recipients.append(email)
if email:
full_name = (clean_recipient_name(obj.get_full_name())
if hasattr(obj, 'get_full_name') else None)
recipients[email] = '"%s" <%s>' % (full_name, email) if full_name else email
elif isinstance(obj, Group):
user_model = obj.user_set.model
email_field = (user_model.get_email_field_name()
......@@ -57,7 +75,7 @@ def get_final_recipients_from_misc(obj):
email = getattr(user, email_field)
if not email:
continue
full_name = (RecipientMixin.clean_recipient_name(obj.get_full_name())
full_name = (clean_recipient_name(obj.get_full_name())
if hasattr(obj, 'get_full_name') else '')
recipients.append('"%s" <%s>' % (full_name, email) if full_name else email)
recipients[email] = '"%s" <%s>' % (full_name, email) if full_name else email
return recipients
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from __future__ import absolute_import
from collections import OrderedDict
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from postman.api import pm_broadcast
def send_messages(sender, subject, recipients, txt, html=None, extra_form_data=None):
user_model = get_user_model()
sender = user_model.objects.get(pk=sender)
recipients = [u for u in user_model.objects.filter(pk__in=recipients.keys())]
skip_notification = extra_form_data.get('skip_notification', False)
pm_broadcast(sender, recipients, subject, txt, skip_notification)
def clean_recipient_name(name):
if name:
return name.replace('"', '').strip()
def get_senders(user):
"""
Returns an OrderedDict of available identities to use as "sender".
User.pk must be used as key and display name as value
"""
senders = OrderedDict()
if user:
senders[user.pk] = "%s %s" % (user.first_name, user.last_name)
return senders
def get_default_message(user):
return ''
def get_final_recipients(obj):
"""
Returns an OrderedDict of valid users pk recipients:
"pk" as key
and `"display name" <username:pk>`
or just `username:pk` if there is not any display name available
"""
user_model = get_user_model()
recipients = OrderedDict()
if isinstance(obj, user_model):
full_name = (clean_recipient_name(obj.get_full_name())
if hasattr(obj, 'get_full_name') else None)
if full_name:
recipients[obj.pk] = '"%s" <%s:%d>' % (full_name, obj.get_username(), obj.pk)
else:
recipients[obj.pk] = '%s:%d' % (obj.get_username(), obj.pk)
elif isinstance(obj, Group):
user_model = obj.user_set.model
for user in obj.user_set.filter(active=True):
full_name = (clean_recipient_name(obj.get_full_name())
if hasattr(obj, 'get_full_name') else '')
if full_name:
recipients[obj.pk] = '"%s" <%s:%d>' % (full_name, user.get_username(), user.pk)
else:
recipients[obj.pk] = '%s:%d' % (user.get_username(), user.pk)
return recipients
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from collections import OrderedDict
from datetime import datetime
import html2text
from html2text import HTML2Text
from django.contrib import admin
from django.contrib import messages
......@@ -13,7 +14,6 @@ from django.template.response import TemplateResponse
from django.utils.translation import ugettext_lazy as _
from admin_message.models import Message
# from admin_message.models import RecipientMixin
from admin_message.forms import SendMessageForm
from admin_message.conf import app_settings
......@@ -27,23 +27,23 @@ class SendMessageAdminMixin(object):
intermédiaire pour tester et écrire le message.
"""
# def __init__(self, *args, **kwargs):
# super(SendMessageAdminMixin, self).__init__(*args, **kwargs)
# if not issubclass(self.model, RecipientMixin):
# raise Exception('%s model is not a subclass of RecipientMixin' % self.model)
def get_actions(self, request):
actions = super(SendMessageAdminMixin, self).get_actions(request)
if 'send_message' not in actions:
actions['send_message'] = self.get_action('send_message')
for message_type, infos in app_settings.MESSAGE_TYPES.items():
action_code = 'admin_message_send_%s' % message_type
if action_code not in actions:
actions[action_code] = (self.get_action('_admin_message_send')[0],
action_code, infos['label'])
return actions
def get_sendmessage_form(self, request, queryset):
def _admin_message_get_sendmessage_form(self, request, queryset, message_type):
data = request.POST if 'send_message' in request.POST else None
form = SendMessageForm(queryset, request.user, data)
form = SendMessageForm(queryset, request.user, message_type, data)
return form
def send_message(self, request, queryset):
def _admin_message_send(self, request, queryset):
action = request.POST['action']
message_type = action.replace('admin_message_send_', '')
opts = self.model._meta
app_label = opts.app_label
......@@ -53,27 +53,27 @@ class SendMessageAdminMixin(object):
# The user validated the form
if request.method == 'POST' and 'send_message' in request.POST:
form = self.get_sendmessage_form(request, queryset)
form = self._admin_message_get_sendmessage_form(request, queryset, message_type)
if form.is_valid():
sender = form.cleaned_data['sender']
message = form.cleaned_data['message']
objects = form.cleaned_data['objects']
kwargs = {
'request': request,
'objects': form.cleaned_data['objects'],
'subject': form.cleaned_data['subject'],
'sender': form.cleaned_data['sender'],
'message': form.cleaned_data['message'],
'message_type': message_type,
}
kwargs['extra_form_data'] = dict(set(form.cleaned_data) - set(kwargs))
if 'test' in request.POST:
test_email = form.cleaned_data['sender']
self._send_message(request, objects,
form.cleaned_data['subject'],
test_email=test_email,
sender=sender, message=message)
kwargs['test_recipient'] = form.cleaned_data['sender']
self._admin_message_send_message(**kwargs)
else:
self._send_message(request, objects,
form.cleaned_data['subject'],
sender=sender, message=message)
self._admin_message_send_message(**kwargs)
# Return None to display the change list page again.
return None
else:
form = self.get_sendmessage_form(request, queryset)
form = self._admin_message_get_sendmessage_form(request, queryset, message_type)
if len(queryset) == 1:
objects_name = opts.verbose_name
......@@ -90,23 +90,20 @@ class SendMessageAdminMixin(object):
'opts': opts,
'app_label': app_label,
'action_checkbox_name': admin.helpers.ACTION_CHECKBOX_NAME,
'action': 'send_message',
'action': action,
}
# Display the confirmation page
return TemplateResponse(
request,
'srn/admin/send_message.html',
'admin_message/admin/send_message.html',
context,
current_app=self.admin_site.name,
)
send_message.short_description = 'Envoyer un message'
def _get_message_body(self, template, message, context={}):
def _admin_message_get_message_body(self, template, message, context={}):
if message:
h2t = html2text.HTML2Text()
# Ignore converting links from HTML
h2t.ignore_links = True
h2t = HTML2Text()
txt_message = h2t.handle(message)
else:
txt_message = ''
......@@ -120,35 +117,36 @@ class SendMessageAdminMixin(object):
html = None
return (txt, html)
def _send_message(
self, request, objects, subject, message_type, test_recipient=None,
sender=None, message=None
def _admin_message_send_message(
self,
request, objects, subject, message_type,
test_recipient=None, sender=None, message=None, extra_form_data=None
):
if test_recipient:
objects = objects[:1]
template = app_settings.MESSAGE_TYPES[message_type]['template']
(txt, html) = self._get_message_body(template, message, context={'subject': subject,
'sender': sender})
get_final_recipients = app_settings.MESSAGE_TYPES[message_type]['get_final_recipients']
(txt, html) = self._admin_message_get_message_body(template, message,
context={'subject': subject,
'sender': sender})
recipients = []
recipients = OrderedDict()
for obj in objects:
for recipient in obj.get_final_recipients(message_type):
for unique, recipient in get_final_recipients(obj).items():
if test_recipient:
recipient = test_recipient
elif not recipient:
continue
if recipient in recipients:
unique = recipient = test_recipient
if unique in recipients:
if not test_recipient:
messages.info(request,
_('A message for "{recipient}" has been ignored because '
'this one was duplicated in recipients list'
).format(recipient=recipient))
continue
recipients.append(recipient)
recipients[unique] = recipient
send_messages = app_settings.MESSAGE_TYPES[message_type]['callable']
send_messages(sender, subject, recipients, txt, html)
send_messages = app_settings.MESSAGE_TYPES[message_type]['send_messages']
send_messages(sender, subject, recipients, txt, html, extra_form_data)
if test_recipient:
messages.info(
......@@ -164,11 +162,12 @@ class SendMessageAdminMixin(object):
msg = Message(
subject=subject,
post_date=datetime.now(),
txt=txt,
html=html,
sent_txt=txt,
sent_html=html,
message=message,
author=request.user,
sender=sender,
recipients=', '.join(recipients),
recipients=', '.join(recipients.values()),
test=bool(test_recipient),
message_type=message_type,
)
......
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from collections import OrderedDict
from importlib import import_module
import sys
from django.utils.translation import ugettext_lazy as _
class AppSettings(object):
......@@ -19,26 +15,37 @@ class AppSettings(object):
if hasattr(self, '_MESSAGE_TYPES'):
return self._MESSAGE_TYPES
message_types = getattr(self.settings, 'ADMIN_MESSAGE_MESSAGE_TYPES', {
'EMAIL': {'path_send_message': 'admin_message.adapters.email.send_message',
'path_get_final_recipients_from_misc': ('admin_message.adapters.email'
'.get_final_recipients_from_misc')
'template': 'admin_message/email'}
})
message_types = getattr(self.settings, 'ADMIN_MESSAGE_MESSAGE_TYPES', None)
if message_types is None:
message_types = {
'EMAIL': {
'path_send_messages': 'admin_message.adapters.email.send_messages',
'path_get_final_recipients': 'admin_message.adapters.email.get_final_recipients',
'path_get_senders': 'admin_message.adapters.email.get_senders',
'path_get_default_message': 'admin_message.adapters.email.get_default_message',
'label': _('Send an email'),
'template': 'admin_message/message_email',
}
}
if 'postman' in self.settings.INSTALLED_APPS:
message_types['POSTMAN'] = {
'path_send_messages': 'admin_message.adapters.postman.send_messages',
'path_get_final_recipients': 'admin_message.adapters.postman.get_final_recipients',
'path_get_senders': 'admin_message.adapters.postman.get_senders',
'path_get_default_message': 'admin_message.adapters.postman.get_default_message',
'label': _('Send private message'),
'template': 'admin_message/message_postman',
}
if not isinstance(message_types, OrderedDict):
message_types = OrderedDict(message_types)
fnames = ('send_messages', 'get_final_recipients', 'get_senders', 'get_default_message')
for message_type, d in message_types.items():
module_path, func_name = d['path_send_message'].rsplit('.', 1)
func = getattr(import_module(module_path), func_name)
if not callable(func):
raise Exception('"%s" from "%s" is not callable' % (module_path, func_name))
message_types[message_type]['send_message'] = func
module_path, func_name = d['path_get_final_recipients_from_misc'].rsplit('.', 1)
func = getattr(import_module(module_path), func_name)
if not callable(func):
raise Exception('"%s" from "%s" is not callable' % (module_path, func_name))
message_types[message_type]['path_get_final_recipients_from_misc'] = func
for fname in fnames:
module_path, func_name = d['path_' + fname].rsplit('.', 1)
func = getattr(import_module(module_path), func_name)
if not callable(func):
raise Exception('"%s" from "%s" is not callable' % (module_path, func_name))
message_types[message_type][fname] = func
self._MESSAGE_TYPES = message_types
return self._MESSAGE_TYPES
......@@ -49,35 +56,13 @@ class AppSettings(object):
self._MESSAGE_TYPES_CHOICES = tuple((k, d['label']) for k, d in self.MESSAGE_TYPES.items())
return self._MESSAGE_TYPES_CHOICES
def CALLABLES_PATHS(self):
if hasattr(self, '_CALLABLES_PATHS'):
return self._CALLABLES_PATHS
callables_paths = getattr(self.settings, 'ADMIN_MESSAGE_CALLABLES_PATHS', {})
if 'get_senders' not in callables_paths:
callables_paths['get_senders'] = 'admin_message.utils.get_senders'
if 'get_default_message' not in callables_paths:
callables_paths['get_default_message'] = 'admin_message.utils.get_default_message'
callables = {}
for k, path in callables_paths:
module_path, func_name = path.rsplit('.', 1)
func = getattr(import_module(module_path), func_name)
if not callable(func):
raise Exception('"%s" from "%s" is not callable' % (module_path, func_name))
callables[k] = func
self._CALLABLES_PATHS = callables_paths
self._CALLABLES = callables
return self._CALLABLES_PATHS
def CALLABLES(self):
if not hasattr(self, '_CALLABLES'):
self._CALLABLES_PATHS
return self._CALLABLES
# Ugly? Guido recommends this himself ...
# http://mail.python.org/pipermail/python-ideas/2012-May/014969.html
app_settings = AppSettings()
app_settings.__name__ = __name__
sys.modules[__name__] = app_settings
from collections import OrderedDict # noqa
from importlib import import_module # noqa
from django.utils.translation import ugettext_lazy as _ # noqa
......@@ -19,21 +19,15 @@ class SendMessageForm(forms.Form):
class ObjetsModelMultipleChoiceField(forms.ModelMultipleChoiceField):
def label_from_instance(self, obj):
if len(app_settings.MESSAGE_TYPES_CHOICES) == 1:
message_type = app_settings.MESSAGE_TYPES_CHOICES[0][0]
get_final_recipients = app_settings.MESSAGE_TYPES[message_type]\
.get('get_final_recipients_from_misc')
final_recipients = get_final_recipients(obj)
nb = len(final_recipients)
else:
nb = None
final_recipients = self.get_final_recipients(obj)
nb = len(final_recipients)
if nb is None:
return _('%s') % obj
elif not nb:
return _('%s : no valid recipient') % obj
elif nb == 1:
return '%s : %s' % (obj._meta.verbose_name, final_recipients[0])
return '%s : %s' % (obj._meta.verbose_name, final_recipients.items()[0])
else:
return _('%(type)s « %(name)s » : %(count)d recipients') % {
'type': obj._meta.verbose_name,
......@@ -58,25 +52,25 @@ class SendMessageForm(forms.Form):
subject = forms.CharField(label=_('Subject'))
message = forms.CharField(label=_('Message'), widget=Textarea())
def __init__(self, queryset, user=None, *args, **kwargs):
if len(app_settings.MESSAGE_TYPES_CHOICES) == 1:
message_type = app_settings.MESSAGE_TYPES_CHOICES[0][0]
else:
message_type = None
senders = app_settings.CALLABLES['get_senders'](user, message_type)
def __init__(self, queryset, user, message_type, *args, **kwargs):
self.message_type = message_type
self.message_type_conf = app_settings.MESSAGE_TYPES[self.message_type]
senders = self.message_type_conf['get_senders'](user)
if 'initial' not in kwargs:
kwargs['initial'] = {}
if 'message' not in kwargs['initial']:
kwargs['initial']['message'] = app_settings.CALLABLES['get_default_message'](user)
kwargs['initial']['message'] = self.message_type_conf['get_default_message'](user)
if 'test_email' not in kwargs['initial'] and user.email in senders:
kwargs['initial']['test_email'] = user.email
if queryset and 'objects' not in kwargs['initial']:
kwargs['initial']['objects'] = [obj for obj in queryset.all()
if obj.get_final_recipients_count(message_type)]
kwargs['initial']['objects'] = [obj for obj in queryset.all()]
super(SendMessageForm, self).__init__(*args, **kwargs)
if queryset:
self.fields['objects'].get_final_recipients = self.message_type_conf\
.get('get_final_recipients')
self.fields['objects'].queryset = queryset
self.fields['sender'].choices = [(value, '%s <%s>' % (label, value))
for value, label in senders.items()]
......@@ -84,10 +78,9 @@ class SendMessageForm(forms.Form):
def clean(self):
super(SendMessageForm, self).clean()
objects = self.cleaned_data['objects']
message_type = self.cleaned_data['message_type']
get_final_recipients_from_misc = app_settings.CALLABLES['get_final_recipients_from_misc']
get_final_recipients = self.message_type_conf['get_final_recipients']
for obj in objects:
recipients = get_final_recipients_from_misc(obj, message_type)
recipients = get_final_recipients(obj)
if not recipients:
self.add_error('objects', _('Recipient "%s" has not any valid recipient' % obj))
return self.cleaned_data
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Message',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('creation_date', models.DateTimeField(auto_now_add=True, verbose_name='date de cr\xe9ation')),
('modification_date', models.DateTimeField(auto_now=True, verbose_name='modification date')),
('post_date', models.DateTimeField(null=True, verbose_name='posting date', blank=True)),
('test', models.BooleanField(default=False, verbose_name='is test')),
('message_type', models.CharField(max_length=14, verbose_name='is test')),
('sender', models.EmailField(max_length=254, verbose_name='exp\xe9diteur')),
('recipients', models.TextField(verbose_name='recipients')),
('subject', models.CharField(max_length=200, verbose_name='objet')),
('message', models.TextField(null=True, verbose_name='message', blank=True)),
('sent_txt', models.TextField(null=True, verbose_name='TXT content sent', blank=True)),
('sent_html', models.TextField(null=True, verbose_name='HTML content sent', blank=True)),
('author', models.ForeignKey(related_name='admin_message_message_related', verbose_name='auteur', to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ['-post_date'],
'abstract': False,
'verbose_name': 'message',
'verbose_name_plural': 'messages',
},
),
]
......@@ -33,7 +33,7 @@ class MessageBase(models.Model):
message_type = models.CharField(
max_length=14,
verbose_name=_('is test'),
default=app_settings)
null=False, blank=False)
sender = models.EmailField(
verbose_name=_('sender'))
recipients = models.TextField(
......@@ -65,17 +65,3 @@ class MessageBase(models.Model):
###################################################################################################
class Message(MessageBase):
pass
###################################################################################################
###################################################################################################
class RecipientMixin(object):
def get_final_recipients(self, force=False):
return []
def get_final_recipients_count(self, message_type=None, force=False):
"""Returns the number of recipients"""
if force or not hasattr(self, '_final_recipients_count'):
self._final_recipients_count = len(self.get_final_recipients(message_type, force))
return self._final_recipients_count
......@@ -5,6 +5,6 @@
<title>{{ subject }}</title>
</head>
<body>
<div>{{ message }}</div>
<div>{{ message|safe }}</div>
</body>
</html>
{% autoescape off %}{{ message }}{% endautoescape %}
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from collections import OrderedDict
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from admin_message.models import RecipientMixin
def get_senders(user, **kwargs):
"""
Returns an OrderedDict of available identities to use as "sender".
Email must be used as key and display name as value
"""
senders = OrderedDict()
if user:
senders[user.email] = "%s %s" % (user.first_name, user.last_name)
return senders
def get_default_message(user, **kwargs):
return ''
def get_final_recipients_from_misc(obj):
user_model = get_user_model()
if isinstance(obj, user_model):
email_field = (user_model.get_email_field_name()
if hasattr(user_model, 'get_email_field_name')
else 'email')
email = getattr(obj, email_field)
if not email:
return []
if hasattr(obj, 'get_full_name'):
full_name = RecipientMixin.clean_recipient_name(obj.get_full_name())
if full_name:
return ['"%s" <%s>' % (full_name, email)]
return [email]
elif isinstance(obj, Group):
recipients = []
user_model = obj.user_set.model
email_field = (user_model.get_email_field_name()
if hasattr(user_model, 'get_email_field_name')
else 'email')
for user in obj.user_set.filter(active=True):
email = getattr(user, email_field)
if not email:
continue
full_name = (RecipientMixin.clean_recipient_name(obj.get_full_name())
if hasattr(obj, 'get_full_name') else '')
recipients.append('"%s" <%s>' % (full_name, email) if full_name else email)
return recipients
......@@ -30,6 +30,7 @@ setup(
classifiers=CLASSIFIERS,
install_requires=[
'Django>=1.8,<1.12',
'html2text',
],
packages=find_packages(exclude=["project", "project.*"]),
include_package_data=True,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment