Browse Source

add mail_bcc module

pull/93/head
Tom Palan 8 years ago
parent
commit
5a6d56233b
  1. 1
      README.md
  2. 53
      mail_bcc/README.rst
  3. 7
      mail_bcc/__init__.py
  4. 44
      mail_bcc/__openerp__.py
  5. 8
      mail_bcc/models/__init__.py
  6. 160
      mail_bcc/models/mail_mail.py
  7. 148
      mail_bcc/models/mail_template.py
  8. 62
      mail_bcc/static/description/index.html
  9. 15
      mail_bcc/views/views.xml

1
README.md

@ -15,6 +15,7 @@ addon | version | summary
[email_template_qweb](email_template_qweb/) | 9.0.1.0.0 | Use the QWeb templating mechanism for emails
[mail_as_letter](mail_as_letter/) | 9.0.1.0.0 | This module allows to download a mail message as a pdf letter.
[mail_attach_existing_attachment](mail_attach_existing_attachment/) | 9.0.1.0.0 | Adding attachment on the object by sending this one
[mail_bcc](mail_bcc/) | 9.0.1.0.0 | This module allows specify a BCC address in mail.template
[mail_optional_autofollow](mail_optional_autofollow/) | 9.0.1.0.0 | Choose if you want to automatically add new recipients as followers on mail.compose.message
[mail_tracking](mail_tracking/) | 9.0.1.0.0 | Email tracking system for all mails sent
[mail_tracking_mailgun](mail_tracking_mailgun/) | 9.0.1.0.0 | Mail tracking and Mailgun webhooks integration

53
mail_bcc/README.rst

@ -0,0 +1,53 @@
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
==============
mail_bcc
==============
This module extends the functionality of mail to support BCC addresses in mail templates.
Usage
=====
To use this module, you need to edit your mail template and enter an email address in the BCC field. Every time you use this mail template, a copy of the message is sent to this address as blind copy.
Bug Tracker
===========
Bugs are tracked on `GitHub Issues
<https://github.com/OCA/social/issues>`_. In case of trouble, please
check there if your issue has already been reported. If you spotted it first,
help us smashing it by providing a detailed and welcomed feedback.
Credits
=======
Images
------
* Odoo Community Association: `Icon <https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg>`_.
Contributors
------------
* Juan Formoso <jfv@anubia.es>
* Tom Palan <thomas@palan.at>
Maintainer
----------
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
This module is maintained by the OCA.
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
To contribute to this module, please visit https://odoo-community.org.

7
mail_bcc/__init__.py

@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
################################################################
# License, author and contributors information in: #
# __openerp__.py file at the root folder of this module. #
################################################################
from . import models

44
mail_bcc/__openerp__.py

@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# mail_bcc module for odoo v9
# Copyright (C) 2015 Anubía, soluciones en la nube,SL (http://www.anubia.es)
# @author: Juan Formoso <jfv@anubia.es>,
# @author: Tom Palan <thomas@palan.at>,
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
{
'name': 'Mail BCC',
'summary': 'Blind Carbon Copy available on mails',
'description': """
Adds a BCC field to mail templates and uses them to send a separate copy of the mail to the BCC recipient.
""",
'version': '0.1',
'license': 'AGPL-3',
'author': 'Juan Formoso <jfv@anubia.es>, Tom Palan <thomas@palan.at>',
'website': 'http://www.anubia.es',
'category': 'Mail',
'depends': [
'mail',
],
'data': [
"views/views.xml"
],
'demo': [],
'test': [],
'installable': True,
}

8
mail_bcc/models/__init__.py

@ -0,0 +1,8 @@
# -*- coding: utf-8 -*-
################################################################
# License, author and contributors information in: #
# __openerp__.py file at the root folder of this module. #
################################################################
from . import mail_mail
from . import mail_template

160
mail_bcc/models/mail_mail.py

