Add email constructor from message
This commit is contained in:
@@ -1,7 +1,5 @@
|
||||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
from email.header import Header
|
||||
from email.utils import formataddr, getaddresses
|
||||
|
||||
from trytond.config import config
|
||||
from trytond.model import fields
|
||||
@@ -9,18 +7,11 @@ from trytond.pool import Pool, PoolMeta
|
||||
from trytond.pyson import Bool, Eval
|
||||
from trytond.report import get_email
|
||||
from trytond.sendmail import SMTPDataManager, send_message_transactional
|
||||
from trytond.tools.email_ import convert_ascii_email, set_from_header
|
||||
from trytond.tools.email_ import format_address, has_rcpt, set_from_header
|
||||
from trytond.transaction import Transaction
|
||||
from trytond.wizard import StateTransition
|
||||
|
||||
|
||||
def _formataddr(pair):
|
||||
name, address = pair
|
||||
if name:
|
||||
name = str(Header(name, 'utf-8'))
|
||||
return formataddr((name, convert_ascii_email(address)))
|
||||
|
||||
|
||||
class Configuration(metaclass=PoolMeta):
|
||||
__name__ = 'account.configuration'
|
||||
|
||||
@@ -126,11 +117,11 @@ class Dunning(metaclass=PoolMeta):
|
||||
'email', usage=self.level.email_contact_mechanism)
|
||||
if contact and contact.email:
|
||||
name = contact.name or self.party.rec_name
|
||||
to.append(_formataddr((name, contact.email)))
|
||||
to.append(format_address(contact.email, name))
|
||||
elif account_config.dunning_email_fallback:
|
||||
user = account_config.get_multivalue(
|
||||
'dunning_email_fallback', company=self.company.id)
|
||||
to.append(_formataddr((self.party.rec_name, user.email)))
|
||||
to.append(format_address(user.email, self.party.rec_name))
|
||||
cc = []
|
||||
bcc = []
|
||||
languages = set()
|
||||
@@ -142,20 +133,11 @@ class Dunning(metaclass=PoolMeta):
|
||||
], limit=1)
|
||||
languages.add(lang)
|
||||
|
||||
msg, title = self._email(from_, to, cc, bcc, languages)
|
||||
addr_fields = filter(None, (msg['To'], msg['Cc'], msg['Bcc']))
|
||||
to_addrs = [e for _, e in getaddresses(addr_fields)]
|
||||
if to_addrs:
|
||||
msg = self._email(from_, to, cc, bcc, languages)
|
||||
if has_rcpt(msg):
|
||||
send_message_transactional(msg, datamanager=datamanager)
|
||||
return Email(
|
||||
recipients=msg['To'],
|
||||
recipients_secondary=msg['Cc'],
|
||||
recipients_hidden=msg['Bcc'],
|
||||
addresses=[{'address': a} for a in to_addrs],
|
||||
subject=title,
|
||||
resource=self,
|
||||
dunning_level=self.level,
|
||||
)
|
||||
return Email.from_message(
|
||||
msg, resource=self, dunning_level=self.level)
|
||||
|
||||
def _email(self, sender, to, cc, bcc, languages):
|
||||
# TODO order languages to get default as last one for title
|
||||
@@ -167,9 +149,9 @@ class Dunning(metaclass=PoolMeta):
|
||||
if dunning.level.email_from:
|
||||
from_ = dunning.level.email_from
|
||||
set_from_header(msg, sender, from_)
|
||||
msg['To'] = ', '.join(to)
|
||||
msg['Cc'] = ', '.join(cc)
|
||||
msg['Bcc'] = ', '.join(bcc)
|
||||
msg['To'] = to
|
||||
msg['Cc'] = cc
|
||||
msg['Bcc'] = bcc
|
||||
msg['Subject'] = title
|
||||
msg['Auto-Submitted'] = 'auto-generated'
|
||||
return msg, title
|
||||
return msg
|
||||
|
||||
@@ -389,7 +389,7 @@ class Payment(StripeCustomerMethodMixin, CheckoutMixin, metaclass=PoolMeta):
|
||||
msg, title = get_email(
|
||||
'account.payment.stripe.email_checkout', self, languages)
|
||||
set_from_header(msg, from_cfg, from_ or from_cfg)
|
||||
msg['To'] = ','.join(emails)
|
||||
msg['To'] = emails
|
||||
msg['Subject'] = title
|
||||
send_message_transactional(msg)
|
||||
|
||||
|
||||
@@ -5,9 +5,7 @@ import logging
|
||||
import time
|
||||
import uuid
|
||||
from collections import defaultdict
|
||||
from email.headerregistry import Address
|
||||
from email.message import EmailMessage
|
||||
from email.utils import getaddresses
|
||||
from functools import partial
|
||||
from urllib.parse import (
|
||||
parse_qsl, quote, urlencode, urljoin, urlsplit, urlunsplit)
|
||||
@@ -39,7 +37,7 @@ from trytond.pyson import Eval, If, PYSONDecoder, TimeDelta
|
||||
from trytond.report import Report
|
||||
from trytond.sendmail import SMTPDataManager, send_message_transactional
|
||||
from trytond.tools import grouped_slice, pairwise_longest, reduce_ids
|
||||
from trytond.tools.email_ import convert_ascii_email, set_from_header
|
||||
from trytond.tools.email_ import format_address, has_rcpt, set_from_header
|
||||
from trytond.transaction import Transaction
|
||||
from trytond.url import http_host
|
||||
from trytond.wsgi import Base64Converter
|
||||
@@ -652,9 +650,8 @@ class Activity(
|
||||
party = record.marketing_party
|
||||
contact = party.contact_mechanism_get('email')
|
||||
if contact and contact.email:
|
||||
return Address(
|
||||
contact.name or party.rec_name,
|
||||
addr_spec=contact.email)
|
||||
return format_address(
|
||||
contact.email, contact.name or party.rec_name)
|
||||
|
||||
def execute_send_email(
|
||||
self, record_activity, smtpd_datamanager=None, **kwargs):
|
||||
@@ -668,8 +665,6 @@ class Activity(
|
||||
translated = self.__class__(self.id)
|
||||
|
||||
to = self._email_recipient(record.record)
|
||||
if not to:
|
||||
return
|
||||
|
||||
def unsubscribe(redirect):
|
||||
parts = urlsplit(urljoin(
|
||||
@@ -727,35 +722,14 @@ class Activity(
|
||||
.filter(convert_href)
|
||||
.render())
|
||||
|
||||
from_ = (config.get('marketing', 'email_from')
|
||||
or config.get('email', 'from'))
|
||||
msg = EmailMessage()
|
||||
set_from_header(msg, from_, translated.email_from or from_)
|
||||
msg['To'] = to
|
||||
msg['Subject'] = title
|
||||
if html2text:
|
||||
converter = html2text.HTML2Text()
|
||||
content_text = converter.handle(content)
|
||||
msg.add_alternative(content_text, subtype='plain')
|
||||
if msg.is_multipart():
|
||||
msg.add_alternative(content, subtype='html')
|
||||
else:
|
||||
msg.set_content(content, subtype='html')
|
||||
msg = self._email(translated.email_from, to, title, content)
|
||||
|
||||
to_addrs = [convert_ascii_email(a) for _, a in getaddresses([str(to)])]
|
||||
if to_addrs:
|
||||
if has_rcpt(msg):
|
||||
send_message_transactional(msg, datamanager=smtpd_datamanager)
|
||||
|
||||
email = Email(
|
||||
recipients=msg['To'],
|
||||
recipients_secondary=msg['Cc'],
|
||||
recipients_hidden=msg['Bcc'],
|
||||
addresses=[{'address': a} for a in to_addrs],
|
||||
subject=title,
|
||||
resource=record.record,
|
||||
email = Email.from_message(
|
||||
msg, resource=record.record,
|
||||
marketing_automation_activity=self,
|
||||
marketing_automation_record=record,
|
||||
)
|
||||
marketing_automation_record=record)
|
||||
email.save()
|
||||
|
||||
def email_context(self, record):
|
||||
@@ -769,6 +743,23 @@ class Activity(
|
||||
'datetime': datetime,
|
||||
}
|
||||
|
||||
def _email(self, sender, to, subject, content):
|
||||
msg = EmailMessage()
|
||||
from_ = (config.get('marketing', 'email_from')
|
||||
or config.get('email', 'from'))
|
||||
set_from_header(msg, from_, sender or from_)
|
||||
msg['To'] = to
|
||||
msg['Subject'] = subject
|
||||
if html2text:
|
||||
converter = html2text.HTML2Text()
|
||||
content_text = converter.handle(content)
|
||||
msg.add_alternative(content_text, subtype='plain')
|
||||
if msg.is_multipart():
|
||||
msg.add_alternative(content, subtype='html')
|
||||
else:
|
||||
msg.set_content(content, subtype='html')
|
||||
return msg
|
||||
|
||||
|
||||
class Record(ModelSQL, ModelView):
|
||||
"Marketing Record"
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
import mimetypes
|
||||
from email.headerregistry import Address
|
||||
from email.utils import getaddresses
|
||||
|
||||
from genshi.template import TextTemplate
|
||||
@@ -13,7 +12,7 @@ from trytond.pool import Pool
|
||||
from trytond.pyson import Eval, TimeDelta
|
||||
from trytond.report import get_email
|
||||
from trytond.sendmail import SMTPDataManager, send_message_transactional
|
||||
from trytond.tools.email_ import set_from_header
|
||||
from trytond.tools.email_ import format_address, has_rcpt, set_from_header
|
||||
from trytond.transaction import Transaction
|
||||
|
||||
from .exceptions import TemplateError
|
||||
@@ -157,7 +156,10 @@ class Email(ModelSQL, ModelView):
|
||||
pool = Pool()
|
||||
EmailTemplate = pool.get('ir.email.template')
|
||||
with Transaction().set_context(usage=self.contact_mechanism):
|
||||
return EmailTemplate.get_addresses(value)
|
||||
# reformat addresses with encoding
|
||||
return [
|
||||
format_address(e, n)
|
||||
for n, e in getaddresses(EmailTemplate.get_addresses(value))]
|
||||
|
||||
def _get_languages(self, value):
|
||||
pool = Pool()
|
||||
@@ -199,12 +201,12 @@ class Email(ModelSQL, ModelView):
|
||||
filename=('utf-8', '', name))
|
||||
|
||||
set_from_header(msg, sender, from_)
|
||||
msg['To'] = [Address(n, addr_spec=a) for n, a in getaddresses(to)]
|
||||
msg['Cc'] = [Address(n, addr_spec=a) for n, a in getaddresses(cc)]
|
||||
msg['Bcc'] = [Address(n, addr_spec=a) for n, a in getaddresses(bcc)]
|
||||
msg['To'] = to
|
||||
msg['Cc'] = cc
|
||||
msg['Bcc'] = bcc
|
||||
msg['Subject'] = title
|
||||
msg['Auto-Submitted'] = 'auto-generated'
|
||||
return msg, title
|
||||
return msg
|
||||
|
||||
@classmethod
|
||||
def trigger(cls, records, trigger):
|
||||
@@ -243,18 +245,11 @@ class Email(ModelSQL, ModelView):
|
||||
cc, cc_languages = self._get_cc(record)
|
||||
bcc, bcc_languages = self._get_bcc(record)
|
||||
languages = to_languages | cc_languages | bcc_languages
|
||||
msg, title = self.get_email(record, from_, to, cc, bcc, languages)
|
||||
to_addrs = [e for _, e in getaddresses(
|
||||
filter(None, (msg['To'], msg['Cc'], msg['Bcc'])))]
|
||||
if to_addrs:
|
||||
msg = self.get_email(record, from_, to, cc, bcc, languages)
|
||||
if has_rcpt(msg):
|
||||
send_message_transactional(msg, datamanager=datamanager)
|
||||
emails.append(Email(
|
||||
recipients=msg['To'],
|
||||
recipients_secondary=msg['Cc'],
|
||||
recipients_hidden=msg['Bcc'],
|
||||
addresses=[{'address': a} for a in to_addrs],
|
||||
subject=title,
|
||||
resource=record,
|
||||
emails.append(Email.from_message(
|
||||
msg, resource=record,
|
||||
notification_email=trigger.notification_email,
|
||||
notification_trigger=trigger))
|
||||
Email.save(emails)
|
||||
|
||||
@@ -237,7 +237,7 @@ class NotificationEmailTestCase(CompanyTestMixin, ModuleTestCase):
|
||||
|
||||
user, = User.create([{'name': "Michael Scott", 'login': "msc"}])
|
||||
|
||||
msg, title = notification_email.get_email(
|
||||
msg = notification_email.get_email(
|
||||
user, FROM, ['Administrator <user@example.com>'], [], [], [en])
|
||||
|
||||
self.assertEqual(msg['From'], FROM)
|
||||
@@ -275,7 +275,7 @@ class NotificationEmailTestCase(CompanyTestMixin, ModuleTestCase):
|
||||
|
||||
user, = User.create([{'name': "Michael Scott", 'login': "msc"}])
|
||||
|
||||
msg, title = notification_email.get_email(
|
||||
msg = notification_email.get_email(
|
||||
user, FROM, ['Administrator <user@example.com>'], [], [], [en])
|
||||
|
||||
self.assertEqual(msg['Subject'], 'Notification for Michael Scott')
|
||||
@@ -311,7 +311,7 @@ class NotificationEmailTestCase(CompanyTestMixin, ModuleTestCase):
|
||||
'language': es.id,
|
||||
}])
|
||||
|
||||
msg, title = notification_email.get_email(
|
||||
msg = notification_email.get_email(
|
||||
user, FROM, ['Administrator <user@example.com>'], [], [], [es])
|
||||
|
||||
self.assertEqual(msg['Subject'], 'Notificación para Michael Scott')
|
||||
|
||||
@@ -8,6 +8,14 @@ Email
|
||||
|
||||
Fill email headers to appear at best from the address.
|
||||
|
||||
.. function:: has_rcpt(message)
|
||||
|
||||
Return if the :py:class:`~email.message.Message` has any recipient.
|
||||
|
||||
.. function:: format_address(email[, name])
|
||||
|
||||
Return a string suitable for an RFC 2822 From, To or Cc header.
|
||||
|
||||
.. function:: validate_email(email)
|
||||
|
||||
Validate the email address.
|
||||
|
||||
@@ -4,7 +4,7 @@ import heapq
|
||||
import mimetypes
|
||||
import re
|
||||
from email.message import EmailMessage
|
||||
from email.utils import formataddr, getaddresses
|
||||
from email.utils import getaddresses
|
||||
|
||||
try:
|
||||
import html2text
|
||||
@@ -22,7 +22,8 @@ from trytond.report import Report
|
||||
from trytond.rpc import RPC
|
||||
from trytond.sendmail import send_message_transactional
|
||||
from trytond.tools import escape_wildcard
|
||||
from trytond.tools.email_ import convert_ascii_email, set_from_header
|
||||
from trytond.tools.email_ import (
|
||||
convert_ascii_email, format_address, set_from_header)
|
||||
from trytond.tools.string_ import StringMatcher
|
||||
from trytond.transaction import Transaction
|
||||
|
||||
@@ -43,11 +44,6 @@ class EmailTemplateError(ValidationError):
|
||||
pass
|
||||
|
||||
|
||||
def _get_emails(value):
|
||||
"Return list of email from the comma separated list"
|
||||
return [e for n, e in getaddresses([value]) if e]
|
||||
|
||||
|
||||
def _formataddr(pair):
|
||||
"Format address without encoding"
|
||||
name, address = pair
|
||||
@@ -158,30 +154,14 @@ class Email(ResourceAccessMixin, ModelSQL, ModelView):
|
||||
filename=('utf-8', '', name))
|
||||
from_ = config.get('email', 'from')
|
||||
set_from_header(msg, from_, user.email or from_)
|
||||
msg['To'] = ', '.join(
|
||||
formataddr((n, convert_ascii_email(a)))
|
||||
for n, a in getaddresses([to]))
|
||||
msg['Cc'] = ', '.join(
|
||||
formataddr((n, convert_ascii_email(a)))
|
||||
for n, a in getaddresses([cc]))
|
||||
msg['Bcc'] = ', '.join(
|
||||
formataddr((n, convert_ascii_email(a)))
|
||||
for n, a in getaddresses([bcc]))
|
||||
msg['To'] = [format_address(a, n) for n, a in getaddresses([to])]
|
||||
msg['Cc'] = [format_address(a, n) for n, a in getaddresses([cc])]
|
||||
msg['Bcc'] = [format_address(a, n) for n, a in getaddresses([bcc])]
|
||||
msg['Subject'] = subject
|
||||
|
||||
to_addrs = list(filter(None, map(
|
||||
str.strip,
|
||||
_get_emails(to) + _get_emails(cc) + _get_emails(bcc))))
|
||||
send_message_transactional(msg, strict=True)
|
||||
|
||||
email = cls(
|
||||
recipients=msg['To'],
|
||||
recipients_secondary=msg['Cc'],
|
||||
recipients_hidden=msg['Bcc'],
|
||||
addresses=[{'address': a} for a in to_addrs],
|
||||
subject=subject,
|
||||
body=body,
|
||||
resource=record)
|
||||
email = cls.from_message(msg, body=body, resource=record)
|
||||
email.save()
|
||||
if files:
|
||||
attachments_ = []
|
||||
@@ -236,6 +216,18 @@ class Email(ResourceAccessMixin, ModelSQL, ModelView):
|
||||
], order=[]):
|
||||
yield user.name, user.email
|
||||
|
||||
@classmethod
|
||||
def from_message(cls, msg, **values):
|
||||
to_addrs = [e for _, e in getaddresses(
|
||||
filter(None, (msg['To'], msg['Cc'], msg['Bcc'])))]
|
||||
return cls(
|
||||
recipients=msg['To'],
|
||||
recipients_secondary=msg['Cc'],
|
||||
recipients_hidden=msg['Bcc'],
|
||||
addresses=[{'address': a} for a in to_addrs],
|
||||
subject=msg['Subject'],
|
||||
**values)
|
||||
|
||||
|
||||
class EmailAddress(ModelSQL):
|
||||
"Email Address"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
from email.utils import parseaddr
|
||||
from email.header import Header
|
||||
from email.utils import formataddr, parseaddr
|
||||
|
||||
from trytond.pool import Pool
|
||||
|
||||
@@ -29,6 +30,10 @@ def set_from_header(message, sender, from_):
|
||||
message['From'] = from_
|
||||
|
||||
|
||||
def has_rcpt(msg):
|
||||
return any((msg['To'], msg['Cc'], msg['Bcc']))
|
||||
|
||||
|
||||
try:
|
||||
from dns.exception import DNSException
|
||||
from email_validator import EmailNotValidError, caching_resolver
|
||||
@@ -80,3 +85,9 @@ except ImportError:
|
||||
|
||||
class EmailNotValidError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def format_address(email, name=None):
|
||||
if name:
|
||||
name = str(Header(name, 'utf-8'))
|
||||
return formataddr((name, convert_ascii_email(email)))
|
||||
|
||||
Reference in New Issue
Block a user