Commit 51984145 authored by Dylann Cordel's avatar Dylann Cordel

wip

parent 7015a56e
TODO
# Admin Message
This app allows you to send messages to recipients via the admin interface where:
* messages can be emails, private messages via postman app, or any type of message if you code
your own (very simple) binding to send these
* recipients are `User`, `Group`, or any model you want by addind some (very simple) code.
## Install
Add admin_message to `INSTALLED_APPS` in `settings.py`.
### Transform models to recipient compatible
#### `User` and `Group`:
If you only want to send messages to `User` and `Group` instances, you have nothing to do.
#### Other Models
This part is already done for User and Group but you have to code it for other Model unknown
from this app.
TODO blabla
```python
from admin_message.adapters.email import get_final_recipients_from_misc
from admin_message.adapters.email import clean_recipient_name
from some.app.models import Profile, Comittee
def my_own_get_final_recipients_from_misc(obj):
"""
Returns final recipients for email message types.
"""
if isinstance(obj, Profile):
display_name = clean_recipient_name(obj.display_name)
return ['"%s" <%s>' % (display_name, obj.user.email) if display_name else obj.user.email]
elif isinstance(obj, Comittee):
recipients = []
for member in obj.members.all():
display_name = clean_recipient_name(member.display_name)
if display_name:
recipients.append('"%s" <%s>' % (display_name, member.user.email))
else:
recipients.append(member.user.email)
return recipients
else:
# fallback on User and Group if you need thoses models
return get_final_recipients_from_misc(obj)
```
### Configure admin interfaces
Now, you can configure the related admin interfaces. In your admin.py:
```python
from django.contrib.admin import ModelAdmin
from django.contrib.auth.admin import UserAdmin as UserAdminBase, GroupAdmin as GroupAdminBase
from admin_message.admin import SendMessageAdminMixin
from some.app.models import Profile, Comittee
class UserAdmin(SendMessageAdminMixin, UserAdminBase):
pass
class GroupAdmin(SendMessageAdminMixin, GroupAdminBase):
pass
class ProfileAdmin(SendMessageAdminMixin, ModelAdmin):
model = Profile
class ComitteeAdmin(SendMessageAdminMixin, ModelAdmin):
model = Comittee
# Unregister the old UserAdmin and register the new one
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
# Unregister the old GroupAdmin and register the new one
admin.site.unregister(Group)
admin.site.register(Group, GroupAdmin)
# Register Comittee and Profile admin interfaces
admin.site.register(Profile, ProfileAdmin)
admin.site.register(Comittee, ComitteeAdmin)
```
## Add a new binding to send a new type of message
In `settings.py`, you can add a `ADMIN_MESSAGE_MESSAGE_TYPES` entry which should be an OrderedDict
of the form:
```python
ADMIN_MESSAGE_MESSAGE_TYPES = OrderedDict()
# copy paste the email conf from admin_message.conf.app_settings if you need it
ADMIN_MESSAGE_MESSAGE_TYPES['EMAIL'] = {
'path': 'admin_message.adapters.email.send_message',
'label': _('email'),
'template': 'admin_message/email'
}
# add your own conf "SOME_BINDING" (must be 14 chars len max)
ADMIN_MESSAGE_MESSAGE_TYPES['SOME_BINDING'] = {
# path to your function which will send the message
'path': 'mms_app.utils.send_message_as_mms',
# Name of your message type as displayed in the admin form
'label': _('MMS'),
# Template path without .html and .txt extensions which will be used to build your message
'template': 'mms_app/mms'
}
```
In `mms_app.utils.py`:
```python
def send_message_as_mms(sender, subject, recipients, txt, html=None):
# do some stuffs
```
In `mms_app/templates/mms.txt`:
```
{% autoescape off %}{{ message }}{% endautoescape %}
```
### `ADMIN_MESSAGE_CALLABLES_PATHS`
## Access to the `admin_message` configuration
If you want to access to the configuration of the app, you must use the module
`admin_message.conf.app_settings`. usage:
```python
from admin_message.conf import app_settings
for message_type, infos in app_settings.MESSAGE_TYPES.items():
print "%(label)s's adapter is in %(path)s and use %(template)s as template" % infos
for func_name, path in app_settings.CALLABLES_PATHS.items():
print "%s's path is %s" % (func_name, path)
```
This module will have the default settings set if needed and
add some extra conf shortcuts :
* `MESSAGE_TYPES_CHOICES`: is a shortcut to access a "choices" forms (tuple of tuple key/label)
of available message types defined in `ADMIN_MESSAGE_MESSAGE_TYPES`
# -*- coding: utf-8 -*-
__version__ = '0.0.1'
......@@ -3,6 +3,8 @@ from __future__ import unicode_literals
from django.core import mail
from admin_message.models import RecipientMixin
def send_messages(sender, subject, recipients, txt, html=None):
connection = mail.get_connection()
......@@ -20,3 +22,42 @@ def send_messages(sender, subject, recipients, txt, html=None):
email.attach_alternative(html, "text/html")
email.send()
connection.close()
def clean_recipient_name(name):
if name:
return name.replace('"', '').strip()
def get_final_recipients_from_misc(obj):
"""
Returns a list of valid email recipient:
`"display name" <email>`
or just `email` if there is not any display name available
"""
user_model = get_user_model()
recipients = []
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 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)
elif isinstance(obj, Group):
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
......@@ -13,7 +13,7 @@ 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.models import RecipientMixin
from admin_message.forms import SendMessageForm
from admin_message.conf import app_settings
......@@ -27,10 +27,10 @@ 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 __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)
......@@ -133,9 +133,10 @@ class SendMessageAdminMixin(object):
recipients = []
for obj in objects:
for email in obj.get_final_recipients():
recipient = test_recipient if test_recipient else email
if not recipient:
for recipient in obj.get_final_recipients(message_type):
if test_recipient:
recipient = test_recipient
elif not recipient:
continue
if recipient in recipients:
if not test_recipient:
......
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from collections import OrderedDict
from importlib import import_module
import sys
......@@ -19,16 +20,25 @@ class AppSettings(object):
return self._MESSAGE_TYPES
message_types = getattr(self.settings, 'ADMIN_MESSAGE_MESSAGE_TYPES', {
'EMAIL': {'path': 'admin_message.adapters.email.send_message',
'label': _('email'),
'template': 'admin_message/email.html'}
'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'}
})
if not isinstance(message_types, OrderedDict):
message_types = OrderedDict(message_types)
for message_type, d in message_types.items():
module_path, func_name = d['path'].rsplit('.', 1)
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]['callable'] = func
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
self._MESSAGE_TYPES = message_types
return self._MESSAGE_TYPES
......
......@@ -7,7 +7,6 @@ from django.forms.widgets import Textarea
from django.utils.translation import ugettext_lazy as _
from admin_message.conf import app_settings
from admin_message.models import RecipientMixin, MultipleRecipientMixin, UniqueRecipientMixin
###################################################################################################
......@@ -20,19 +19,23 @@ class SendMessageForm(forms.Form):
class ObjetsModelMultipleChoiceField(forms.ModelMultipleChoiceField):
def label_from_instance(self, obj):
if not isinstance(obj, RecipientMixin):
final_recipients = app_settings.CALLABLES['get_final_recipients_from_misc'](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:
final_recipients = obj.get_final_recipients()
nb = None
nb = len(final_recipients)
if not nb:
return '%s : pas d\'email valide' % (obj, )
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])
else:
return _('%(type)s « %(name)s » : %(count)d emails') % {
return _('%(type)s « %(name)s » : %(count)d recipients') % {
'type': obj._meta.verbose_name,
'name': obj,
'count': nb
......@@ -56,7 +59,11 @@ class SendMessageForm(forms.Form):
message = forms.CharField(label=_('Message'), widget=Textarea())
def __init__(self, queryset, user=None, *args, **kwargs):
senders = app_settings.CALLABLES['get_senders'](user)
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)
if 'initial' not in kwargs:
kwargs['initial'] = {}
if 'message' not in kwargs['initial']:
......@@ -65,25 +72,22 @@ class SendMessageForm(forms.Form):
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()]
if obj.get_final_recipients_count(message_type)]
super(SendMessageForm, self).__init__(*args, **kwargs)
if queryset:
self.fields['objects'].queryset = queryset
self.fields['sender'].choices = [
(email, "%s <%s>" % (desc, email))
for email, desc in senders.items()]
def clean_objects(self):
self.fields['sender'].choices = [(value, '%s <%s>' % (label, value))
for value, label in senders.items()]
def clean(self):
super(SendMessageForm, self).clean()
objects = self.cleaned_data['objects']
errors = []
message_type = self.cleaned_data['message_type']
get_final_recipients_from_misc = app_settings.CALLABLES['get_final_recipients_from_misc']
for obj in objects:
if not isinstance(obj, RecipientMixin):
errors.append(_('Recipient "%s" does not extends RecipientMixin' % obj))
elif not obj.get_final_recipients_count():
errors.append(_('Recipient "%s" has not any email address' % obj))
if errors:
self._errors['objects'] = self.error_class(errors)
return objects
recipients = get_final_recipients_from_misc(obj, message_type)
if not recipients:
self.add_error('objects', _('Recipient "%s" has not any valid recipient' % obj))
return self.cleaned_data
......@@ -2,20 +2,11 @@
from __future__ import unicode_literals
from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext as _
AUTH_USER_MODEL = settings.AUTH_USER_MODEL
def extends_recipient_mixin(value):
"""checks that submited value is an instance of RecipientMixin"""
if not isinstance(value, RecipientMixin):
raise ValidationError(('wrong recipient type. Must inherit from '
'`UniqueRecipientMixin` or `MultipleRecipientMixin`'))
from admin_message.conf import app_settings
###################################################################################################
......@@ -32,13 +23,17 @@ class MessageBase(models.Model):
verbose_name=_('posting date'),
blank=True, null=True)
author = models.ForeignKey(
AUTH_USER_MODEL,
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
verbose_name=_('author'),
related_name='%(app_label)s_%(class)s_related')
test = models.BooleanField(
verbose_name=_('is test'),
default=False)
message_type = models.CharField(
max_length=14,
verbose_name=_('is test'),
default=app_settings)
sender = models.EmailField(
verbose_name=_('sender'))
recipients = models.TextField(
......@@ -46,11 +41,14 @@ class MessageBase(models.Model):
subject = models.CharField(
verbose_name=_('subject'),
max_length=200)
body_txt = models.TextField(
verbose_name=_('txt content'),
blank=False, null=False)
body_html = models.TextField(
verbose_name=_('html content'),
message = models.TextField(
verbose_name=_('message'),
blank=True, null=True)
sent_txt = models.TextField(
verbose_name=_('TXT content sent'),
blank=True, null=True)
sent_html = models.TextField(
verbose_name=_('HTML content sent'),
blank=True, null=True)
class Meta:
......@@ -74,28 +72,10 @@ class Message(MessageBase):
class RecipientMixin(object):
def get_final_recipients(self, force=False):
"""
Returns a list of `"my name" <email@host>` or `email@host` from all Recipient instances
"""
raise Exception('Not implemented')
if force or not hasattr(self, '_final_recipients'):
self._final_recipients = []
return self._final_recipients
return []
def get_final_recipients_count(self, force=False):
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(force))
self._final_recipients_count = len(self.get_final_recipients(message_type, force))
return self._final_recipients_count
###################################################################################################
###################################################################################################
class UniqueRecipientMixin(RecipientMixin):
pass
###################################################################################################
###################################################################################################
class MultipleRecipientMixin(RecipientMixin):
pass
......@@ -6,6 +6,8 @@ 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):
"""
......@@ -18,21 +20,36 @@ def get_senders(user, **kwargs):
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 = (obj.get_email_field_name() if hasattr(obj, 'get_email_field_name')
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 = obj.get_full_name().strip().replace('"', '')
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):
TODO !
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
from setuptools import setup, find_packages
import os
import profileallauth
import admin_message
CLASSIFIERS = [
'Development Status :: 5 - Production/Stable',
......
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