Merge pull request #57 from lavr/multiple-repicipients-update
Send to multiple repicipients
This commit is contained in:
commit
ab496996a8
|
@ -0,0 +1,36 @@
|
|||
# encoding: utf-8
|
||||
from __future__ import unicode_literals
|
||||
|
||||
__all__ = ['InMemoryBackend', ]
|
||||
|
||||
import logging
|
||||
|
||||
|
||||
class InMemoryBackend(object):
|
||||
|
||||
"""
|
||||
InMemoryBackend store message in memory for testing purposes.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.kwargs = kwargs
|
||||
self.messages = {}
|
||||
|
||||
def sendmail(self, from_addr, to_addrs, msg, **kwargs):
|
||||
|
||||
logging.debug('InMemoryBackend.sendmail(%s, %s, %r, %s)', from_addr, to_addrs, msg, kwargs)
|
||||
|
||||
if not to_addrs:
|
||||
return None
|
||||
|
||||
if not isinstance(to_addrs, (list, tuple)):
|
||||
to_addrs = [to_addrs, ]
|
||||
|
||||
for addr in to_addrs:
|
||||
data = dict(from_addr=from_addr,
|
||||
message=msg.as_string(),
|
||||
source_message=msg,
|
||||
**kwargs)
|
||||
self.messages.setdefault(addr.lower(), []).append(data)
|
||||
|
||||
return True
|
|
@ -7,7 +7,8 @@ from .compat import (string_types, is_callable, formataddr as compat_formataddr,
|
|||
from .utils import (SafeMIMEText, SafeMIMEMultipart, sanitize_address,
|
||||
parse_name_and_email, load_email_charsets,
|
||||
encode_header as encode_header_,
|
||||
renderable, format_date_header, parse_name_and_email_list)
|
||||
renderable, format_date_header, parse_name_and_email_list,
|
||||
cached_property)
|
||||
from .exc import BadHeaderError
|
||||
from .backend import ObjectFactory, SMTPBackend
|
||||
from .store import MemoryFileStore, BaseFile
|
||||
|
@ -37,7 +38,9 @@ class BaseMessage(object):
|
|||
headers=None,
|
||||
html=None,
|
||||
text=None,
|
||||
attachments=None):
|
||||
attachments=None,
|
||||
cc=None,
|
||||
bcc=None):
|
||||
|
||||
self._attachments = None
|
||||
self.charset = charset or 'utf-8' # utf-8 is standard de-facto, yeah
|
||||
|
@ -46,6 +49,8 @@ class BaseMessage(object):
|
|||
self.set_date(date)
|
||||
self.set_mail_from(mail_from)
|
||||
self.set_mail_to(mail_to)
|
||||
self.set_cc(cc)
|
||||
self.set_bcc(bcc)
|
||||
self.set_headers(headers)
|
||||
self.set_html(html=html)
|
||||
self.set_text(text=text)
|
||||
|
@ -73,6 +78,29 @@ class BaseMessage(object):
|
|||
|
||||
mail_to = property(get_mail_to, set_mail_to)
|
||||
|
||||
def set_cc(self, addr):
|
||||
self._cc = parse_name_and_email_list(addr)
|
||||
|
||||
def get_cc(self):
|
||||
return self._cc
|
||||
|
||||
cc = property(get_cc, set_cc)
|
||||
|
||||
def set_bcc(self, addr):
|
||||
self._bcc = parse_name_and_email_list(addr)
|
||||
|
||||
def get_bcc(self):
|
||||
return self._bcc
|
||||
|
||||
bcc = property(get_bcc, set_bcc)
|
||||
|
||||
def get_recipients_emails(self):
|
||||
"""
|
||||
Returns message recipient's emails for actual sending.
|
||||
:return: list of emails
|
||||
"""
|
||||
return list(set([a[1] for a in self._mail_to] + [a[1] for a in self._cc] + [a[1] for a in self._bcc]))
|
||||
|
||||
def set_headers(self, headers):
|
||||
self._headers = headers or {}
|
||||
|
||||
|
@ -224,8 +252,12 @@ class MessageBuildMixin(object):
|
|||
self.set_header(msg, 'Subject', subject)
|
||||
|
||||
self.set_header(msg, 'From', self.encode_address_header(self._mail_from), encode=False)
|
||||
self.set_header(msg, 'To', self._mail_to and ", ".join([self.encode_address_header(addr)
|
||||
for addr in self._mail_to]) or None, encode=False)
|
||||
|
||||
if self._mail_to:
|
||||
self.set_header(msg, 'To', ", ".join([self.encode_address_header(addr) for addr in self._mail_to]), encode=False)
|
||||
|
||||
if self._cc:
|
||||
self.set_header(msg, 'Cc', ", ".join([self.encode_address_header(addr) for addr in self._cc]), encode=False)
|
||||
|
||||
return msg
|
||||
|
||||
|
@ -312,12 +344,9 @@ class MessageSendMixin(object):
|
|||
smtp_pool_factory = ObjectFactory
|
||||
smtp_cls = SMTPBackend
|
||||
|
||||
@property
|
||||
@cached_property
|
||||
def smtp_pool(self):
|
||||
pool = getattr(self, '_smtp_pool', None)
|
||||
if pool is None:
|
||||
pool = self._smtp_pool = self.smtp_pool_factory(cls=self.smtp_cls)
|
||||
return pool
|
||||
return self.smtp_pool_factory(cls=self.smtp_cls)
|
||||
|
||||
def send(self,
|
||||
to=None,
|
||||
|
@ -342,17 +371,17 @@ class MessageSendMixin(object):
|
|||
raise ValueError(
|
||||
"smtp must be a dict or an object with method 'sendmail'. got %s" % type(smtp))
|
||||
|
||||
mail_to = to
|
||||
if mail_to:
|
||||
mail_to = parse_name_and_email(mail_to)
|
||||
to_addr = mail_to[1]
|
||||
to_addrs = None
|
||||
|
||||
if to:
|
||||
if set_mail_to:
|
||||
self.set_mail_to(mail_to)
|
||||
self.set_mail_to(to)
|
||||
else:
|
||||
to_addrs = [a[1] for a in parse_name_and_email_list(to)]
|
||||
|
||||
else:
|
||||
to_addr = self._mail_to[0][1]
|
||||
to_addrs = to_addrs or self.get_recipients_emails()
|
||||
|
||||
if not to_addr:
|
||||
if not to_addrs:
|
||||
raise ValueError('No to-addr')
|
||||
|
||||
if mail_from:
|
||||
|
@ -368,7 +397,7 @@ class MessageSendMixin(object):
|
|||
if not from_addr:
|
||||
raise ValueError('No "from" addr')
|
||||
|
||||
params = dict(from_addr=from_addr, to_addrs=[to_addr, ], msg=self,
|
||||
params = dict(from_addr=from_addr, to_addrs=to_addrs, msg=self,
|
||||
mail_options=smtp_mail_options, rcpt_options=smtp_rcpt_options)
|
||||
|
||||
return smtp.sendmail(**params)
|
||||
|
|
|
@ -10,6 +10,7 @@ from emails import Message
|
|||
import emails.exc
|
||||
from emails.compat import to_unicode, StringIO, is_py2, is_py34_plus
|
||||
from emails.utils import decode_header, MessageID
|
||||
from emails.backend.inmemory import InMemoryBackend
|
||||
|
||||
from .helpers import common_email_data
|
||||
|
||||
|
@ -171,12 +172,23 @@ def test_message_id():
|
|||
assert m.as_message()['Message-ID'] == 'XXX'
|
||||
|
||||
|
||||
def test_several_recipients_in_to_header():
|
||||
def test_several_recipients():
|
||||
|
||||
# Test multiple recipients in "To" header
|
||||
|
||||
params = dict(html='...', mail_from='a@b.c')
|
||||
|
||||
m = Message(mail_to=['d@e.f', 'g@h.i'], **params)
|
||||
assert m.as_message()['To'] == 'd@e.f, g@h.i'
|
||||
m = Message(mail_to=['a@x.z', 'b@x.z'], cc='c@x.z', **params)
|
||||
assert m.as_message()['To'] == 'a@x.z, b@x.z'
|
||||
assert m.as_message()['cc'] == 'c@x.z'
|
||||
|
||||
m = Message(mail_to=[('♡', 'd@e.f'), ('웃', 'g@h.i')], **params)
|
||||
assert m.as_message()['To'] == '=?utf-8?b?4pmh?= <d@e.f>, =?utf-8?b?7JuD?= <g@h.i>'
|
||||
m = Message(mail_to=[('♡', 'a@x.z'), ('웃', 'b@x.z')], **params)
|
||||
assert m.as_message()['To'] == '=?utf-8?b?4pmh?= <a@x.z>, =?utf-8?b?7JuD?= <b@x.z>'
|
||||
|
||||
# Test sending to several emails
|
||||
|
||||
backend = InMemoryBackend()
|
||||
m = Message(mail_to=[('♡', 'a@x.z'), ('웃', 'b@x.z')], cc=['c@x.z', 'b@x.z'], bcc=['c@x.z', 'd@x.z'], **params)
|
||||
m.send(smtp=backend)
|
||||
for addr in ['a@x.z', 'b@x.z', 'c@x.z', 'd@x.z']:
|
||||
assert len(backend.messages[addr]) == 1
|
||||
|
|
|
@ -41,6 +41,24 @@ def load_email_charsets():
|
|||
charset)
|
||||
|
||||
|
||||
class cached_property(object):
|
||||
"""
|
||||
A property that is only computed once per instance and then replaces itself
|
||||
with an ordinary attribute. Deleting the attribute resets the property.
|
||||
Source: https://github.com/bottlepy/bottle/commit/fa7733e075da0d790d809aa3d2f53071897e6f76
|
||||
""" # noqa
|
||||
|
||||
def __init__(self, func):
|
||||
self.__doc__ = getattr(func, '__doc__')
|
||||
self.func = func
|
||||
|
||||
def __get__(self, obj, cls):
|
||||
if obj is None:
|
||||
return self
|
||||
value = obj.__dict__[self.func.__name__] = self.func(obj)
|
||||
return value
|
||||
|
||||
|
||||
# Django's CachedDnsName:
|
||||
# Cached the hostname, but do it lazily: socket.getfqdn() can take a couple of
|
||||
# seconds, which slows down the restart of the server.
|
||||
|
|
Reference in New Issue