Tom Palan
8 years ago
9 changed files with 498 additions and 0 deletions
-
1README.md
-
53mail_bcc/README.rst
-
7mail_bcc/__init__.py
-
44mail_bcc/__openerp__.py
-
8mail_bcc/models/__init__.py
-
160mail_bcc/models/mail_mail.py
-
148mail_bcc/models/mail_template.py
-
62mail_bcc/static/description/index.html
-
15mail_bcc/views/views.xml
@ -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. |
@ -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 |
@ -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, |
|||
} |
@ -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 |
@ -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 |
@ -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 |
|||
|
@ -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 <<a href="mailto:jfv@anubia.es">email.jfv@anubia.es</a>></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> |
@ -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> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue