Commit 7015a56e authored by Dylann « le Ratel »'s avatar Dylann « le Ratel »

Initial commit

parents
.sass-cache
*.pyc
*.egg-info
* Webu (webu.coop) https://www.webu.coop
* Dylann CORDEL <d.cordel@webu.coop> https://github.com/DylannCordel
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.core import mail
def send_messages(sender, subject, recipients, txt, html=None):
connection = mail.get_connection()
connection.open()
for recipient in recipients:
email = mail.EmailMultiAlternatives(
subject,
txt,
sender,
[recipient],
connection=connection,
)
if html:
# Ajout de la version HTML en plus si supporté par le destinataire
email.attach_alternative(html, "text/html")
email.send()
connection.close()
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from datetime import datetime
import html2text
from django.contrib import admin
from django.contrib import messages
from django.template import Context
from django.template.base import TemplateDoesNotExist
from django.template.loader import get_template
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
###################################################################################################
###################################################################################################
class SendMessageAdminMixin(object):
"""
Fourni une action pour Envoyer un message en affichant un formulaire
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')
return actions
def get_sendmessage_form(self, request, queryset):
data = request.POST if 'send_message' in request.POST else None
form = SendMessageForm(queryset, request.user, data)
return form
def send_message(self, request, queryset):
opts = self.model._meta
app_label = opts.app_label
# The user canceled the form
if request.method == 'POST' and 'cancel' in request.POST:
return None
# The user validated the form
if request.method == 'POST' and 'send_message' in request.POST:
form = self.get_sendmessage_form(request, queryset)
if form.is_valid():
sender = form.cleaned_data['sender']
message = form.cleaned_data['message']
objects = form.cleaned_data['objects']
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)
else:
self._send_message(request, objects,
form.cleaned_data['subject'],
sender=sender, message=message)
# Return None to display the change list page again.
return None
else:
form = self.get_sendmessage_form(request, queryset)
if len(queryset) == 1:
objects_name = opts.verbose_name
else:
objects_name = opts.verbose_name_plural
title = _('Send a message')
context = {
'form': form,
'title': title,
'objects_name': objects_name,
'queryset': queryset,
'opts': opts,
'app_label': app_label,
'action_checkbox_name': admin.helpers.ACTION_CHECKBOX_NAME,
'action': 'send_message',
}
# Display the confirmation page
return TemplateResponse(
request,
'srn/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={}):
if message:
h2t = html2text.HTML2Text()
# Ignore converting links from HTML
h2t.ignore_links = True
txt_message = h2t.handle(message)
else:
txt_message = ''
context = Context(context)
context['message'] = txt_message
txt = get_template(template + '.txt').render(context)
try:
context['message'] = message
html = get_template(template + '.html').render(context)
except TemplateDoesNotExist:
html = None
return (txt, html)
def _send_message(
self, request, objects, subject, message_type, test_recipient=None,
sender=None, message=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})
recipients = []
for obj in objects:
for email in obj.get_final_recipients():
recipient = test_recipient if test_recipient else email
if not recipient:
continue
if recipient 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)
send_messages = app_settings.MESSAGE_TYPES[message_type]['callable']
send_messages(sender, subject, recipients, txt, html)
if test_recipient:
messages.info(
request,
_('A test message has been sent to "{recipient}"').format(recipient=test_recipient)
)
else:
messages.success(
request,
_('Message sent to {number} recipient(s)').format(number=len(recipients))
)
msg = Message(
subject=subject,
post_date=datetime.now(),
txt=txt,
html=html,
author=request.user,
sender=sender,
recipients=', '.join(recipients),
test=bool(test_recipient),
message_type=message_type,
)
msg.save()
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from importlib import import_module
import sys
from django.utils.translation import ugettext_lazy as _
class AppSettings(object):
def __init__(self):
from django.conf import settings
self.settings = settings
@property
def MESSAGE_TYPES(self):
if hasattr(self, '_MESSAGE_TYPES'):
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'}
})
for message_type, d in message_types.items():
module_path, func_name = d['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))
message_types[message_type]['callable'] = func
self._MESSAGE_TYPES = message_types
return self._MESSAGE_TYPES
@property
def MESSAGE_TYPES_CHOICES(self):
if hasattr(self, '_MESSAGE_TYPES_CHOICES'):
return self._MESSAGE_TYPES_CHOICES
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
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django import forms
from django.db.models.query import EmptyQuerySet
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
###################################################################################################
###################################################################################################
class SendMessageForm(forms.Form):
class Media:
js = ('cms/js/libs/jquery.min.js',)
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)
else:
final_recipients = obj.get_final_recipients()
nb = len(final_recipients)
if not nb:
return '%s : pas d\'email valide' % (obj, )
elif nb == 1:
return '%s : %s' % (obj._meta.verbose_name, final_recipients[0])
else:
return _('%(type)s « %(name)s » : %(count)d emails') % {
'type': obj._meta.verbose_name,
'name': obj,
'count': nb
}
objects = ObjetsModelMultipleChoiceField(
label="Destinataires",
widget=forms.CheckboxSelectMultiple,
queryset=EmptyQuerySet,
help_text=_("Si vous choisissez de tester cet envoi, l'unique "
"destinataire sera vous-même.")
)
sender = forms.ChoiceField(
label=_('sender'),
choices=[('', _('default'))],
required=False
)
subject = forms.CharField(label=_('Subject'))
message = forms.CharField(label=_('Message'), widget=Textarea())
def __init__(self, queryset, user=None, *args, **kwargs):
senders = app_settings.CALLABLES['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)
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()]
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):
objects = self.cleaned_data['objects']
errors = []
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
# -*- coding: utf-8 -*-
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`'))
###################################################################################################
###################################################################################################
@python_2_unicode_compatible
class MessageBase(models.Model):
creation_date = models.DateTimeField(
verbose_name=_('creation date'),
auto_now_add=True)
modification_date = models.DateTimeField(
verbose_name=_('modification date'),
auto_now=True)
post_date = models.DateTimeField(
verbose_name=_('posting date'),
blank=True, null=True)
author = models.ForeignKey(
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)
sender = models.EmailField(
verbose_name=_('sender'))
recipients = models.TextField(
verbose_name=_('recipients'))
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'),
blank=True, null=True)
class Meta:
abstract = True
ordering = ['-post_date']
verbose_name = _('message')
verbose_name_plural = _('messages')
def __str__(self):
return self.subject if self.subject else _('Pending message')
###################################################################################################
###################################################################################################
class Message(MessageBase):
pass
###################################################################################################
###################################################################################################
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
def get_final_recipients_count(self, 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))
return self._final_recipients_count
###################################################################################################
###################################################################################################
class UniqueRecipientMixin(RecipientMixin):
pass
###################################################################################################
###################################################################################################
class MultipleRecipientMixin(RecipientMixin):
pass
{% extends "admin/base_site.html" %}
{% load i18n %}
{% load url from future %}
{% load admin_urls %}
{% load static %}
{% block extrahead %}
{{ block.super }}
<link rel="stylesheet" type="text/css" href="{% static "admin/css/forms.css" %}" />
{{ form.media }}
{% endblock %}
{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
&rsaquo; <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ app_label|capfirst }}</a>
&rsaquo; <a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst|escape }}</a>
&rsaquo; {% trans 'Envoyer un message' %}
</div>
{% endblock %}
{% block content %}
<div class="content-main">
<form class="send_by_email" action="{% if next %}?next={{ next }}{% endif %}" method="post">{% csrf_token %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
<div>
<fieldset class="module aligned ">
{% for field in form.visible_fields %}
<div class="form-row field-{{field.name}}">
<div{% if not line.fields|length_is:'1' %} class="field-box{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% if not field.is_readonly and field.errors %} errors{% endif %}"{% elif field.is_checkbox %} class="checkbox-row"{% endif %}>
{% if not line.fields|length_is:'1' and not field.is_readonly %}{{ field.errors }}{% endif %}
{% if field.is_checkbox %}
{{ field.field }}{{ field.label_tag }}
{% else %}
{{ field.label_tag }}
{% if field.is_readonly %}
<p>{{ field.contents|linebreaksbr }}</p>
{% else %}
{{ field }}
{% endif %}
{% endif %}
{% if field.field.help_text %}
<p class="help">{{ field.field.help_text|safe }}</p>
{% endif %}
</div>
</div>
{% endfor %}
</fieldset>
<div class="buttons submit-row">
{% for obj in queryset %}
<input type="hidden" name="{{ action_checkbox_name }}" value="{{ obj.pk }}" />
{% endfor %}
<input type="hidden" name="action" value="{{ action }}" />
<input type="hidden" name="send_message" value="yes" />
<input type="hidden" name="post" value="yes" />
<input type="submit" class="deletelink" name="cancel" value="{% trans "Cancel" %}" />
<input type="submit" name="test" value="{% trans "Test" %}" />
<input type="submit" class="default" value="{% trans "Send" %}" />
</div>
</div>
</form>
</div>
{% endblock %}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>{{ subject }}</title>
</head>
<body>
<div>{{ message }}</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
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 = (obj.get_email_field_name() if hasattr(obj, '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('"', '')
if full_name:
return ['"%s" <%s>' % (full_name, email)]
return [email]
elif isinstance(obj, Group):
TODO !
from setuptools import setup, find_packages
import os
import profileallauth
CLASSIFIERS = [
'Development Status :: 5 - Production/Stable',
'Environment :: Web Environment',
'Framework :: Django',
'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
'Topic :: Software Development',
'Topic :: Software Development :: Libraries :: Application Frameworks',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3.3',
]
setup(
author='WebU',
author_email='contact@webu.coop',
name='django-admin-message',
version=admin_message.__version__,
description='Send messages to your users via the admin',
long_description=open(os.path.join(os.path.dirname(__file__), 'README')).read(),
url='https://dev.webu.coop/shared/django-admin-message',
license='BSD License',
platforms=['OS Independent'],
classifiers=CLASSIFIERS,
install_requires=[
'Django>=1.8,<1.12',
],
packages=find_packages(exclude=["project", "project.*"]),
include_package_data=True,
zip_safe=False,
)
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