@ -0,0 +1,160 @@
# -*- coding: utf-8 -*-
################################################################
# License, author and contributors information in: #
# __openerp__.py file at the root folder of this module. #
################################################################
import base64
import logging
from email.utils import formataddr
import psycopg2
from openerp import _, api, fields, models
from openerp import tools
from openerp.addons.base.ir.ir_mail_server import MailDeliveryException
from openerp.tools.safe_eval import safe_eval as eval
_logger = logging.getLogger(__name__)
class MailMail(models.Model):
_inherit = 'mail.mail'
email_bcc = fields.Char(string='BCC',
help='Blind carbon copy message recipients')
@api.multi
def send(self, auto_commit=False, raise_exception=False):
""" Sends the selected emails immediately, ignoring their current
state (mails that have already been sent should not be passed
unless they should actually be re-sent).
Emails successfully delivered are marked as 'sent', and those
that fail to be deliver are marked as 'exception', and the
corresponding error mail is output in the server logs.
:param bool auto_commit: whether to force a commit of the mail status
after sending each mail (meant only for scheduler processing);
should never be True during normal transactions (default: False)
:param bool raise_exception: whether to raise an exception if the
email sending process has failed
:return: True
"""
IrMailServer = self.env['ir.mail_server']
for mail in self:
try:
# TDE note: remove me when model_id field is present on mail.message - done here to avoid doing it multiple times in the sub method
if mail.model:
model = self.env['ir.model'].sudo().search([('model', '=', mail.model)])[0]
else:
model = None
if model:
mail = mail.with_context(model_name=model.name)
# load attachment binary data with a separate read(), as prefetching all
# `datas` (binary field) could bloat the browse cache, triggerring
# soft/hard mem limits with temporary data.
attachments = [(a['datas_fname'], base64.b64decode(a['datas']))
for a in mail.attachment_ids.sudo().read(['datas_fname', 'datas'])]
# specific behavior to customize the send email for notified partners
email_list = []
if mail.email_to:
email_list.append(mail.send_get_email_dict())
for partner in mail.recipient_ids:
email_list.append(mail.send_get_email_dict(partner=partner))
# headers
headers = {}
bounce_alias = self.env['ir.config_parameter'].get_param("mail.bounce.alias")
catchall_domain = self.env['ir.config_parameter'].get_param("mail.catchall.domain")
if bounce_alias and catchall_domain:
if mail.model and mail.res_id:
headers['Return-Path'] = '%s-%d-%s-%d@%s' % (bounce_alias, mail.id, mail.model, mail.res_id, catchall_domain)
else:
headers['Return-Path'] = '%s-%d@%s' % (bounce_alias, mail.id, catchall_domain)
if mail.headers:
try:
headers.update(eval(mail.headers))
except Exception:
pass
# Writing on the mail object may fail (e.g. lock on user) which
# would trigger a rollback *after* actually sending the email.
# To avoid sending twice the same email, provoke the failure earlier
mail.write({
'state': 'exception',
'failure_reason': _('Error without exception. Probably due do sending an email without computed recipients.'),
})
mail_sent = False
# build an RFC2822 email.message.Message object and send it without queuing
res = None
for email in email_list:
msg = IrMailServer.build_email(
email_from=mail.email_from,
email_to=email.get('email_to'),
subject=mail.subject,
body=email.get('body'),
body_alternative=email.get('body_alternative'),
email_cc=tools.email_split(mail.email_cc),
email_bcc=tools.email_split(mail.email_bcc),
reply_to=mail.reply_to,
attachments=attachments,
message_id=mail.message_id,
references=mail.references,
object_id=mail.res_id and ('%s-%s' % (mail.res_id, mail.model)),
subtype='html',
subtype_alternative='plain',
headers=headers)
try:
res = IrMailServer.send_email(msg, mail_server_id=mail.mail_server_id.id)
except AssertionError as error:
if error.message == IrMailServer.NO_VALID_RECIPIENT:
# No valid recipient found for this particular
# mail item -> ignore error to avoid blocking
# delivery to next recipients, if any. If this is
# the only recipient, the mail will show as failed.
_logger.info("Ignoring invalid recipients for mail.mail %s: %s",
mail.message_id, email.get('email_to'))
else:
raise
if res:
mail.write({'state': 'sent', 'message_id': res, 'failure_reason': False})
mail_sent = True
# /!\ can't use mail.state here, as mail.refresh() will cause an error
# see revid:odo@openerp.com-20120622152536-42b2s28lvdv3odyr in 6.1
if mail_sent:
_logger.info('Mail with ID %r and Message-Id %r successfully sent', mail.id, mail.message_id)
mail._postprocess_sent_message_v9(mail_sent=mail_sent)
except MemoryError:
# prevent catching transient MemoryErrors, bubble up to notify user or abort cron job
# instead of marking the mail as failed
_logger.exception(
'MemoryError while processing mail with ID %r and Msg-Id %r. Consider raising the --limit-memory-hard startup option',
mail.id, mail.message_id)
raise
except psycopg2.Error:
# If an error with the database occurs, chances are that the cursor is unusable.
# This will lead to an `psycopg2.InternalError` being raised when trying to write
# `state`, shadowing the original exception and forbid a retry on concurrent
# update. Let's bubble it.
raise
except Exception as e:
failure_reason = tools.ustr(e)
_logger.exception('failed sending mail (id: %s) due to %s', mail.id, failure_reason)
mail.write({'state': 'exception', 'failure_reason': failure_reason})
mail._postprocess_sent_message_v9(mail_sent=False)
if raise_exception:
if isinstance(e, AssertionError):
# get the args of the original error, wrap into a value and throw a MailDeliveryException
# that is an except_orm, with name and value as arguments
value = '. '.join(e.args)
raise MailDeliveryException(_("Mail Delivery Failed"), value)
raise
if auto_commit is True:
self._cr.commit()
return True

148
mail_bcc/models/mail_template.py

@ -0,0 +1,148 @@
# -*- coding: utf-8 -*-
################################################################
# License, author and contributors information in: #
# __openerp__.py file at the root folder of this module. #
################################################################
import base64
import copy
import datetime
import dateutil.relativedelta as relativedelta
import logging
import lxml
import urlparse
import openerp
from urllib import urlencode, quote as quote
from openerp import _, api, fields, models, SUPERUSER_ID
from openerp import tools
from openerp import report as odoo_report
from openerp.exceptions import UserError
_logger = logging.getLogger(__name__)
class MailTemplate(models.Model):
_inherit = 'mail.template'
email_bcc = fields.Char(string='Bcc',
help='Blind carbon copy message recipients')
@api.multi
def generate_email(self, res_ids, fields=None):
"""Generates an email from the template for given the given model based on
records given by res_ids.
:param template_id: id of the template to render.
:param res_id: id of the record to use for rendering the template (model
is taken from template definition)
:returns: a dict containing all relevant fields for creating a new
mail.mail entry, with one extra key ``attachments``, in the
format [(report_name, data)] where data is base64 encoded.
"""
self.ensure_one()
multi_mode = True
if isinstance(res_ids, (int, long)):
res_ids = [res_ids]
multi_mode = False
if fields is None:
fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to']
fields = fields + ['email_bcc']
res_ids_to_templates = self.get_email_template_batch(res_ids)
# templates: res_id -> template; template -> res_ids
templates_to_res_ids = {}
for res_id, template in res_ids_to_templates.iteritems():
templates_to_res_ids.setdefault(template, []).append(res_id)
results = dict()
for template, template_res_ids in templates_to_res_ids.iteritems():
Template = self.env['mail.template']
# generate fields value for all res_ids linked to the current template
if template.lang:
Template = Template.with_context(lang=template._context.get('lang'))
for field in fields:
Template = Template.with_context(safe=field in {'subject'})
generated_field_values = Template.render_template(
getattr(template, field), template.model, template_res_ids,
post_process=(field == 'body_html'))
for res_id, field_value in generated_field_values.iteritems():
results.setdefault(res_id, dict())[field] = field_value
# compute recipients
if any(field in fields for field in ['email_to', 'partner_to', 'email_cc']):
results = template.generate_recipients(results, template_res_ids)
# update values for all res_ids
for res_id in template_res_ids:
values = results[res_id]
# body: add user signature, sanitize
if 'body_html' in fields and template.user_signature:
signature = self.env.user.signature
if signature:
values['body_html'] = tools.append_content_to_html(values['body_html'], signature, plaintext=False)
if values.get('body_html'):
values['body'] = tools.html_sanitize(values['body_html'])
# technical settings
values.update(
mail_server_id=template.mail_server_id.id or False,
auto_delete=template.auto_delete,
model=template.model,
res_id=res_id or False,
attachment_ids=[attach.id for attach in template.attachment_ids],
)
# Add report in attachments: generate once for all template_res_ids
if template.report_template and not 'report_template_in_attachment' in self.env.context:
for res_id in template_res_ids:
attachments = []
report_name = self.render_template(template.report_name, template.model, res_id)
report = template.report_template
report_service = report.report_name
if report.report_type in ['qweb-html', 'qweb-pdf']:
result, format = self.pool['report'].get_pdf(self._cr, self._uid, [res_id], report_service, context=Template._context), 'pdf'
else:
result, format = odoo_report.render_report(self._cr, self._uid, [res_id], report_service, {'model': template.model}, Template._context)
# TODO in trunk, change return format to binary to match message_post expected format
result = base64.b64encode(result)
if not report_name:
report_name = 'report.' + report_service
ext = "." + format
if not report_name.endswith(ext):
report_name += ext
attachments.append((report_name, result))
results[res_id]['attachments'] = attachments
return multi_mode and results or results[res_ids[0]]
@api.multi
def generate_recipients(self, results, res_ids):
"""Generates the recipients of the template. Default values can ben generated
instead of the template values if requested by template or context.
Emails (email_to, email_cc) can be transformed into partners if requested
in the context. """
self.ensure_one()
if self.use_default_to or self._context.get('tpl_force_default_to'):
default_recipients = self.env['mail.thread'].message_get_default_recipients(res_model=self.model, res_ids=res_ids)
for res_id, recipients in default_recipients.iteritems():
results[res_id].pop('partner_to', None)
results[res_id].update(recipients)
for res_id, values in results.iteritems():
partner_ids = values.get('partner_ids', list())
if self._context.get('tpl_partners_only'):
mails = tools.email_split(values.pop('email_to', '')) + tools.email_split(values.pop('email_cc', '')) + tools.email_split(values.pop('email_bcc', ''))
for mail in mails:
partner_id = self.env['res.partner'].find_or_create(mail)
partner_ids.append(partner_id)
partner_to = values.pop('partner_to', '')
if partner_to:
# placeholders could generate '', 3, 2 due to some empty field values
tpl_partner_ids = [int(pid) for pid in partner_to.split(',') if pid]
partner_ids += self.env['res.partner'].sudo().browse(tpl_partner_ids).exists().ids
results[res_id]['partner_ids'] = partner_ids
return results

62
mail_bcc/static/description/index.html

@ -0,0 +1,62 @@
<section class="oe_container">
<div class="oe_row oe_spaced">
<div class="oe_span12">
<h2 class="oe_slogan">Mail BCC</h2>
<p>This module was written to add the field Blind Carbon Copy (BCC) to emails and email templates.</p>
</div>
</div>
</section>
<section class="oe_container oe_dark">
<div class="oe_row oe_spaced">
<div class="oe_span12">
<h2 class="oe_slogan">Installation</h2>
</div>
<div class="oe_span12">
<p class="oe_mt32">To install this module, you need to:
<ul>
<li>Check that you have installed the module <strong>email_template</strong>.</li>
</ul>
</p>
</div>
</div>
</section>
<section class="oe_container oe_dark">
<div class="oe_row oe_spaced">
<div class="oe_span12">
<h2 class="oe_slogan">Usage</h2>
</div>
<div class="oe_span12">
<p class="oe_mt32">To use this module, you need to:
<ul>
<li>Create a new email template from XML code and add fill in the new field <strong>bcc_email</strong>.</li>
<li>Send an email loading the above template from Python code.</li>
</ul>
</p>
</div>
</div>
</section>
<section class="oe_container oe_dark">
<div class="oe_row">
<div class="oe_span12">
<h2 class="oe_slogan">Credits</h2>
</div>
<div class="oe_span12">
<h3>Contributors</h3>
<ul>
<li>Juan Formoso &lt;<a href="mailto:jfv@anubia.es">email.jfv@anubia.es</a>&gt;</li>
</ul>
</div>
<div class="oe_span12">
<h3>Maintainer</h3>
<p>
This module is maintained by the OCA.<br/>
OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.<br/>
To contribute to this module, please visit <a href="http://odoo-community.org">http://odoo-community.org</a>.<br/>
<a href="http://odoo-community.org"><img class="oe_picture oe_centered" src="http://odoo-community.org/logo.png"></a>
</p>
</div>
</div>
</section>

15
mail_bcc/views/views.xml

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data>
<record id="mail_bcc_form" model="ir.ui.view">
<field name="name">mail.template.mail_bcc</field>
<field name="model">mail.template</field>
<field name="inherit_id" ref="mail.email_template_form"/>
<field name="arch" type="xml">
<xpath expr="/form/sheet/notebook/page[2]/group/field[@name='email_cc']" position="after">
<field name="email_bcc"/>
</xpath>
</field>
</record>
</data>
</odoo>
Loading…
Cancel
Save