[MERGE] from trunk
bzr revid: fva@openerp.com-20130917152501-2uohstpmkf5jrcvr
This commit is contained in:
@@ -76,6 +76,9 @@ class sale_config_settings(osv.osv_memory):
|
||||
or a lead object and archive a selected
|
||||
email into an OpenERP mail message with attachments.
|
||||
This installs the module plugin_outlook."""),
|
||||
'module_mass_mailing': fields.boolean(
|
||||
'Manage mass mailing campaigns',
|
||||
help='Get access to statistics with your mass mailing, manage campaigns.'),
|
||||
}
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -123,6 +123,10 @@
|
||||
<field name="module_web_linkedin"/>
|
||||
<label for="module_web_linkedin"/>
|
||||
</div>
|
||||
<div>
|
||||
<field name="module_mass_mailing" class="oe_inline"/>
|
||||
<label for="module_mass_mailing"/>
|
||||
</div>
|
||||
</div>
|
||||
</group>
|
||||
</div>
|
||||
|
||||
@@ -253,6 +253,8 @@ class crm_lead(format_address, osv.osv):
|
||||
multi='day_close', type="float", store=True),
|
||||
'date_last_stage_update': fields.datetime('Last Stage Update', select=True),
|
||||
|
||||
# Messaging and marketing
|
||||
'message_bounce': fields.integer('Bounce'),
|
||||
# Only used for type opportunity
|
||||
'probability': fields.float('Success Rate (%)', group_operator="avg"),
|
||||
'planned_revenue': fields.float('Expected Revenue', track_visibility='always'),
|
||||
|
||||
@@ -169,16 +169,17 @@
|
||||
<field name="description"/>
|
||||
</page>
|
||||
<page string="Extra Info">
|
||||
<group string="Categorization" groups="base.group_multi_company,base.group_no_one" name="categorization">
|
||||
<field name="company_id"
|
||||
groups="base.group_multi_company"
|
||||
widget="selection" colspan="2"/>
|
||||
</group>
|
||||
<group string="Mailings">
|
||||
<field name="opt_out"/>
|
||||
</group>
|
||||
<group string="Misc">
|
||||
<group>
|
||||
<group>
|
||||
<group string="Categorization" groups="base.group_multi_company,base.group_no_one" name="categorization">
|
||||
<field name="company_id"
|
||||
groups="base.group_multi_company"
|
||||
widget="selection"/>
|
||||
</group>
|
||||
<group string="Mailings">
|
||||
<field name="opt_out"/>
|
||||
<field name="message_bounce"/>
|
||||
</group>
|
||||
<group string="Misc">
|
||||
<field name="probability" groups="base.group_no_one"/>
|
||||
<field name="active"/>
|
||||
<field name="referred"/>
|
||||
|
||||
@@ -75,7 +75,7 @@ class email_template(osv.osv):
|
||||
_description = 'Email Templates'
|
||||
_order = 'name'
|
||||
|
||||
def render_template(self, cr, uid, template, model, res_id, context=None):
|
||||
def render_template_batch(self, cr, uid, template, model, res_ids, context=None):
|
||||
"""Render the given template text, replace mako expressions ``${expr}``
|
||||
with the result of evaluating these expressions with
|
||||
an evaluation context containing:
|
||||
@@ -87,46 +87,60 @@ class email_template(osv.osv):
|
||||
|
||||
:param str template: the template text to render
|
||||
:param str model: model name of the document record this mail is related to.
|
||||
:param int res_id: id of the document record this mail is related to.
|
||||
:param int res_ids: list of ids of document records those mails are related to.
|
||||
"""
|
||||
if not template:
|
||||
return u""
|
||||
if context is None:
|
||||
context = {}
|
||||
try:
|
||||
template = tools.ustr(template)
|
||||
record = None
|
||||
if res_id:
|
||||
record = self.pool[model].browse(cr, uid, res_id, context=context)
|
||||
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
|
||||
variables = {
|
||||
'object': record,
|
||||
'user': user,
|
||||
'ctx': context, # context kw would clash with mako internals
|
||||
}
|
||||
result = mako_template_env.from_string(template).render(variables)
|
||||
if result == u"False":
|
||||
result = u""
|
||||
return result
|
||||
except Exception:
|
||||
_logger.exception("failed to render mako template value %r", template)
|
||||
return u""
|
||||
results = dict.fromkeys(res_ids, u"")
|
||||
|
||||
def get_email_template(self, cr, uid, template_id=False, record_id=None, context=None):
|
||||
# try to load the template
|
||||
try:
|
||||
template = mako_template_env.from_string(tools.ustr(template))
|
||||
except Exception:
|
||||
_logger.exception("Failed to load template %r", template)
|
||||
return results
|
||||
|
||||
# prepare template variables
|
||||
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
|
||||
records = self.pool[model].browse(cr, uid, res_ids, context=context) or [None]
|
||||
variables = {
|
||||
'user': user,
|
||||
'ctx': context, # context kw would clash with mako internals
|
||||
}
|
||||
for record in records:
|
||||
res_id = record.id if record else None
|
||||
variables['object'] = record
|
||||
try:
|
||||
render_result = template.render(variables)
|
||||
except Exception:
|
||||
_logger.exception("Failed to render template %r using values %r" % (template, variables))
|
||||
render_result = u""
|
||||
if render_result == u"False":
|
||||
render_result = u""
|
||||
results[res_id] = render_result
|
||||
return results
|
||||
|
||||
def get_email_template_batch(self, cr, uid, template_id=False, res_ids=None, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
if res_ids is None:
|
||||
res_ids = [None]
|
||||
results = dict.fromkeys(res_ids, False)
|
||||
|
||||
if not template_id:
|
||||
return False
|
||||
return results
|
||||
template = self.browse(cr, uid, template_id, context)
|
||||
lang = self.render_template(cr, uid, template.lang, template.model, record_id, context)
|
||||
if lang:
|
||||
# Use translated template if necessary
|
||||
ctx = context.copy()
|
||||
ctx['lang'] = lang
|
||||
template = self.browse(cr, uid, template.id, ctx)
|
||||
else:
|
||||
template = self.browse(cr, uid, int(template_id), context)
|
||||
return template
|
||||
langs = self.render_template_batch(cr, uid, template.lang, template.model, res_ids, context)
|
||||
for res_id, lang in langs.iteritems():
|
||||
if lang:
|
||||
# Use translated template if necessary
|
||||
ctx = context.copy()
|
||||
ctx['lang'] = lang
|
||||
template = self.browse(cr, uid, template.id, ctx)
|
||||
else:
|
||||
template = self.browse(cr, uid, int(template_id), context)
|
||||
results[res_id] = template
|
||||
return results
|
||||
|
||||
def onchange_model_id(self, cr, uid, ids, model_id, context=None):
|
||||
mod_name = False
|
||||
@@ -308,64 +322,75 @@ class email_template(osv.osv):
|
||||
})
|
||||
return {'value': result}
|
||||
|
||||
def generate_email(self, cr, uid, template_id, res_id, context=None):
|
||||
"""Generates an email from the template for given (model, res_id) pair.
|
||||
def generate_email_batch(self, cr, uid, template_id, res_ids, context=None, 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 expected by :py:meth:`mail_thread.message_post`.
|
||||
: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 expected by :py:meth:`mail_thread.message_post`.
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
if fields is None:
|
||||
fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to']
|
||||
|
||||
report_xml_pool = self.pool.get('ir.actions.report.xml')
|
||||
template = self.get_email_template(cr, uid, template_id, res_id, context)
|
||||
values = {}
|
||||
for field in ['subject', 'body_html', 'email_from',
|
||||
'email_to', 'partner_to', 'email_cc', 'reply_to']:
|
||||
values[field] = self.render_template(cr, uid, getattr(template, field),
|
||||
template.model, res_id, context=context) \
|
||||
or False
|
||||
if template.user_signature:
|
||||
signature = self.pool.get('res.users').browse(cr, uid, uid, context).signature
|
||||
values['body_html'] = tools.append_content_to_html(values['body_html'], signature)
|
||||
res_ids_to_templates = self.get_email_template_batch(cr, uid, template_id, res_ids, context)
|
||||
|
||||
if values['body_html']:
|
||||
values['body'] = tools.html_sanitize(values['body_html'])
|
||||
# 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)
|
||||
|
||||
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)
|
||||
results = dict()
|
||||
for template, template_res_ids in templates_to_res_ids.iteritems():
|
||||
# generate fields value for all res_ids linked to the current template
|
||||
for field in ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to']:
|
||||
generated_field_values = self.render_template_batch(cr, uid, getattr(template, field), template.model, template_res_ids, context=context)
|
||||
for res_id, field_value in generated_field_values.iteritems():
|
||||
results.setdefault(res_id, dict())[field] = field_value
|
||||
# update values for all res_ids
|
||||
for res_id in template_res_ids:
|
||||
values = results[res_id]
|
||||
if template.user_signature:
|
||||
signature = self.pool.get('res.users').browse(cr, uid, uid, context).signature
|
||||
values['body_html'] = tools.append_content_to_html(values['body_html'], signature)
|
||||
if values['body_html']:
|
||||
values['body'] = tools.html_sanitize(values['body_html'])
|
||||
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],
|
||||
)
|
||||
|
||||
attachments = []
|
||||
# Add report in attachments
|
||||
if template.report_template:
|
||||
report_name = self.render_template(cr, uid, template.report_name, template.model, res_id, context=context)
|
||||
report_service = report_xml_pool.browse(cr, uid, template.report_template.id, context).report_name
|
||||
# Ensure report is rendered using template's language
|
||||
ctx = context.copy()
|
||||
if template.lang:
|
||||
ctx['lang'] = self.render_template(cr, uid, template.lang, template.model, res_id, context)
|
||||
result, format = openerp.report.render_report(cr, uid, [res_id], report_service, {'model': template.model}, ctx)
|
||||
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))
|
||||
# Add report in attachments
|
||||
if template.report_template:
|
||||
for res_id in template_res_ids:
|
||||
attachments = []
|
||||
report_name = self.render_template(cr, uid, template.report_name, template.model, res_id, context=context)
|
||||
report_service = report_xml_pool.browse(cr, uid, template.report_template.id, context).report_name
|
||||
# Ensure report is rendered using template's language
|
||||
ctx = context.copy()
|
||||
if template.lang:
|
||||
ctx['lang'] = self.render_template_batch(cr, uid, template.lang, template.model, res_id, context) # take 0 ?
|
||||
result, format = openerp.report.render_report(cr, uid, [res_id], report_service, {'model': template.model}, ctx)
|
||||
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))
|
||||
|
||||
attachment_ids = []
|
||||
# Add template attachments
|
||||
for attach in template.attachment_ids:
|
||||
attachment_ids.append(attach.id)
|
||||
values['attachments'] = attachments
|
||||
|
||||
values['attachments'] = attachments
|
||||
values['attachment_ids'] = attachment_ids
|
||||
return values
|
||||
return results
|
||||
|
||||
def send_mail(self, cr, uid, template_id, res_id, force_send=False, raise_exception=False, context=None):
|
||||
"""Generates a new mail message for the given template and record,
|
||||
@@ -412,4 +437,14 @@ class email_template(osv.osv):
|
||||
mail_mail.send(cr, uid, [msg_id], raise_exception=raise_exception, context=context)
|
||||
return msg_id
|
||||
|
||||
# Compatibility method
|
||||
def render_template(self, cr, uid, template, model, res_id, context=None):
|
||||
return self.render_template_batch(cr, uid, template, model, [res_id], context)[res_id]
|
||||
|
||||
def get_email_template(self, cr, uid, template_id=False, record_id=None, context=None):
|
||||
return self.get_email_template_batch(cr, uid, template_id, [record_id], context)[record_id]
|
||||
|
||||
def generate_email(self, cr, uid, template_id, res_id, context=None):
|
||||
return self.generate_email_batch(cr, uid, template_id, [res_id], context)[res_id]
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -20,10 +20,10 @@
|
||||
##############################################################################
|
||||
|
||||
import base64
|
||||
from openerp.addons.mail.tests.test_mail_base import TestMailBase
|
||||
from openerp.addons.mail.tests.common import TestMail
|
||||
|
||||
|
||||
class test_message_compose(TestMailBase):
|
||||
class test_message_compose(TestMail):
|
||||
|
||||
def setUp(self):
|
||||
super(test_message_compose, self).setUp()
|
||||
@@ -73,7 +73,7 @@ class test_message_compose(TestMailBase):
|
||||
|
||||
# 1. Comment on pigs
|
||||
compose_id = mail_compose.create(cr, uid,
|
||||
{'subject': 'Forget me subject', 'body': '<p>Dummy body</p>'},
|
||||
{'subject': 'Forget me subject', 'body': '<p>Dummy body</p>', 'post': True},
|
||||
{'default_composition_mode': 'comment',
|
||||
'default_model': 'mail.group',
|
||||
'default_res_id': self.group_pigs_id,
|
||||
@@ -101,7 +101,7 @@ class test_message_compose(TestMailBase):
|
||||
'default_template_id': email_template_id,
|
||||
'active_ids': [self.group_pigs_id, self.group_bird_id]
|
||||
}
|
||||
compose_id = mail_compose.create(cr, uid, {'subject': 'Forget me subject', 'body': 'Dummy body'}, context)
|
||||
compose_id = mail_compose.create(cr, uid, {'subject': 'Forget me subject', 'body': 'Dummy body', 'post': True}, context)
|
||||
compose = mail_compose.browse(cr, uid, compose_id, context)
|
||||
onchange_res = compose.onchange_template_id(email_template_id, 'comment', 'mail.group', self.group_pigs_id)['value']
|
||||
onchange_res['partner_ids'] = [(4, partner_id) for partner_id in onchange_res.pop('partner_ids', [])]
|
||||
@@ -145,7 +145,7 @@ class test_message_compose(TestMailBase):
|
||||
'default_partner_ids': [p_a_id],
|
||||
'active_ids': [self.group_pigs_id, self.group_bird_id]
|
||||
}
|
||||
compose_id = mail_compose.create(cr, uid, {'subject': 'Forget me subject', 'body': 'Dummy body'}, context)
|
||||
compose_id = mail_compose.create(cr, uid, {'subject': 'Forget me subject', 'body': 'Dummy body', 'post': True}, context)
|
||||
compose = mail_compose.browse(cr, uid, compose_id, context)
|
||||
onchange_res = compose.onchange_template_id(email_template_id, 'mass_mail', 'mail.group', self.group_pigs_id)['value']
|
||||
onchange_res['partner_ids'] = [(4, partner_id) for partner_id in onchange_res.pop('partner_ids', [])]
|
||||
|
||||
@@ -62,6 +62,7 @@ class mail_compose_message(osv.TransientModel):
|
||||
for wizard in self.browse(cr, uid, ids, context=context):
|
||||
if wizard.template_id:
|
||||
wizard_context['mail_notify_user_signature'] = False # template user_signature is added when generating body_html
|
||||
wizard_context['mail_auto_delete'] = wizard.template_id.auto_delete # mass mailing: use template auto_delete value -> note, for emails mass mailing only
|
||||
if not wizard.attachment_ids or wizard.composition_mode == 'mass_mail' or not wizard.template_id:
|
||||
continue
|
||||
new_attachment_ids = []
|
||||
@@ -81,7 +82,7 @@ class mail_compose_message(osv.TransientModel):
|
||||
template_values = self.pool.get('email.template').read(cr, uid, template_id, fields, context)
|
||||
values = dict((field, template_values[field]) for field in fields if template_values.get(field))
|
||||
elif template_id:
|
||||
values = self.generate_email_for_composer(cr, uid, template_id, res_id, context=context)
|
||||
values = self.generate_email_for_composer_batch(cr, uid, template_id, [res_id], context=context)[res_id]
|
||||
# transform attachments into attachment_ids; not attached to the document because this will
|
||||
# be done further in the posting process, allowing to clean database if email not send
|
||||
values['attachment_ids'] = values.pop('attachment_ids', [])
|
||||
@@ -147,45 +148,55 @@ class mail_compose_message(osv.TransientModel):
|
||||
partner_ids.append(int(partner_id))
|
||||
return partner_ids
|
||||
|
||||
def generate_email_for_composer(self, cr, uid, template_id, res_id, context=None):
|
||||
def generate_email_for_composer_batch(self, cr, uid, template_id, res_ids, context=None):
|
||||
""" Call email_template.generate_email(), get fields relevant for
|
||||
mail.compose.message, transform email_cc and email_to into partner_ids """
|
||||
template_values = self.pool.get('email.template').generate_email(cr, uid, template_id, res_id, context=context)
|
||||
# filter template values
|
||||
fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to', 'attachment_ids', 'attachments', 'mail_server_id']
|
||||
values = dict((field, template_values[field]) for field in fields if template_values.get(field))
|
||||
values['body'] = values.pop('body_html', '')
|
||||
values = dict.fromkeys(res_ids, False)
|
||||
|
||||
# transform email_to, email_cc into partner_ids
|
||||
ctx = dict((k, v) for k, v in (context or {}).items() if not k.startswith('default_'))
|
||||
partner_ids = self._get_or_create_partners_from_values(cr, uid, values, context=ctx)
|
||||
# legacy template behavior: void values do not erase existing values and the
|
||||
# related key is removed from the values dict
|
||||
if partner_ids:
|
||||
values['partner_ids'] = list(partner_ids)
|
||||
template_values = self.pool.get('email.template').generate_email_batch(cr, uid, template_id, res_ids, context=context)
|
||||
for res_id in res_ids:
|
||||
res_id_values = dict((field, template_values[res_id][field]) for field in fields if template_values[res_id].get(field))
|
||||
res_id_values['body'] = res_id_values.pop('body_html', '')
|
||||
|
||||
# transform email_to, email_cc into partner_ids
|
||||
ctx = dict((k, v) for k, v in (context or {}).items() if not k.startswith('default_'))
|
||||
partner_ids = self._get_or_create_partners_from_values(cr, uid, res_id_values, context=ctx)
|
||||
# legacy template behavior: void values do not erase existing values and the
|
||||
# related key is removed from the values dict
|
||||
if partner_ids:
|
||||
res_id_values['partner_ids'] = list(partner_ids)
|
||||
|
||||
values[res_id] = res_id_values
|
||||
return values
|
||||
|
||||
def render_message(self, cr, uid, wizard, res_id, context=None):
|
||||
def render_message_batch(self, cr, uid, wizard, res_ids, context=None):
|
||||
""" Override to handle templates. """
|
||||
# generate the composer email
|
||||
# generate template-based values
|
||||
if wizard.template_id:
|
||||
values = self.generate_email_for_composer(cr, uid, wizard.template_id.id, res_id, context=context)
|
||||
template_values = self.generate_email_for_composer_batch(cr, uid, wizard.template_id.id, res_ids, context=context)
|
||||
else:
|
||||
values = {}
|
||||
# remove attachments as they should not be rendered
|
||||
values.pop('attachment_ids', None)
|
||||
# get values to return
|
||||
email_dict = super(mail_compose_message, self).render_message(cr, uid, wizard, res_id, context)
|
||||
# those values are not managed; they are readonly
|
||||
email_dict.pop('email_to', None)
|
||||
email_dict.pop('email_cc', None)
|
||||
email_dict.pop('partner_to', None)
|
||||
# update template values by wizard values
|
||||
values.update(email_dict)
|
||||
return values
|
||||
template_values = dict.fromkeys(res_ids, dict())
|
||||
# generate composer values
|
||||
composer_values = super(mail_compose_message, self).render_message_batch(cr, uid, wizard, res_ids, context)
|
||||
|
||||
def render_template(self, cr, uid, template, model, res_id, context=None):
|
||||
return self.pool.get('email.template').render_template(cr, uid, template, model, res_id, context=context)
|
||||
for res_id in res_ids:
|
||||
# remove attachments from template values as they should not be rendered
|
||||
template_values[res_id].pop('attachment_ids', None)
|
||||
# remove some keys from composer that are readonly
|
||||
composer_values[res_id].pop('email_to', None)
|
||||
composer_values[res_id].pop('email_cc', None)
|
||||
composer_values[res_id].pop('partner_to', None)
|
||||
# update template values by composer values
|
||||
template_values[res_id].update(composer_values[res_id])
|
||||
return template_values
|
||||
|
||||
def render_template_batch(self, cr, uid, template, model, res_ids, context=None):
|
||||
return self.pool.get('email.template').render_template_batch(cr, uid, template, model, res_ids, context=context)
|
||||
|
||||
# Compatibility methods
|
||||
def generate_email_for_composer(self, cr, uid, template_id, res_id, context=None):
|
||||
return self.generate_email_for_composer_batch(cr, uid, template_id, [res_id], context)[res_id]
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<label string="Template Recipients" for="partner_to"
|
||||
groups="base.group_no_one"
|
||||
attrs="{'invisible':[('composition_mode', '!=', 'mass_mail')]}"/>
|
||||
<div groups="base.group_no_one"
|
||||
<div groups="base.group_no_one" name="template_recipients"
|
||||
attrs="{'invisible':[('composition_mode', '!=', 'mass_mail')]}">
|
||||
<group class="oe_grey">
|
||||
<!-- <label string="Partners" for="partner_to"/> -->
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
import base64
|
||||
import psycopg2
|
||||
|
||||
import openerp
|
||||
from openerp import SUPERUSER_ID
|
||||
import openerp.addons.web.http as oeweb
|
||||
import openerp.addons.web.http as http
|
||||
from openerp.addons.web.controllers.main import content_disposition
|
||||
|
||||
#----------------------------------------------------------
|
||||
# Controller
|
||||
#----------------------------------------------------------
|
||||
class MailController(oeweb.Controller):
|
||||
|
||||
class MailController(http.Controller):
|
||||
_cp_path = '/mail'
|
||||
|
||||
@oeweb.httprequest
|
||||
@http.httprequest
|
||||
def download_attachment(self, req, model, id, method, attachment_id, **kw):
|
||||
Model = req.session.model(model)
|
||||
res = getattr(Model, method)(int(id), int(attachment_id))
|
||||
@@ -24,7 +23,7 @@ class MailController(oeweb.Controller):
|
||||
('Content-Disposition', content_disposition(filename, req))])
|
||||
return req.not_found()
|
||||
|
||||
@oeweb.jsonrequest
|
||||
@http.jsonrequest
|
||||
def receive(self, req):
|
||||
""" End-point to receive mail from an external SMTP server. """
|
||||
dbs = req.jsonrequest.get('databases')
|
||||
|
||||
@@ -6,6 +6,15 @@ Changelog
|
||||
`trunk (saas-2)`
|
||||
----------------
|
||||
|
||||
- ``mass_mailing_campaign`` update
|
||||
|
||||
- ``mail_mail`: moved ``reply_to`` computation from ``mail_mail`` to ``mail_message``
|
||||
where it belongs, as the field is located onto the ``mail_message`` model.
|
||||
- ``mail_compose_message``: template rendering is now done in batch. Each template
|
||||
is rendered for all res_ids, instead of all templates one id at a time.
|
||||
- ``mail_thread``: to ease inheritance, processing of routes is now done in
|
||||
message_route_process, called in message_route
|
||||
|
||||
- added support of ``active_domain`` form context, coming from the list view.
|
||||
When checking the header hook, the mass mailing will be done on all records
|
||||
matching the ``active_domain``.
|
||||
|
||||
@@ -77,7 +77,7 @@ class mail_notification(osv.Model):
|
||||
if not cr.fetchone():
|
||||
cr.execute('CREATE INDEX mail_notification_partner_id_read_starred_message_id ON mail_notification (partner_id, read, starred, message_id)')
|
||||
|
||||
def get_partners_to_notify(self, cr, uid, message, partners_to_notify=None, context=None):
|
||||
def get_partners_to_email(self, cr, uid, ids, message, context=None):
|
||||
""" Return the list of partners to notify, based on their preferences.
|
||||
|
||||
:param browse_record message: mail.message to notify
|
||||
@@ -85,13 +85,10 @@ class mail_notification(osv.Model):
|
||||
the notifications to process
|
||||
"""
|
||||
notify_pids = []
|
||||
for notification in message.notification_ids:
|
||||
for notification in self.browse(cr, uid, ids, context=context):
|
||||
if notification.read:
|
||||
continue
|
||||
partner = notification.partner_id
|
||||
# If partners_to_notify specified: restrict to them
|
||||
if partners_to_notify is not None and partner.id not in partners_to_notify:
|
||||
continue
|
||||
# Do not send to partners without email address defined
|
||||
if not partner.email:
|
||||
continue
|
||||
@@ -143,14 +140,62 @@ class mail_notification(osv.Model):
|
||||
company = user.company_id.name
|
||||
sent_by = _('Sent by %(company)s using %(openerp)s.')
|
||||
signature_company = '<small>%s</small>' % (sent_by % {
|
||||
'company': company,
|
||||
'openerp': "<a style='color:inherit' href='https://www.openerp.com/'>OpenERP</a>"
|
||||
})
|
||||
'company': company,
|
||||
'openerp': "<a style='color:inherit' href='https://www.openerp.com/'>OpenERP</a>"
|
||||
})
|
||||
footer = tools.append_content_to_html(footer, signature_company, plaintext=False, container_tag='div')
|
||||
|
||||
return footer
|
||||
|
||||
def _notify(self, cr, uid, msg_id, partners_to_notify=None, context=None,
|
||||
def update_message_notification(self, cr, uid, ids, message_id, partner_ids, context=None):
|
||||
existing_pids = set()
|
||||
new_pids = set()
|
||||
new_notif_ids = []
|
||||
|
||||
for notification in self.browse(cr, uid, ids, context=context):
|
||||
existing_pids.add(notification.partner_id.id)
|
||||
|
||||
# update existing notifications
|
||||
self.write(cr, uid, ids, {'read': False}, context=context)
|
||||
|
||||
# create new notifications
|
||||
new_pids = set(partner_ids) - existing_pids
|
||||
for new_pid in new_pids:
|
||||
new_notif_ids.append(self.create(cr, uid, {'message_id': message_id, 'partner_id': new_pid, 'read': False}, context=context))
|
||||
return new_notif_ids
|
||||
|
||||
def _notify_email(self, cr, uid, ids, message_id, force_send=False, user_signature=True, context=None):
|
||||
message = self.pool['mail.message'].browse(cr, SUPERUSER_ID, message_id, context=context)
|
||||
|
||||
# compute partners
|
||||
email_pids = self.get_partners_to_email(cr, uid, ids, message, context=None)
|
||||
if not email_pids:
|
||||
return True
|
||||
|
||||
# compute email body (signature, company data)
|
||||
body_html = message.body
|
||||
user_id = message.author_id and message.author_id.user_ids and message.author_id.user_ids[0] and message.author_id.user_ids[0].id or None
|
||||
if user_signature:
|
||||
signature_company = self.get_signature_footer(cr, uid, user_id, res_model=message.model, res_id=message.res_id, context=context)
|
||||
body_html = tools.append_content_to_html(body_html, signature_company, plaintext=False, container_tag='div')
|
||||
|
||||
# compute email references
|
||||
references = message.parent_id.message_id if message.parent_id else False
|
||||
|
||||
# create email values
|
||||
mail_values = {
|
||||
'mail_message_id': message.id,
|
||||
'auto_delete': True,
|
||||
'body_html': body_html,
|
||||
'recipient_ids': [(4, id) for id in email_pids],
|
||||
'references': references,
|
||||
}
|
||||
email_notif_id = self.pool.get('mail.mail').create(cr, uid, mail_values, context=context)
|
||||
if force_send:
|
||||
self.pool.get('mail.mail').send(cr, uid, [email_notif_id], context=context)
|
||||
return True
|
||||
|
||||
def _notify(self, cr, uid, message_id, partners_to_notify=None, context=None,
|
||||
force_send=False, user_signature=True):
|
||||
""" Send by email the notification depending on the user preferences
|
||||
|
||||
@@ -162,57 +207,14 @@ class mail_notification(osv.Model):
|
||||
:param bool user_signature: if True, the generated mail.mail body is
|
||||
the body of the related mail.message with the author's signature
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
mail_message_obj = self.pool.get('mail.message')
|
||||
notif_ids = self.search(cr, SUPERUSER_ID, [('message_id', '=', message_id), ('partner_id', 'in', partners_to_notify)], context=context)
|
||||
|
||||
# optional list of partners to notify: subscribe them if not already done or update the notification
|
||||
if partners_to_notify:
|
||||
notifications_to_update = []
|
||||
notified_partners = []
|
||||
notif_ids = self.search(cr, SUPERUSER_ID, [('message_id', '=', msg_id), ('partner_id', 'in', partners_to_notify)], context=context)
|
||||
for notification in self.browse(cr, SUPERUSER_ID, notif_ids, context=context):
|
||||
notified_partners.append(notification.partner_id.id)
|
||||
notifications_to_update.append(notification.id)
|
||||
partners_to_notify = filter(lambda item: item not in notified_partners, partners_to_notify)
|
||||
if notifications_to_update:
|
||||
self.write(cr, SUPERUSER_ID, notifications_to_update, {'read': False}, context=context)
|
||||
mail_message_obj.write(cr, uid, msg_id, {'notified_partner_ids': [(4, id) for id in partners_to_notify]}, context=context)
|
||||
# update or create notifications
|
||||
new_notif_ids = self.update_message_notification(cr, SUPERUSER_ID, notif_ids, message_id, partners_to_notify, context=context)
|
||||
|
||||
# mail_notify_noemail (do not send email) or no partner_ids: do not send, return
|
||||
if context.get('mail_notify_noemail'):
|
||||
if context and context.get('mail_notify_noemail'):
|
||||
return True
|
||||
|
||||
# browse as SUPERUSER_ID because of access to res_partner not necessarily allowed
|
||||
msg = self.pool.get('mail.message').browse(cr, SUPERUSER_ID, msg_id, context=context)
|
||||
notify_partner_ids = self.get_partners_to_notify(cr, uid, msg, partners_to_notify=partners_to_notify, context=context)
|
||||
if not notify_partner_ids:
|
||||
return True
|
||||
|
||||
# add the context in the email
|
||||
# TDE FIXME: commented, to be improved in a future branch
|
||||
# quote_context = self.pool.get('mail.message').message_quote_context(cr, uid, msg_id, context=context)
|
||||
|
||||
# add signature
|
||||
body_html = msg.body
|
||||
user_id = msg.author_id and msg.author_id.user_ids and msg.author_id.user_ids[0] and msg.author_id.user_ids[0].id or None
|
||||
if user_signature:
|
||||
signature_company = self.get_signature_footer(cr, uid, user_id, res_model=msg.model, res_id=msg.res_id, context=context)
|
||||
body_html = tools.append_content_to_html(body_html, signature_company, plaintext=False, container_tag='div')
|
||||
|
||||
references = False
|
||||
if msg.parent_id:
|
||||
references = msg.parent_id.message_id
|
||||
|
||||
mail_values = {
|
||||
'mail_message_id': msg.id,
|
||||
'auto_delete': True,
|
||||
'body_html': body_html,
|
||||
'recipient_ids': [(4, id) for id in notify_partner_ids],
|
||||
'references': references,
|
||||
}
|
||||
mail_mail = self.pool.get('mail.mail')
|
||||
email_notif_id = mail_mail.create(cr, uid, mail_values, context=context)
|
||||
|
||||
if force_send:
|
||||
mail_mail.send(cr, uid, [email_notif_id], context=context)
|
||||
return True
|
||||
self._notify_email(cr, SUPERUSER_ID, new_notif_ids, message_id, force_send, user_signature, context=context)
|
||||
|
||||
+29
-92
@@ -61,15 +61,9 @@ class mail_mail(osv.Model):
|
||||
# Auto-detected based on create() - if 'mail_message_id' was passed then this mail is a notification
|
||||
# and during unlink() we will not cascade delete the parent and its attachments
|
||||
'notification': fields.boolean('Is Notification',
|
||||
help='Mail has been created to notify people of an existing mail.message')
|
||||
help='Mail has been created to notify people of an existing mail.message'),
|
||||
}
|
||||
|
||||
def _get_default_from(self, cr, uid, context=None):
|
||||
""" Kept for compatibility
|
||||
TDE TODO: remove me in 8.0
|
||||
"""
|
||||
return self.pool['mail.message']._get_default_from(cr, uid, context=context)
|
||||
|
||||
_defaults = {
|
||||
'state': 'outgoing',
|
||||
}
|
||||
@@ -81,74 +75,11 @@ class mail_mail(osv.Model):
|
||||
context = dict(context, default_type=None)
|
||||
return super(mail_mail, self).default_get(cr, uid, fields, context=context)
|
||||
|
||||
def _get_reply_to(self, cr, uid, values, context=None):
|
||||
""" Return a specific reply_to: alias of the document through message_get_reply_to
|
||||
or take the email_from
|
||||
"""
|
||||
# if value specified: directly return it
|
||||
if values.get('reply_to'):
|
||||
return values.get('reply_to')
|
||||
format_name = True # whether to use a 'Followers of Pigs <pigs@openerp.com' format
|
||||
email_reply_to = None
|
||||
|
||||
ir_config_parameter = self.pool.get("ir.config_parameter")
|
||||
catchall_domain = ir_config_parameter.get_param(cr, uid, "mail.catchall.domain", context=context)
|
||||
|
||||
# model, res_id, email_from: comes from values OR related message
|
||||
model, res_id, email_from = values.get('model'), values.get('res_id'), values.get('email_from')
|
||||
if values.get('mail_message_id'):
|
||||
message = self.pool.get('mail.message').browse(cr, uid, values.get('mail_message_id'), context=context)
|
||||
if message.reply_to:
|
||||
email_reply_to = message.reply_to
|
||||
format_name = False
|
||||
if not model:
|
||||
model = message.model
|
||||
if not res_id:
|
||||
res_id = message.res_id
|
||||
if not email_from:
|
||||
email_from = message.email_from
|
||||
|
||||
# if model and res_id: try to use ``message_get_reply_to`` that returns the document alias
|
||||
if not email_reply_to and model and res_id and hasattr(self.pool[model], 'message_get_reply_to'):
|
||||
email_reply_to = self.pool[model].message_get_reply_to(cr, uid, [res_id], context=context)[0]
|
||||
# no alias reply_to -> catchall alias
|
||||
if not email_reply_to:
|
||||
catchall_alias = ir_config_parameter.get_param(cr, uid, "mail.catchall.alias", context=context)
|
||||
if catchall_domain and catchall_alias:
|
||||
email_reply_to = '%s@%s' % (catchall_alias, catchall_domain)
|
||||
|
||||
# still no reply_to -> reply_to will be the email_from
|
||||
if not email_reply_to and email_from:
|
||||
email_reply_to = email_from
|
||||
|
||||
# format 'Document name <email_address>'
|
||||
if email_reply_to and model and res_id and format_name:
|
||||
emails = tools.email_split(email_reply_to)
|
||||
if emails:
|
||||
email_reply_to = emails[0]
|
||||
document_name = self.pool[model].name_get(cr, SUPERUSER_ID, [res_id], context=context)[0]
|
||||
if document_name:
|
||||
# sanitize document name
|
||||
sanitized_doc_name = re.sub(r'[^\w+.]+', '-', document_name[1])
|
||||
# generate reply to
|
||||
email_reply_to = _('"Followers of %s" <%s>') % (sanitized_doc_name, email_reply_to)
|
||||
|
||||
return email_reply_to
|
||||
|
||||
def create(self, cr, uid, values, context=None):
|
||||
# notification field: if not set, set if mail comes from an existing mail.message
|
||||
if 'notification' not in values and values.get('mail_message_id'):
|
||||
values['notification'] = True
|
||||
mail_id = super(mail_mail, self).create(cr, uid, values, context=context)
|
||||
|
||||
# reply_to: if not set, set with default values that require creation values
|
||||
# but delegate after creation because of mail_message.message_id automatic
|
||||
# creation using existence of reply_to
|
||||
if not values.get('reply_to'):
|
||||
reply_to = self._get_reply_to(cr, uid, values, context=context)
|
||||
if reply_to:
|
||||
self.write(cr, uid, [mail_id], {'reply_to': reply_to}, context=context)
|
||||
return mail_id
|
||||
return super(mail_mail, self).create(cr, uid, values, context=context)
|
||||
|
||||
def unlink(self, cr, uid, ids, context=None):
|
||||
# cascade-delete the parent message for all mails that are not created for a notification
|
||||
@@ -213,11 +144,6 @@ class mail_mail(osv.Model):
|
||||
# mail_mail formatting, tools and send mechanism
|
||||
#------------------------------------------------------
|
||||
|
||||
# TODO in 8.0(+): maybe factorize this to enable in modules link generation
|
||||
# independently of mail_mail model
|
||||
# TODO in 8.0(+): factorize doc name sanitized and 'Followers of ...' formatting
|
||||
# because it begins to appear everywhere
|
||||
|
||||
def _get_partner_access_link(self, cr, uid, mail, partner=None, context=None):
|
||||
""" Generate URLs for links in mails:
|
||||
- partner is an user and has read access to the document: direct link to document with model, res_id
|
||||
@@ -232,8 +158,8 @@ class mail_mail(osv.Model):
|
||||
}
|
||||
if mail.notification:
|
||||
fragment.update({
|
||||
'message_id': mail.mail_message_id.id,
|
||||
})
|
||||
'message_id': mail.mail_message_id.id,
|
||||
})
|
||||
url = urljoin(base_url, "?%s#%s" % (urlencode(query), urlencode(fragment)))
|
||||
return _("""<small>Access your messages and documents <a style='color:inherit' href="%s">in OpenERP</a></small>""") % url
|
||||
else:
|
||||
@@ -325,26 +251,37 @@ class mail_mail(osv.Model):
|
||||
email_list.append(self.send_get_email_dict(cr, uid, mail, context=context))
|
||||
for partner in mail.recipient_ids:
|
||||
email_list.append(self.send_get_email_dict(cr, uid, mail, partner=partner, context=context))
|
||||
# headers
|
||||
headers = {}
|
||||
bounce_alias = self.pool['ir.config_parameter'].get_param(cr, uid, "mail.bounce.alias", context=context)
|
||||
catchall_domain = self.pool['ir.config_parameter'].get_param(cr, uid, "mail.catchall.domain", context=context)
|
||||
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)
|
||||
|
||||
# build an RFC2822 email.message.Message object and send it without queuing
|
||||
res = None
|
||||
for email in email_list:
|
||||
msg = ir_mail_server.build_email(
|
||||
email_from = mail.email_from,
|
||||
email_to = email.get('email_to'),
|
||||
subject = email.get('subject'),
|
||||
body = email.get('body'),
|
||||
body_alternative = email.get('body_alternative'),
|
||||
email_cc = tools.email_split(mail.email_cc),
|
||||
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')
|
||||
email_from=mail.email_from,
|
||||
email_to=email.get('email_to'),
|
||||
subject=email.get('subject'),
|
||||
body=email.get('body'),
|
||||
body_alternative=email.get('body_alternative'),
|
||||
email_cc=tools.email_split(mail.email_cc),
|
||||
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)
|
||||
res = ir_mail_server.send_email(cr, uid, msg,
|
||||
mail_server_id=mail.mail_server_id.id, context=context)
|
||||
mail_server_id=mail.mail_server_id.id,
|
||||
context=context)
|
||||
if res:
|
||||
mail.write({'state': 'sent', 'message_id': res})
|
||||
mail_sent = True
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<sheet>
|
||||
<label for="subject" class="oe_edit_only"/>
|
||||
<h2><field name="subject"/></h2>
|
||||
<div>
|
||||
<div style="vertical-align: top;">
|
||||
by <field name="author_id" class="oe_inline" string="User"/> on <field name="date" class="oe_inline"/>
|
||||
<button name="%(action_email_compose_message_wizard)d" string="Reply" type="action" icon="terp-mail-replied"
|
||||
context="{'default_composition_mode':'reply', 'default_parent_id': active_id}" states='received,sent,exception,cancel'/>
|
||||
@@ -32,20 +32,26 @@
|
||||
</page>
|
||||
<page string="Advanced" groups="base.group_no_one">
|
||||
<group>
|
||||
<group>
|
||||
<field name="auto_delete"/>
|
||||
<field name="type"/>
|
||||
<field name="state"/>
|
||||
<field name="mail_server_id"/>
|
||||
<field name="model"/>
|
||||
<field name="res_id"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="message_id"/>
|
||||
<field name="references"/>
|
||||
<field name="partner_ids" widget="many2many_tags"/>
|
||||
<field name="notified_partner_ids" widget="many2many_tags"/>
|
||||
</group>
|
||||
<div>
|
||||
<group string="Status">
|
||||
<field name="auto_delete"/>
|
||||
<field name="type"/>
|
||||
<field name="state"/>
|
||||
<field name="mail_server_id"/>
|
||||
<field name="model"/>
|
||||
<field name="res_id"/>
|
||||
</group>
|
||||
</div>
|
||||
<div>
|
||||
<group string="Headers">
|
||||
<field name="message_id"/>
|
||||
<field name="references"/>
|
||||
</group>
|
||||
<group string="Recipients">
|
||||
<field name="partner_ids" widget="many2many_tags"/>
|
||||
<field name="notified_partner_ids" widget="many2many_tags"/>
|
||||
</group>
|
||||
</div>
|
||||
</group>
|
||||
</page>
|
||||
<page string="Attachments">
|
||||
|
||||
+80
-27
@@ -20,6 +20,8 @@
|
||||
##############################################################################
|
||||
|
||||
import logging
|
||||
import re
|
||||
|
||||
from openerp import tools
|
||||
|
||||
from email.header import decode_header
|
||||
@@ -98,7 +100,7 @@ class mail_message(osv.Model):
|
||||
def _get_to_read(self, cr, uid, ids, name, arg, context=None):
|
||||
""" Compute if the message is unread by the current user. """
|
||||
res = dict((id, False) for id in ids)
|
||||
partner_id = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
|
||||
partner_id = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid, context=context).partner_id.id
|
||||
notif_obj = self.pool.get('mail.notification')
|
||||
notif_ids = notif_obj.search(cr, uid, [
|
||||
('partner_id', 'in', [partner_id]),
|
||||
@@ -117,7 +119,7 @@ class mail_message(osv.Model):
|
||||
def _get_starred(self, cr, uid, ids, name, arg, context=None):
|
||||
""" Compute if the message is unread by the current user. """
|
||||
res = dict((id, False) for id in ids)
|
||||
partner_id = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
|
||||
partner_id = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid, context=context).partner_id.id
|
||||
notif_obj = self.pool.get('mail.notification')
|
||||
notif_ids = notif_obj.search(cr, uid, [
|
||||
('partner_id', 'in', [partner_id]),
|
||||
@@ -206,11 +208,11 @@ class mail_message(osv.Model):
|
||||
raise osv.except_osv(_('Invalid Action!'), _("Unable to send email, please configure the sender's email address or alias."))
|
||||
|
||||
def _get_default_author(self, cr, uid, context=None):
|
||||
return self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
|
||||
return self.pool.get('res.users').browse(cr, SUPERUSER_ID, uid, context=context).partner_id.id
|
||||
|
||||
_defaults = {
|
||||
'type': 'email',
|
||||
'date': lambda *a: fields.datetime.now(),
|
||||
'date': fields.datetime.now(),
|
||||
'author_id': lambda self, cr, uid, ctx=None: self._get_default_author(cr, uid, ctx),
|
||||
'body': '',
|
||||
'email_from': lambda self, cr, uid, ctx=None: self._get_default_from(cr, uid, ctx),
|
||||
@@ -264,7 +266,7 @@ class mail_message(osv.Model):
|
||||
:return number of message mark as read
|
||||
"""
|
||||
notification_obj = self.pool.get('mail.notification')
|
||||
user_pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
|
||||
user_pid = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid, context=context).partner_id.id
|
||||
domain = [('partner_id', '=', user_pid), ('message_id', 'in', msg_ids)]
|
||||
if not create_missing:
|
||||
domain += [('read', '=', not read)]
|
||||
@@ -292,7 +294,7 @@ class mail_message(osv.Model):
|
||||
(i.e. when acting on displayed messages not notified)
|
||||
"""
|
||||
notification_obj = self.pool.get('mail.notification')
|
||||
user_pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
|
||||
user_pid = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid, context=context).partner_id.id
|
||||
domain = [('partner_id', '=', user_pid), ('message_id', 'in', msg_ids)]
|
||||
if not create_missing:
|
||||
domain += [('starred', '=', not starred)]
|
||||
@@ -330,7 +332,7 @@ class mail_message(osv.Model):
|
||||
"""
|
||||
res_partner_obj = self.pool.get('res.partner')
|
||||
ir_attachment_obj = self.pool.get('ir.attachment')
|
||||
pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=None)['partner_id'][0]
|
||||
pid = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid, context=context).partner_id.id
|
||||
|
||||
# 1. Aggregate partners (author_id and partner_ids) and attachments
|
||||
partner_ids = set()
|
||||
@@ -646,7 +648,7 @@ class mail_message(osv.Model):
|
||||
elif not ids:
|
||||
return ids
|
||||
|
||||
pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'])['partner_id'][0]
|
||||
pid = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid, context=context).partner_id.id
|
||||
author_ids, partner_ids, allowed_ids = set([]), set([]), set([])
|
||||
model_ids = {}
|
||||
|
||||
@@ -706,7 +708,7 @@ class mail_message(osv.Model):
|
||||
ids = [ids]
|
||||
not_obj = self.pool.get('mail.notification')
|
||||
fol_obj = self.pool.get('mail.followers')
|
||||
partner_id = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=None)['partner_id'][0]
|
||||
partner_id = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid, context=None).partner_id.id
|
||||
|
||||
# Read mail_message.ids to have their values
|
||||
message_values = dict.fromkeys(ids)
|
||||
@@ -775,17 +777,66 @@ class mail_message(osv.Model):
|
||||
_('The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: %s, Operation: %s)') % \
|
||||
(self._description, operation))
|
||||
|
||||
def _get_reply_to(self, cr, uid, values, context=None):
|
||||
""" Return a specific reply_to: alias of the document through message_get_reply_to
|
||||
or take the email_from
|
||||
"""
|
||||
email_reply_to = None
|
||||
|
||||
ir_config_parameter = self.pool.get("ir.config_parameter")
|
||||
catchall_domain = ir_config_parameter.get_param(cr, uid, "mail.catchall.domain", context=context)
|
||||
|
||||
# model, res_id, email_from: comes from values OR related message
|
||||
model, res_id, email_from = values.get('model'), values.get('res_id'), values.get('email_from')
|
||||
|
||||
# if model and res_id: try to use ``message_get_reply_to`` that returns the document alias
|
||||
if not email_reply_to and model and res_id and catchall_domain and hasattr(self.pool[model], 'message_get_reply_to'):
|
||||
email_reply_to = self.pool[model].message_get_reply_to(cr, uid, [res_id], context=context)[0]
|
||||
# no alias reply_to -> catchall alias
|
||||
if not email_reply_to and catchall_domain:
|
||||
catchall_alias = ir_config_parameter.get_param(cr, uid, "mail.catchall.alias", context=context)
|
||||
if catchall_alias:
|
||||
email_reply_to = '%s@%s' % (catchall_alias, catchall_domain)
|
||||
# still no reply_to -> reply_to will be the email_from
|
||||
if not email_reply_to and email_from:
|
||||
email_reply_to = email_from
|
||||
|
||||
# format 'Document name <email_address>'
|
||||
if email_reply_to and model and res_id:
|
||||
emails = tools.email_split(email_reply_to)
|
||||
if emails:
|
||||
email_reply_to = emails[0]
|
||||
document_name = self.pool[model].name_get(cr, SUPERUSER_ID, [res_id], context=context)[0]
|
||||
if document_name:
|
||||
# sanitize document name
|
||||
sanitized_doc_name = re.sub(r'[^\w+.]+', '-', document_name[1])
|
||||
# generate reply to
|
||||
email_reply_to = _('"Followers of %s" <%s>') % (sanitized_doc_name, email_reply_to)
|
||||
|
||||
return email_reply_to
|
||||
|
||||
def _get_message_id(self, cr, uid, values, context=None):
|
||||
message_id = None
|
||||
if not values.get('message_id') and values.get('reply_to'):
|
||||
message_id = tools.generate_tracking_message_id('reply_to')
|
||||
elif not values.get('message_id') and values.get('res_id') and values.get('model'):
|
||||
message_id = tools.generate_tracking_message_id('%(res_id)s-%(model)s' % values)
|
||||
elif not values.get('message_id'):
|
||||
message_id = tools.generate_tracking_message_id('private')
|
||||
return message_id
|
||||
|
||||
def create(self, cr, uid, values, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
default_starred = context.pop('default_starred', False)
|
||||
# generate message_id, to redirect answers to the right discussion thread
|
||||
if not values.get('message_id') and values.get('reply_to'):
|
||||
values['message_id'] = tools.generate_tracking_message_id('reply_to')
|
||||
elif not values.get('message_id') and values.get('res_id') and values.get('model'):
|
||||
values['message_id'] = tools.generate_tracking_message_id('%(res_id)s-%(model)s' % values)
|
||||
elif not values.get('message_id'):
|
||||
values['message_id'] = tools.generate_tracking_message_id('private')
|
||||
|
||||
if 'email_from' not in values: # needed to compute reply_to
|
||||
values['email_from'] = self._get_default_from(cr, uid, context=context)
|
||||
if not values.get('message_id'):
|
||||
values['message_id'] = self._get_message_id(cr, uid, values, context=context)
|
||||
if 'reply_to' not in values:
|
||||
values['reply_to'] = self._get_reply_to(cr, uid, values, context=context)
|
||||
|
||||
newid = super(mail_message, self).create(cr, uid, values, context)
|
||||
self._notify(cr, uid, newid, context=context,
|
||||
force_send=context.get('mail_notify_force_send', True),
|
||||
@@ -915,26 +966,28 @@ class mail_message(osv.Model):
|
||||
if message.subtype_id and message.model and message.res_id:
|
||||
fol_obj = self.pool.get("mail.followers")
|
||||
# browse as SUPERUSER because rules could restrict the search results
|
||||
fol_ids = fol_obj.search(cr, SUPERUSER_ID, [
|
||||
('res_model', '=', message.model),
|
||||
('res_id', '=', message.res_id),
|
||||
('subtype_ids', 'in', message.subtype_id.id)
|
||||
fol_ids = fol_obj.search(
|
||||
cr, SUPERUSER_ID, [
|
||||
('res_model', '=', message.model),
|
||||
('res_id', '=', message.res_id),
|
||||
('subtype_ids', 'in', message.subtype_id.id)
|
||||
], context=context)
|
||||
partners_to_notify |= set(fo.partner_id for fo in fol_obj.browse(cr, SUPERUSER_ID, fol_ids, context=context))
|
||||
partners_to_notify |= set(fo.partner_id.id for fo in fol_obj.browse(cr, SUPERUSER_ID, fol_ids, context=context))
|
||||
# remove me from notified partners, unless the message is written on my own wall
|
||||
if message.subtype_id and message.author_id and message.model == "res.partner" and message.res_id == message.author_id.id:
|
||||
partners_to_notify |= set([message.author_id])
|
||||
partners_to_notify |= set([message.author_id.id])
|
||||
elif message.author_id:
|
||||
partners_to_notify -= set([message.author_id])
|
||||
partners_to_notify -= set([message.author_id.id])
|
||||
|
||||
# all partner_ids of the mail.message have to be notified regardless of the above (even the author if explicitly added!)
|
||||
if message.partner_ids:
|
||||
partners_to_notify |= set(message.partner_ids)
|
||||
partners_to_notify |= set([p.id for p in message.partner_ids])
|
||||
|
||||
# notify
|
||||
if partners_to_notify:
|
||||
notification_obj._notify(cr, uid, newid, partners_to_notify=[p.id for p in partners_to_notify], context=context,
|
||||
force_send=force_send, user_signature=user_signature)
|
||||
notification_obj._notify(
|
||||
cr, uid, newid, partners_to_notify=list(partners_to_notify), context=context,
|
||||
force_send=force_send, user_signature=user_signature
|
||||
)
|
||||
message.refresh()
|
||||
|
||||
# An error appear when a user receive a notification without notifying
|
||||
|
||||
@@ -56,7 +56,8 @@
|
||||
<field name="priority">25</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Messages Search">
|
||||
<field name="subject" string="Content" filter_domain="['|', ('subject', 'ilike', self), ('body', 'ilike', self)]" />
|
||||
<field name="body" string="Content" filter_domain="['|', ('subject', 'ilike', self), ('body', 'ilike', self)]" />
|
||||
<field name="subject"/>
|
||||
<field name="type"/>
|
||||
<field name="author_id"/>
|
||||
<field name="partner_ids"/>
|
||||
@@ -66,23 +67,13 @@
|
||||
<filter string="To Read"
|
||||
name="message_unread" help="Show messages to read"
|
||||
domain="[('to_read', '=', True)]"/>
|
||||
<filter string="Read"
|
||||
name="message_read" help="Show already read messages"
|
||||
domain="[('to_read', '=', False)]"/>
|
||||
<separator/>
|
||||
<filter string="Comments"
|
||||
name="comments" help="Comments"
|
||||
domain="[('type', '=', 'comment')]"/>
|
||||
<filter string="Notifications"
|
||||
name="notifications" help="Notifications"
|
||||
domain="[('type', '=', 'notification')]"/>
|
||||
<filter string="Emails"
|
||||
name="emails" help="Emails"
|
||||
domain="[('type', '=', 'email')]"/>
|
||||
<separator/>
|
||||
<filter string="Has attachments"
|
||||
name="attachments"
|
||||
domain="[('attachment_ids', '!=', False)]"/>
|
||||
<group expand="0" string="Group By...">
|
||||
<filter string="Type" name="thread" domain="[]" context="{'group_by':'type'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
+38
-34
@@ -874,6 +874,40 @@ class mail_thread(osv.AbstractModel):
|
||||
"No possible route found for incoming message from %s to %s (Message-Id %s:)." \
|
||||
"Create an appropriate mail.alias or force the destination model." % (email_from, email_to, message_id)
|
||||
|
||||
def message_route_process(self, cr, uid, message, message_dict, routes, context=None):
|
||||
# postpone setting message_dict.partner_ids after message_post, to avoid double notifications
|
||||
partner_ids = message_dict.pop('partner_ids', [])
|
||||
thread_id = False
|
||||
for model, thread_id, custom_values, user_id, alias in routes:
|
||||
if self._name == 'mail.thread':
|
||||
context.update({'thread_model': model})
|
||||
if model:
|
||||
model_pool = self.pool[model]
|
||||
assert thread_id and hasattr(model_pool, 'message_update') or hasattr(model_pool, 'message_new'), \
|
||||
"Undeliverable mail with Message-Id %s, model %s does not accept incoming emails" % \
|
||||
(message_dict['message_id'], model)
|
||||
|
||||
# disabled subscriptions during message_new/update to avoid having the system user running the
|
||||
# email gateway become a follower of all inbound messages
|
||||
nosub_ctx = dict(context, mail_create_nosubscribe=True, mail_create_nolog=True)
|
||||
if thread_id and hasattr(model_pool, 'message_update'):
|
||||
model_pool.message_update(cr, user_id, [thread_id], message_dict, context=nosub_ctx)
|
||||
else:
|
||||
thread_id = model_pool.message_new(cr, user_id, message_dict, custom_values, context=nosub_ctx)
|
||||
else:
|
||||
assert thread_id == 0, "Posting a message without model should be with a null res_id, to create a private message."
|
||||
model_pool = self.pool.get('mail.thread')
|
||||
if not hasattr(model_pool, 'message_post'):
|
||||
context['thread_model'] = model
|
||||
model_pool = self.pool['mail.thread']
|
||||
new_msg_id = model_pool.message_post(cr, uid, [thread_id], context=context, subtype='mail.mt_comment', **message_dict)
|
||||
|
||||
if partner_ids:
|
||||
# postponed after message_post, because this is an external message and we don't want to create
|
||||
# duplicate emails due to notifications
|
||||
self.pool.get('mail.message').write(cr, uid, [new_msg_id], {'partner_ids': partner_ids}, context=context)
|
||||
return thread_id
|
||||
|
||||
def message_process(self, cr, uid, model, message, custom_values=None,
|
||||
save_original=False, strip_attachments=False,
|
||||
thread_id=None, context=None):
|
||||
@@ -926,8 +960,7 @@ class mail_thread(osv.AbstractModel):
|
||||
msg = self.message_parse(cr, uid, msg_txt, save_original=save_original, context=context)
|
||||
if strip_attachments:
|
||||
msg.pop('attachments', None)
|
||||
# postpone setting msg.partner_ids after message_post, to avoid double notifications
|
||||
partner_ids = msg.pop('partner_ids', [])
|
||||
|
||||
if msg.get('message_id'): # should always be True as message_parse generate one if missing
|
||||
existing_msg_ids = self.pool.get('mail.message').search(cr, SUPERUSER_ID, [
|
||||
('message_id', '=', msg.get('message_id')),
|
||||
@@ -939,36 +972,7 @@ class mail_thread(osv.AbstractModel):
|
||||
|
||||
# find possible routes for the message
|
||||
routes = self.message_route(cr, uid, msg_txt, msg, model, thread_id, custom_values, context=context)
|
||||
thread_id = False
|
||||
for model, thread_id, custom_values, user_id, alias in routes:
|
||||
if self._name == 'mail.thread':
|
||||
context.update({'thread_model': model})
|
||||
if model:
|
||||
model_pool = self.pool[model]
|
||||
assert thread_id and hasattr(model_pool, 'message_update') or hasattr(model_pool, 'message_new'), \
|
||||
"Undeliverable mail with Message-Id %s, model %s does not accept incoming emails" % \
|
||||
(msg['message_id'], model)
|
||||
|
||||
# disabled subscriptions during message_new/update to avoid having the system user running the
|
||||
# email gateway become a follower of all inbound messages
|
||||
nosub_ctx = dict(context, mail_create_nosubscribe=True, mail_create_nolog=True)
|
||||
if thread_id and hasattr(model_pool, 'message_update'):
|
||||
model_pool.message_update(cr, user_id, [thread_id], msg, context=nosub_ctx)
|
||||
else:
|
||||
thread_id = model_pool.message_new(cr, user_id, msg, custom_values, context=nosub_ctx)
|
||||
else:
|
||||
assert thread_id == 0, "Posting a message without model should be with a null res_id, to create a private message."
|
||||
model_pool = self.pool.get('mail.thread')
|
||||
if not hasattr(model_pool, 'message_post'):
|
||||
context['thread_model'] = model
|
||||
model_pool = self.pool['mail.thread']
|
||||
new_msg_id = model_pool.message_post(cr, uid, [thread_id], context=context, subtype='mail.mt_comment', **msg)
|
||||
|
||||
if partner_ids:
|
||||
# postponed after message_post, because this is an external message and we don't want to create
|
||||
# duplicate emails due to notifications
|
||||
self.pool.get('mail.message').write(cr, uid, [new_msg_id], {'partner_ids': partner_ids}, context=context)
|
||||
|
||||
thread_id = self.message_route_process(cr, uid, msg_txt, msg, routes, context=context)
|
||||
return thread_id
|
||||
|
||||
def message_new(self, cr, uid, msg_dict, custom_values=None, context=None):
|
||||
@@ -1274,8 +1278,8 @@ class mail_thread(osv.AbstractModel):
|
||||
return result
|
||||
|
||||
def message_post(self, cr, uid, thread_id, body='', subject=None, type='notification',
|
||||
subtype=None, parent_id=False, attachments=None, context=None,
|
||||
content_subtype='html', **kwargs):
|
||||
subtype=None, parent_id=False, attachments=None, context=None,
|
||||
content_subtype='html', **kwargs):
|
||||
""" Post a new message in an existing thread, returning the new
|
||||
mail.message ID.
|
||||
|
||||
|
||||
@@ -19,9 +19,10 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from . import test_mail_message, test_mail_features, test_mail_gateway, test_message_read, test_invite
|
||||
from . import test_mail_group, test_mail_message, test_mail_features, test_mail_gateway, test_message_read, test_invite
|
||||
|
||||
checks = [
|
||||
test_mail_group,
|
||||
test_mail_message,
|
||||
test_mail_features,
|
||||
test_mail_gateway,
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
from openerp.tests import common
|
||||
|
||||
|
||||
class TestMailBase(common.TransactionCase):
|
||||
class TestMail(common.TransactionCase):
|
||||
|
||||
def _mock_smtp_gateway(self, *args, **kwargs):
|
||||
return args[2]['Message-Id']
|
||||
@@ -39,7 +39,7 @@ class TestMailBase(common.TransactionCase):
|
||||
return self._build_email(*args, **kwargs)
|
||||
|
||||
def setUp(self):
|
||||
super(TestMailBase, self).setUp()
|
||||
super(TestMail, self).setUp()
|
||||
cr, uid = self.cr, self.uid
|
||||
|
||||
# Install mock SMTP gateway
|
||||
@@ -68,12 +68,46 @@ class TestMailBase(common.TransactionCase):
|
||||
group_employee_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'base', 'group_user')
|
||||
self.group_employee_id = group_employee_ref and group_employee_ref[1] or False
|
||||
|
||||
# Partner Data
|
||||
|
||||
# User Data: employee, noone
|
||||
self.user_employee_id = self.res_users.create(cr, uid, {
|
||||
'name': 'Ernest Employee',
|
||||
'login': 'ernest',
|
||||
'alias_name': 'ernest',
|
||||
'email': 'e.e@example.com',
|
||||
'signature': '--\nErnest',
|
||||
'notification_email_send': 'comment',
|
||||
'groups_id': [(6, 0, [self.group_employee_id])]
|
||||
}, {'no_reset_password': True})
|
||||
self.user_noone_id = self.res_users.create(cr, uid, {
|
||||
'name': 'Noemie NoOne',
|
||||
'login': 'noemie',
|
||||
'alias_name': 'noemie',
|
||||
'email': 'n.n@example.com',
|
||||
'signature': '--\nNoemie',
|
||||
'notification_email_send': 'comment',
|
||||
'groups_id': [(6, 0, [])]
|
||||
}, {'no_reset_password': True})
|
||||
|
||||
# Test users to use through the various tests
|
||||
self.res_users.write(cr, uid, uid, {'name': 'Administrator'})
|
||||
self.user_raoul_id = self.res_users.create(cr, uid,
|
||||
{'name': 'Raoul Grosbedon', 'signature': 'SignRaoul', 'email': 'raoul@raoul.fr', 'login': 'raoul', 'alias_name': 'raoul', 'groups_id': [(6, 0, [self.group_employee_id])]})
|
||||
self.user_bert_id = self.res_users.create(cr, uid,
|
||||
{'name': 'Bert Tartignole', 'signature': 'SignBert', 'email': 'bert@bert.fr', 'login': 'bert', 'alias_name': 'bert', 'groups_id': [(6, 0, [])]})
|
||||
self.user_raoul_id = self.res_users.create(cr, uid, {
|
||||
'name': 'Raoul Grosbedon',
|
||||
'signature': 'SignRaoul',
|
||||
'email': 'raoul@raoul.fr',
|
||||
'login': 'raoul',
|
||||
'alias_name': 'raoul',
|
||||
'groups_id': [(6, 0, [self.group_employee_id])]
|
||||
})
|
||||
self.user_bert_id = self.res_users.create(cr, uid, {
|
||||
'name': 'Bert Tartignole',
|
||||
'signature': 'SignBert',
|
||||
'email': 'bert@bert.fr',
|
||||
'login': 'bert',
|
||||
'alias_name': 'bert',
|
||||
'groups_id': [(6, 0, [])]
|
||||
})
|
||||
self.user_raoul = self.res_users.browse(cr, uid, self.user_raoul_id)
|
||||
self.user_bert = self.res_users.browse(cr, uid, self.user_bert_id)
|
||||
self.user_admin = self.res_users.browse(cr, uid, uid)
|
||||
@@ -82,13 +116,19 @@ class TestMailBase(common.TransactionCase):
|
||||
self.partner_bert_id = self.user_bert.partner_id.id
|
||||
|
||||
# Test 'pigs' group to use through the various tests
|
||||
self.group_pigs_id = self.mail_group.create(cr, uid,
|
||||
self.group_pigs_id = self.mail_group.create(
|
||||
cr, uid,
|
||||
{'name': 'Pigs', 'description': 'Fans of Pigs, unite !', 'alias_name': 'group+pigs'},
|
||||
{'mail_create_nolog': True})
|
||||
{'mail_create_nolog': True}
|
||||
)
|
||||
self.group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
|
||||
# Test mail.group: public to provide access to everyone
|
||||
self.group_jobs_id = self.mail_group.create(cr, uid, {'name': 'Jobs', 'public': 'public'})
|
||||
# Test mail.group: private to restrict access
|
||||
self.group_priv_id = self.mail_group.create(cr, uid, {'name': 'Private', 'public': 'private'})
|
||||
|
||||
def tearDown(self):
|
||||
# Remove mocks
|
||||
self.registry('ir.mail_server').build_email = self._build_email
|
||||
self.registry('ir.mail_server').send_email = self._send_email
|
||||
super(TestMailBase, self).tearDown()
|
||||
super(TestMail, self).tearDown()
|
||||
@@ -19,10 +19,10 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.addons.mail.tests.test_mail_base import TestMailBase
|
||||
from openerp.addons.mail.tests.common import TestMail
|
||||
|
||||
|
||||
class test_invite(TestMailBase):
|
||||
class test_invite(TestMail):
|
||||
|
||||
def test_00_basic_invite(self):
|
||||
cr, uid = self.cr, self.uid
|
||||
|
||||
@@ -21,12 +21,12 @@
|
||||
|
||||
from openerp.addons.mail.mail_mail import mail_mail
|
||||
from openerp.addons.mail.mail_thread import mail_thread
|
||||
from openerp.addons.mail.tests.test_mail_base import TestMailBase
|
||||
from openerp.addons.mail.tests.common import TestMail
|
||||
from openerp.tools import mute_logger, email_split
|
||||
from openerp.tools.mail import html_sanitize
|
||||
|
||||
|
||||
class test_mail(TestMailBase):
|
||||
class test_mail(TestMail):
|
||||
|
||||
def test_000_alias_setup(self):
|
||||
""" Test basic mail.alias setup works, before trying to use them for routing """
|
||||
@@ -631,6 +631,7 @@ class test_mail(TestMailBase):
|
||||
{
|
||||
'subject': _subject,
|
||||
'body': '${object.description}',
|
||||
'post': True,
|
||||
'partner_ids': [(4, p_c_id), (4, p_d_id)],
|
||||
}, context={
|
||||
'default_composition_mode': 'mass_mail',
|
||||
@@ -684,6 +685,7 @@ class test_mail(TestMailBase):
|
||||
{
|
||||
'subject': _subject,
|
||||
'body': '${object.description}',
|
||||
'post': True,
|
||||
'partner_ids': [(4, p_c_id), (4, p_d_id)],
|
||||
}, context={
|
||||
'default_composition_mode': 'mass_mail',
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.addons.mail.tests.test_mail_base import TestMailBase
|
||||
from openerp.addons.mail.tests.common import TestMail
|
||||
from openerp.tools import mute_logger
|
||||
|
||||
MAIL_TEMPLATE = """Return-Path: <whatever-2a840@postmaster.twitter.com>
|
||||
@@ -143,173 +143,9 @@ dGVzdAo=
|
||||
--089e01536c4ed4d17204e49b8e96--"""
|
||||
|
||||
|
||||
class TestMailgateway(TestMailBase):
|
||||
class TestMailgateway(TestMail):
|
||||
|
||||
def test_00_partner_find_from_email(self):
|
||||
""" Tests designed for partner fetch based on emails. """
|
||||
cr, uid, user_raoul, group_pigs = self.cr, self.uid, self.user_raoul, self.group_pigs
|
||||
|
||||
# --------------------------------------------------
|
||||
# Data creation
|
||||
# --------------------------------------------------
|
||||
# 1 - Partner ARaoul
|
||||
p_a_id = self.res_partner.create(cr, uid, {'name': 'ARaoul', 'email': 'test@test.fr'})
|
||||
|
||||
# --------------------------------------------------
|
||||
# CASE1: without object
|
||||
# --------------------------------------------------
|
||||
|
||||
# Do: find partner with email -> first partner should be found
|
||||
partner_info = self.mail_thread.message_partner_info_from_emails(cr, uid, None, ['Maybe Raoul <test@test.fr>'], link_mail=False)[0]
|
||||
self.assertEqual(partner_info['full_name'], 'Maybe Raoul <test@test.fr>',
|
||||
'mail_thread: message_partner_info_from_emails did not handle email')
|
||||
self.assertEqual(partner_info['partner_id'], p_a_id,
|
||||
'mail_thread: message_partner_info_from_emails wrong partner found')
|
||||
|
||||
# Data: add some data about partners
|
||||
# 2 - User BRaoul
|
||||
p_b_id = self.res_partner.create(cr, uid, {'name': 'BRaoul', 'email': 'test@test.fr', 'user_ids': [(4, user_raoul.id)]})
|
||||
|
||||
# Do: find partner with email -> first user should be found
|
||||
partner_info = self.mail_thread.message_partner_info_from_emails(cr, uid, None, ['Maybe Raoul <test@test.fr>'], link_mail=False)[0]
|
||||
self.assertEqual(partner_info['partner_id'], p_b_id,
|
||||
'mail_thread: message_partner_info_from_emails wrong partner found')
|
||||
|
||||
# --------------------------------------------------
|
||||
# CASE1: with object
|
||||
# --------------------------------------------------
|
||||
|
||||
# Do: find partner in group where there is a follower with the email -> should be taken
|
||||
self.mail_group.message_subscribe(cr, uid, [group_pigs.id], [p_b_id])
|
||||
partner_info = self.mail_group.message_partner_info_from_emails(cr, uid, group_pigs.id, ['Maybe Raoul <test@test.fr>'], link_mail=False)[0]
|
||||
self.assertEqual(partner_info['partner_id'], p_b_id,
|
||||
'mail_thread: message_partner_info_from_emails wrong partner found')
|
||||
|
||||
def test_05_mail_message_mail_mail(self):
|
||||
""" Tests designed for testing email values based on mail.message, aliases, ... """
|
||||
cr, uid, user_raoul_id = self.cr, self.uid, self.user_raoul_id
|
||||
|
||||
# Data: update + generic variables
|
||||
reply_to1 = '_reply_to1@example.com'
|
||||
reply_to2 = '_reply_to2@example.com'
|
||||
email_from1 = 'from@example.com'
|
||||
alias_domain = 'schlouby.fr'
|
||||
raoul_from = 'Raoul Grosbedon <raoul@raoul.fr>'
|
||||
raoul_from_alias = 'Raoul Grosbedon <raoul@schlouby.fr>'
|
||||
raoul_reply = '"Followers of Pigs" <raoul@raoul.fr>'
|
||||
raoul_reply_alias = '"Followers of Pigs" <group+pigs@schlouby.fr>'
|
||||
# Data: remove alias_domain to see emails with alias
|
||||
param_ids = self.registry('ir.config_parameter').search(cr, uid, [('key', '=', 'mail.catchall.domain')])
|
||||
self.registry('ir.config_parameter').unlink(cr, uid, param_ids)
|
||||
|
||||
# Do: free message; specified values > default values
|
||||
msg_id = self.mail_message.create(cr, user_raoul_id, {'reply_to': reply_to1, 'email_from': email_from1})
|
||||
msg = self.mail_message.browse(cr, user_raoul_id, msg_id)
|
||||
# Test: message content
|
||||
self.assertIn('reply_to', msg.message_id,
|
||||
'mail_message: message_id should be specific to a mail_message with a given reply_to')
|
||||
self.assertEqual(msg.reply_to, reply_to1,
|
||||
'mail_message: incorrect reply_to: should come from values')
|
||||
self.assertEqual(msg.email_from, email_from1,
|
||||
'mail_message: incorrect email_from: should come from values')
|
||||
# Do: create a mail_mail with the previous mail_message
|
||||
mail_id = self.mail_mail.create(cr, user_raoul_id, {'mail_message_id': msg_id, 'state': 'cancel'})
|
||||
mail = self.mail_mail.browse(cr, user_raoul_id, mail_id)
|
||||
# Test: mail_mail content
|
||||
self.assertEqual(mail.reply_to, reply_to1,
|
||||
'mail_mail: incorrect reply_to: should come from mail.message')
|
||||
self.assertEqual(mail.email_from, email_from1,
|
||||
'mail_mail: incorrect email_from: should come from mail.message')
|
||||
# Do: create a mail_mail with the previous mail_message + specified reply_to
|
||||
mail_id = self.mail_mail.create(cr, user_raoul_id, {'mail_message_id': msg_id, 'state': 'cancel', 'reply_to': reply_to2})
|
||||
mail = self.mail_mail.browse(cr, user_raoul_id, mail_id)
|
||||
# Test: mail_mail content
|
||||
self.assertEqual(mail.reply_to, reply_to2,
|
||||
'mail_mail: incorrect reply_to: should come from values')
|
||||
self.assertEqual(mail.email_from, email_from1,
|
||||
'mail_mail: incorrect email_from: should come from mail.message')
|
||||
|
||||
# Do: mail_message attached to a document
|
||||
msg_id = self.mail_message.create(cr, user_raoul_id, {'model': 'mail.group', 'res_id': self.group_pigs_id})
|
||||
msg = self.mail_message.browse(cr, user_raoul_id, msg_id)
|
||||
# Test: message content
|
||||
self.assertIn('mail.group', msg.message_id,
|
||||
'mail_message: message_id should contain model')
|
||||
self.assertIn('%s' % self.group_pigs_id, msg.message_id,
|
||||
'mail_message: message_id should contain res_id')
|
||||
self.assertFalse(msg.reply_to,
|
||||
'mail_message: incorrect reply_to: should not be generated if not specified')
|
||||
self.assertEqual(msg.email_from, raoul_from,
|
||||
'mail_message: incorrect email_from: should be Raoul')
|
||||
# Do: create a mail_mail based on the previous mail_message
|
||||
mail_id = self.mail_mail.create(cr, user_raoul_id, {'mail_message_id': msg_id, 'state': 'cancel'})
|
||||
mail = self.mail_mail.browse(cr, user_raoul_id, mail_id)
|
||||
# Test: mail_mail content
|
||||
self.assertEqual(mail.reply_to, raoul_reply,
|
||||
'mail_mail: incorrect reply_to: should be Raoul')
|
||||
|
||||
# Data: set catchall domain
|
||||
self.registry('ir.config_parameter').set_param(cr, uid, 'mail.catchall.domain', alias_domain)
|
||||
self.registry('ir.config_parameter').unlink(cr, uid, self.registry('ir.config_parameter').search(cr, uid, [('key', '=', 'mail.catchall.alias')]))
|
||||
|
||||
# Update message
|
||||
self.mail_message.write(cr, user_raoul_id, [msg_id], {'email_from': False, 'reply_to': False})
|
||||
msg.refresh()
|
||||
# Do: create a mail_mail based on the previous mail_message
|
||||
mail_id = self.mail_mail.create(cr, user_raoul_id, {'mail_message_id': msg_id, 'state': 'cancel'})
|
||||
mail = self.mail_mail.browse(cr, user_raoul_id, mail_id)
|
||||
# Test: mail_mail content
|
||||
self.assertEqual(mail.reply_to, raoul_reply_alias,
|
||||
'mail_mail: incorrect reply_to: should be Pigs alias')
|
||||
|
||||
# Update message: test alias on email_from
|
||||
msg_id = self.mail_message.create(cr, user_raoul_id, {})
|
||||
msg = self.mail_message.browse(cr, user_raoul_id, msg_id)
|
||||
# Do: create a mail_mail based on the previous mail_message
|
||||
mail_id = self.mail_mail.create(cr, user_raoul_id, {'mail_message_id': msg_id, 'state': 'cancel'})
|
||||
mail = self.mail_mail.browse(cr, user_raoul_id, mail_id)
|
||||
# Test: mail_mail content
|
||||
self.assertEqual(mail.reply_to, raoul_from_alias,
|
||||
'mail_mail: incorrect reply_to: should be message email_from using Raoul alias')
|
||||
|
||||
# Update message
|
||||
self.mail_message.write(cr, user_raoul_id, [msg_id], {'res_id': False, 'email_from': 'someone@schlouby.fr', 'reply_to': False})
|
||||
msg.refresh()
|
||||
# Do: create a mail_mail based on the previous mail_message
|
||||
mail_id = self.mail_mail.create(cr, user_raoul_id, {'mail_message_id': msg_id, 'state': 'cancel'})
|
||||
mail = self.mail_mail.browse(cr, user_raoul_id, mail_id)
|
||||
# Test: mail_mail content
|
||||
self.assertEqual(mail.reply_to, msg.email_from,
|
||||
'mail_mail: incorrect reply_to: should be message email_from')
|
||||
|
||||
# Data: set catchall alias
|
||||
self.registry('ir.config_parameter').set_param(self.cr, self.uid, 'mail.catchall.alias', 'gateway')
|
||||
|
||||
# Update message
|
||||
self.mail_message.write(cr, uid, [msg_id], {'email_from': False, 'reply_to': False})
|
||||
msg.refresh()
|
||||
# Do: create a mail_mail based on the previous mail_message
|
||||
mail_id = self.mail_mail.create(cr, uid, {'mail_message_id': msg_id, 'state': 'cancel'})
|
||||
mail = self.mail_mail.browse(cr, uid, mail_id)
|
||||
# Test: mail_mail Content-Type
|
||||
self.assertEqual(mail.reply_to, 'gateway@schlouby.fr',
|
||||
'mail_mail: reply_to should equal the catchall email alias')
|
||||
|
||||
# Do: create a mail_mail
|
||||
mail_id = self.mail_mail.create(cr, uid, {'state': 'cancel'})
|
||||
mail = self.mail_mail.browse(cr, uid, mail_id)
|
||||
# Test: mail_mail content
|
||||
self.assertEqual(mail.reply_to, 'gateway@schlouby.fr',
|
||||
'mail_mail: reply_to should equal the catchall email alias')
|
||||
|
||||
# Do: create a mail_mail
|
||||
mail_id = self.mail_mail.create(cr, uid, {'state': 'cancel', 'reply_to': 'someone@example.com'})
|
||||
mail = self.mail_mail.browse(cr, uid, mail_id)
|
||||
# Test: mail_mail content
|
||||
self.assertEqual(mail.reply_to, 'someone@example.com',
|
||||
'mail_mail: reply_to should equal the rpely_to given to create')
|
||||
|
||||
def test_09_message_parse(self):
|
||||
def test_00_message_parse(self):
|
||||
""" Testing incoming emails parsing """
|
||||
cr, uid = self.cr, self.uid
|
||||
|
||||
@@ -738,9 +574,7 @@ class TestMailgateway(TestMailBase):
|
||||
'message_post: private discussion: incorrect notified recipients')
|
||||
self.assertEqual(msg.model, False,
|
||||
'message_post: private discussion: context key "thread_model" not correctly ignored when having no res_id')
|
||||
# Test: message reply_to and message-id
|
||||
self.assertFalse(msg.reply_to,
|
||||
'message_post: private discussion: initial message should not have any reply_to specified')
|
||||
# Test: message-id
|
||||
self.assertIn('openerp-private', msg.message_id,
|
||||
'message_post: private discussion: message-id should contain the private keyword')
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Business Applications
|
||||
# Copyright (c) 2012-TODAY OpenERP S.A. <http://openerp.com>
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.addons.mail.tests.common import TestMail
|
||||
from openerp.osv.orm import except_orm
|
||||
from openerp.tools import mute_logger
|
||||
|
||||
|
||||
class TestMailGroup(TestMail):
|
||||
|
||||
@mute_logger('openerp.addons.base.ir.ir_model', 'openerp.osv.orm')
|
||||
def test_00_mail_group_access_rights(self):
|
||||
""" Testing mail_group access rights and basic mail_thread features """
|
||||
cr, uid, user_noone_id, user_employee_id = self.cr, self.uid, self.user_noone_id, self.user_employee_id
|
||||
|
||||
# Do: Bert reads Jobs -> ok, public
|
||||
self.mail_group.read(cr, user_noone_id, [self.group_jobs_id])
|
||||
# Do: Bert read Pigs -> ko, restricted to employees
|
||||
with self.assertRaises(except_orm):
|
||||
self.mail_group.read(cr, user_noone_id, [self.group_pigs_id])
|
||||
# Do: Raoul read Pigs -> ok, belong to employees
|
||||
self.mail_group.read(cr, user_employee_id, [self.group_pigs_id])
|
||||
|
||||
# Do: Bert creates a group -> ko, no access rights
|
||||
with self.assertRaises(except_orm):
|
||||
self.mail_group.create(cr, user_noone_id, {'name': 'Test'})
|
||||
# Do: Raoul creates a restricted group -> ok
|
||||
new_group_id = self.mail_group.create(cr, user_employee_id, {'name': 'Test'})
|
||||
# Do: Bert added in followers, read -> ok, in followers
|
||||
self.mail_group.message_subscribe_users(cr, uid, [new_group_id], [user_noone_id])
|
||||
self.mail_group.read(cr, user_noone_id, [new_group_id])
|
||||
|
||||
# Do: Raoul reads Priv -> ko, private
|
||||
with self.assertRaises(except_orm):
|
||||
self.mail_group.read(cr, user_employee_id, [self.group_priv_id])
|
||||
# Do: Raoul added in follower, read -> ok, in followers
|
||||
self.mail_group.message_subscribe_users(cr, uid, [self.group_priv_id], [user_employee_id])
|
||||
self.mail_group.read(cr, user_employee_id, [self.group_priv_id])
|
||||
|
||||
# Do: Raoul write on Jobs -> ok
|
||||
self.mail_group.write(cr, user_employee_id, [self.group_priv_id], {'name': 'modified'})
|
||||
# Do: Bert cannot write on Private -> ko (read but no write)
|
||||
with self.assertRaises(except_orm):
|
||||
self.mail_group.write(cr, user_noone_id, [self.group_priv_id], {'name': 're-modified'})
|
||||
# Test: Bert cannot unlink the group
|
||||
with self.assertRaises(except_orm):
|
||||
self.mail_group.unlink(cr, user_noone_id, [self.group_priv_id])
|
||||
# Do: Raoul unlinks the group, there are no followers and messages left
|
||||
self.mail_group.unlink(cr, user_employee_id, [self.group_priv_id])
|
||||
fol_ids = self.mail_followers.search(cr, uid, [('res_model', '=', 'mail.group'), ('res_id', '=', self.group_priv_id)])
|
||||
self.assertFalse(fol_ids, 'unlinked document should not have any followers left')
|
||||
msg_ids = self.mail_message.search(cr, uid, [('model', '=', 'mail.group'), ('res_id', '=', self.group_priv_id)])
|
||||
self.assertFalse(msg_ids, 'unlinked document should not have any followers left')
|
||||
@@ -19,66 +19,147 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.addons.mail.tests.test_mail_base import TestMailBase
|
||||
from openerp.addons.mail.tests.common import TestMail
|
||||
from openerp.osv.orm import except_orm
|
||||
from openerp.tools import mute_logger
|
||||
|
||||
|
||||
class test_mail_access_rights(TestMailBase):
|
||||
class TestMailMail(TestMail):
|
||||
|
||||
def setUp(self):
|
||||
super(test_mail_access_rights, self).setUp()
|
||||
cr, uid = self.cr, self.uid
|
||||
def test_00_partner_find_from_email(self):
|
||||
""" Tests designed for partner fetch based on emails. """
|
||||
cr, uid, user_raoul, group_pigs = self.cr, self.uid, self.user_raoul, self.group_pigs
|
||||
|
||||
# Test mail.group: public to provide access to everyone
|
||||
self.group_jobs_id = self.mail_group.create(cr, uid, {'name': 'Jobs', 'public': 'public'})
|
||||
# Test mail.group: private to restrict access
|
||||
self.group_priv_id = self.mail_group.create(cr, uid, {'name': 'Private', 'public': 'private'})
|
||||
# --------------------------------------------------
|
||||
# Data creation
|
||||
# --------------------------------------------------
|
||||
# 1 - Partner ARaoul
|
||||
p_a_id = self.res_partner.create(cr, uid, {'name': 'ARaoul', 'email': 'test@test.fr'})
|
||||
|
||||
@mute_logger('openerp.addons.base.ir.ir_model', 'openerp.osv.orm')
|
||||
def test_00_mail_group_access_rights(self):
|
||||
""" Testing mail_group access rights and basic mail_thread features """
|
||||
cr, uid, user_bert_id, user_raoul_id = self.cr, self.uid, self.user_bert_id, self.user_raoul_id
|
||||
# --------------------------------------------------
|
||||
# CASE1: without object
|
||||
# --------------------------------------------------
|
||||
|
||||
# Do: Bert reads Jobs -> ok, public
|
||||
self.mail_group.read(cr, user_bert_id, [self.group_jobs_id])
|
||||
# Do: Bert read Pigs -> ko, restricted to employees
|
||||
self.assertRaises(except_orm, self.mail_group.read,
|
||||
cr, user_bert_id, [self.group_pigs_id])
|
||||
# Do: Raoul read Pigs -> ok, belong to employees
|
||||
self.mail_group.read(cr, user_raoul_id, [self.group_pigs_id])
|
||||
# Do: find partner with email -> first partner should be found
|
||||
partner_info = self.mail_thread.message_partner_info_from_emails(cr, uid, None, ['Maybe Raoul <test@test.fr>'], link_mail=False)[0]
|
||||
self.assertEqual(partner_info['full_name'], 'Maybe Raoul <test@test.fr>',
|
||||
'mail_thread: message_partner_info_from_emails did not handle email')
|
||||
self.assertEqual(partner_info['partner_id'], p_a_id,
|
||||
'mail_thread: message_partner_info_from_emails wrong partner found')
|
||||
|
||||
# Do: Bert creates a group -> ko, no access rights
|
||||
self.assertRaises(except_orm, self.mail_group.create,
|
||||
cr, user_bert_id, {'name': 'Test'})
|
||||
# Do: Raoul creates a restricted group -> ok
|
||||
new_group_id = self.mail_group.create(cr, user_raoul_id, {'name': 'Test'})
|
||||
# Do: Bert added in followers, read -> ok, in followers
|
||||
self.mail_group.message_subscribe_users(cr, uid, [new_group_id], [user_bert_id])
|
||||
self.mail_group.read(cr, user_bert_id, [new_group_id])
|
||||
# Data: add some data about partners
|
||||
# 2 - User BRaoul
|
||||
p_b_id = self.res_partner.create(cr, uid, {'name': 'BRaoul', 'email': 'test@test.fr', 'user_ids': [(4, user_raoul.id)]})
|
||||
|
||||
# Do: Raoul reads Priv -> ko, private
|
||||
self.assertRaises(except_orm, self.mail_group.read,
|
||||
cr, user_raoul_id, [self.group_priv_id])
|
||||
# Do: Raoul added in follower, read -> ok, in followers
|
||||
self.mail_group.message_subscribe_users(cr, uid, [self.group_priv_id], [user_raoul_id])
|
||||
self.mail_group.read(cr, user_raoul_id, [self.group_priv_id])
|
||||
# Do: find partner with email -> first user should be found
|
||||
partner_info = self.mail_thread.message_partner_info_from_emails(cr, uid, None, ['Maybe Raoul <test@test.fr>'], link_mail=False)[0]
|
||||
self.assertEqual(partner_info['partner_id'], p_b_id,
|
||||
'mail_thread: message_partner_info_from_emails wrong partner found')
|
||||
|
||||
# Do: Raoul write on Jobs -> ok
|
||||
self.mail_group.write(cr, user_raoul_id, [self.group_priv_id], {'name': 'modified'})
|
||||
# Do: Bert cannot write on Private -> ko (read but no write)
|
||||
self.assertRaises(except_orm, self.mail_group.write,
|
||||
cr, user_bert_id, [self.group_priv_id], {'name': 're-modified'})
|
||||
# Test: Bert cannot unlink the group
|
||||
self.assertRaises(except_orm,
|
||||
self.mail_group.unlink,
|
||||
cr, user_bert_id, [self.group_priv_id])
|
||||
# Do: Raoul unlinks the group, there are no followers and messages left
|
||||
self.mail_group.unlink(cr, user_raoul_id, [self.group_priv_id])
|
||||
fol_ids = self.mail_followers.search(cr, uid, [('res_model', '=', 'mail.group'), ('res_id', '=', self.group_priv_id)])
|
||||
self.assertFalse(fol_ids, 'unlinked document should not have any followers left')
|
||||
msg_ids = self.mail_message.search(cr, uid, [('model', '=', 'mail.group'), ('res_id', '=', self.group_priv_id)])
|
||||
self.assertFalse(msg_ids, 'unlinked document should not have any followers left')
|
||||
# --------------------------------------------------
|
||||
# CASE1: with object
|
||||
# --------------------------------------------------
|
||||
|
||||
# Do: find partner in group where there is a follower with the email -> should be taken
|
||||
self.mail_group.message_subscribe(cr, uid, [group_pigs.id], [p_b_id])
|
||||
partner_info = self.mail_group.message_partner_info_from_emails(cr, uid, group_pigs.id, ['Maybe Raoul <test@test.fr>'], link_mail=False)[0]
|
||||
self.assertEqual(partner_info['partner_id'], p_b_id,
|
||||
'mail_thread: message_partner_info_from_emails wrong partner found')
|
||||
|
||||
|
||||
class TestMailMessage(TestMail):
|
||||
|
||||
def test_00_mail_message_values(self):
|
||||
""" Tests designed for testing email values based on mail.message, aliases, ... """
|
||||
cr, uid, user_raoul_id = self.cr, self.uid, self.user_raoul_id
|
||||
|
||||
# Data: update + generic variables
|
||||
reply_to1 = '_reply_to1@example.com'
|
||||
reply_to2 = '_reply_to2@example.com'
|
||||
email_from1 = 'from@example.com'
|
||||
alias_domain = 'schlouby.fr'
|
||||
raoul_from = 'Raoul Grosbedon <raoul@raoul.fr>'
|
||||
raoul_from_alias = 'Raoul Grosbedon <raoul@schlouby.fr>'
|
||||
raoul_reply = '"Followers of Pigs" <raoul@raoul.fr>'
|
||||
raoul_reply_alias = '"Followers of Pigs" <group+pigs@schlouby.fr>'
|
||||
|
||||
# --------------------------------------------------
|
||||
# Case1: without alias_domain
|
||||
# --------------------------------------------------
|
||||
param_ids = self.registry('ir.config_parameter').search(cr, uid, [('key', '=', 'mail.catchall.domain')])
|
||||
self.registry('ir.config_parameter').unlink(cr, uid, param_ids)
|
||||
|
||||
# Do: free message; specified values > default values
|
||||
msg_id = self.mail_message.create(cr, user_raoul_id, {'reply_to': reply_to1, 'email_from': email_from1})
|
||||
msg = self.mail_message.browse(cr, user_raoul_id, msg_id)
|
||||
# Test: message content
|
||||
self.assertIn('reply_to', msg.message_id,
|
||||
'mail_message: message_id should be specific to a mail_message with a given reply_to')
|
||||
self.assertEqual(msg.reply_to, reply_to1,
|
||||
'mail_message: incorrect reply_to: should come from values')
|
||||
self.assertEqual(msg.email_from, email_from1,
|
||||
'mail_message: incorrect email_from: should come from values')
|
||||
|
||||
# Do: create a mail_mail with the previous mail_message + specified reply_to
|
||||
mail_id = self.mail_mail.create(cr, user_raoul_id, {'mail_message_id': msg_id, 'state': 'cancel', 'reply_to': reply_to2})
|
||||
mail = self.mail_mail.browse(cr, user_raoul_id, mail_id)
|
||||
# Test: mail_mail content
|
||||
self.assertEqual(mail.reply_to, reply_to2,
|
||||
'mail_mail: incorrect reply_to: should come from values')
|
||||
self.assertEqual(mail.email_from, email_from1,
|
||||
'mail_mail: incorrect email_from: should come from mail.message')
|
||||
|
||||
# Do: mail_message attached to a document
|
||||
msg_id = self.mail_message.create(cr, user_raoul_id, {'model': 'mail.group', 'res_id': self.group_pigs_id})
|
||||
msg = self.mail_message.browse(cr, user_raoul_id, msg_id)
|
||||
# Test: message content
|
||||
self.assertIn('mail.group', msg.message_id,
|
||||
'mail_message: message_id should contain model')
|
||||
self.assertIn('%s' % self.group_pigs_id, msg.message_id,
|
||||
'mail_message: message_id should contain res_id')
|
||||
self.assertEqual(msg.reply_to, raoul_reply,
|
||||
'mail_message: incorrect reply_to: should be Raoul')
|
||||
self.assertEqual(msg.email_from, raoul_from,
|
||||
'mail_message: incorrect email_from: should be Raoul')
|
||||
|
||||
# --------------------------------------------------
|
||||
# Case2: with alias_domain, without catchall alias
|
||||
# --------------------------------------------------
|
||||
self.registry('ir.config_parameter').set_param(cr, uid, 'mail.catchall.domain', alias_domain)
|
||||
self.registry('ir.config_parameter').unlink(cr, uid, self.registry('ir.config_parameter').search(cr, uid, [('key', '=', 'mail.catchall.alias')]))
|
||||
|
||||
# Update message
|
||||
msg_id = self.mail_message.create(cr, user_raoul_id, {'model': 'mail.group', 'res_id': self.group_pigs_id})
|
||||
msg = self.mail_message.browse(cr, user_raoul_id, msg_id)
|
||||
# Test: generated reply_to
|
||||
self.assertEqual(msg.reply_to, raoul_reply_alias,
|
||||
'mail_mail: incorrect reply_to: should be Pigs alias')
|
||||
|
||||
# Update message: test alias on email_from
|
||||
msg_id = self.mail_message.create(cr, user_raoul_id, {})
|
||||
msg = self.mail_message.browse(cr, user_raoul_id, msg_id)
|
||||
# Test: generated reply_to
|
||||
self.assertEqual(msg.reply_to, raoul_from_alias,
|
||||
'mail_mail: incorrect reply_to: should be message email_from using Raoul alias')
|
||||
|
||||
# --------------------------------------------------
|
||||
# Case2: with alias_domain and catchall alias
|
||||
# --------------------------------------------------
|
||||
self.registry('ir.config_parameter').set_param(self.cr, self.uid, 'mail.catchall.alias', 'gateway')
|
||||
|
||||
# Update message
|
||||
msg_id = self.mail_message.create(cr, user_raoul_id, {})
|
||||
msg = self.mail_message.browse(cr, user_raoul_id, msg_id)
|
||||
# Test: generated reply_to
|
||||
self.assertEqual(msg.reply_to, 'gateway@schlouby.fr',
|
||||
'mail_mail: reply_to should equal the catchall email alias')
|
||||
|
||||
# Do: create a mail_mail
|
||||
mail_id = self.mail_mail.create(cr, uid, {'state': 'cancel', 'reply_to': 'someone@example.com'})
|
||||
mail = self.mail_mail.browse(cr, uid, mail_id)
|
||||
# Test: mail_mail content
|
||||
self.assertEqual(mail.reply_to, 'someone@example.com',
|
||||
'mail_mail: reply_to should equal the rpely_to given to create')
|
||||
|
||||
@mute_logger('openerp.addons.base.ir.ir_model', 'openerp.osv.orm')
|
||||
def test_10_mail_message_search_access_rights(self):
|
||||
|
||||
@@ -19,10 +19,10 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.addons.mail.tests.test_mail_base import TestMailBase
|
||||
from openerp.addons.mail.tests.common import TestMail
|
||||
|
||||
|
||||
class test_mail_access_rights(TestMailBase):
|
||||
class test_mail_access_rights(TestMail):
|
||||
|
||||
def test_00_message_read(self):
|
||||
""" Tests for message_read and expandables. """
|
||||
|
||||
@@ -75,7 +75,7 @@ class mail_compose_message(osv.TransientModel):
|
||||
if 'active_domain' in context: # not context.get() because we want to keep global [] domains
|
||||
result['use_active_domain'] = True
|
||||
result['active_domain'] = '%s' % context.get('active_domain')
|
||||
else:
|
||||
elif not result.get('active_domain'):
|
||||
result['active_domain'] = ''
|
||||
# get default values according to the composition mode
|
||||
if composition_mode == 'reply':
|
||||
@@ -134,8 +134,9 @@ class mail_compose_message(osv.TransientModel):
|
||||
'body': lambda self, cr, uid, ctx={}: '',
|
||||
'subject': lambda self, cr, uid, ctx={}: False,
|
||||
'partner_ids': lambda self, cr, uid, ctx={}: [],
|
||||
'post': lambda self, cr, uid, ctx={}: True,
|
||||
'same_thread': lambda self, cr, uid, ctx={}: True,
|
||||
'post': False,
|
||||
'notify': False,
|
||||
'same_thread': True,
|
||||
}
|
||||
|
||||
def check_access_rule(self, cr, uid, ids, operation, context=None):
|
||||
@@ -232,7 +233,11 @@ class mail_compose_message(osv.TransientModel):
|
||||
email(s), rendering any template patterns on the fly if needed. """
|
||||
if context is None:
|
||||
context = {}
|
||||
ir_attachment_obj = self.pool.get('ir.attachment')
|
||||
# clean the context (hint: mass mailing sets some default values that
|
||||
# could be wrongly interpreted by mail_mail)
|
||||
context.pop('default_email_to', None)
|
||||
context.pop('default_partner_ids', None)
|
||||
|
||||
active_ids = context.get('active_ids')
|
||||
is_log = context.get('mail_compose_log', False)
|
||||
|
||||
@@ -251,43 +256,11 @@ class mail_compose_message(osv.TransientModel):
|
||||
else:
|
||||
res_ids = [wizard.res_id]
|
||||
|
||||
for res_id in res_ids:
|
||||
# mail.message values, according to the wizard options
|
||||
post_values = {
|
||||
'subject': wizard.subject,
|
||||
'body': wizard.body,
|
||||
'parent_id': wizard.parent_id and wizard.parent_id.id,
|
||||
'partner_ids': [partner.id for partner in wizard.partner_ids],
|
||||
'attachment_ids': [attach.id for attach in wizard.attachment_ids],
|
||||
}
|
||||
# mass mailing: render and override default values
|
||||
if mass_mail_mode and wizard.model:
|
||||
email_dict = self.render_message(cr, uid, wizard, res_id, context=context)
|
||||
post_values['partner_ids'] += email_dict.pop('partner_ids', [])
|
||||
post_values['attachments'] = email_dict.pop('attachments', [])
|
||||
attachment_ids = []
|
||||
for attach_id in post_values.pop('attachment_ids'):
|
||||
new_attach_id = ir_attachment_obj.copy(cr, uid, attach_id, {'res_model': self._name, 'res_id': wizard.id}, context=context)
|
||||
attachment_ids.append(new_attach_id)
|
||||
post_values['attachment_ids'] = attachment_ids
|
||||
# email_from: mass mailing only can specify another email_from
|
||||
if email_dict.get('email_from'):
|
||||
post_values['email_from'] = email_dict.pop('email_from')
|
||||
# replies redirection: mass mailing only
|
||||
if not wizard.same_thread:
|
||||
post_values['reply_to'] = email_dict.pop('reply_to')
|
||||
else:
|
||||
email_dict.pop('reply_to')
|
||||
post_values.update(email_dict)
|
||||
# clean the context (hint: mass mailing sets some default values that
|
||||
# could be wrongly interpreted by mail_mail)
|
||||
context.pop('default_email_to', None)
|
||||
context.pop('default_partner_ids', None)
|
||||
# post the message
|
||||
all_mail_values = self.get_mail_values(cr, uid, wizard, res_ids, context=context)
|
||||
|
||||
for res_id, mail_values in all_mail_values.iteritems():
|
||||
if mass_mail_mode and not wizard.post:
|
||||
post_values['body_html'] = post_values.get('body', '')
|
||||
post_values['recipient_ids'] = [(4, id) for id in post_values.pop('partner_ids', [])]
|
||||
self.pool.get('mail.mail').create(cr, uid, post_values, context=context)
|
||||
self.pool.get('mail.mail').create(cr, uid, mail_values, context=context)
|
||||
else:
|
||||
subtype = 'mail.mt_comment'
|
||||
if is_log: # log a note: subtype is False
|
||||
@@ -296,46 +269,122 @@ class mail_compose_message(osv.TransientModel):
|
||||
if not wizard.notify:
|
||||
subtype = False
|
||||
context = dict(context,
|
||||
mail_notify_force_send=False, # do not send emails directly but use the queue instead
|
||||
mail_create_nosubscribe=True) # add context key to avoid subscribing the author
|
||||
active_model_pool.message_post(cr, uid, [res_id], type='comment', subtype=subtype, context=context, **post_values)
|
||||
mail_notify_force_send=False, # do not send emails directly but use the queue instead
|
||||
mail_create_nosubscribe=True) # add context key to avoid subscribing the author
|
||||
active_model_pool.message_post(cr, uid, [res_id], type='comment', subtype=subtype, context=context, **mail_values)
|
||||
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
def render_message(self, cr, uid, wizard, res_id, context=None):
|
||||
""" Generate an email from the template for given (wizard.model, res_id)
|
||||
pair. This method is meant to be inherited by email_template that
|
||||
will produce a more complete dictionary. """
|
||||
return {
|
||||
'subject': self.render_template(cr, uid, wizard.subject, wizard.model, res_id, context),
|
||||
'body': self.render_template(cr, uid, wizard.body, wizard.model, res_id, context),
|
||||
'email_from': self.render_template(cr, uid, wizard.email_from, wizard.model, res_id, context),
|
||||
'reply_to': self.render_template(cr, uid, wizard.reply_to, wizard.model, res_id, context),
|
||||
}
|
||||
def get_mail_values(self, cr, uid, wizard, res_ids, context=None):
|
||||
"""Generate the values that will be used by send_mail to create mail_messages
|
||||
or mail_mails. """
|
||||
results = dict.fromkeys(res_ids, False)
|
||||
mass_mail_mode = wizard.composition_mode == 'mass_mail'
|
||||
|
||||
def render_template(self, cr, uid, template, model, res_id, context=None):
|
||||
# render all template-based value at once
|
||||
if mass_mail_mode and wizard.model:
|
||||
rendered_values = self.render_message_batch(cr, uid, wizard, res_ids, context=context)
|
||||
|
||||
for res_id in res_ids:
|
||||
# static wizard (mail.message) values
|
||||
mail_values = {
|
||||
'subject': wizard.subject,
|
||||
'body': wizard.body,
|
||||
'parent_id': wizard.parent_id and wizard.parent_id.id,
|
||||
'partner_ids': [partner.id for partner in wizard.partner_ids],
|
||||
'attachment_ids': [attach.id for attach in wizard.attachment_ids],
|
||||
}
|
||||
# mass mailing: rendering override wizard static values
|
||||
if mass_mail_mode and wizard.model:
|
||||
email_dict = rendered_values[res_id]
|
||||
mail_values['partner_ids'] += email_dict.pop('partner_ids', [])
|
||||
mail_values['attachments'] = email_dict.pop('attachments', [])
|
||||
attachment_ids = []
|
||||
for attach_id in mail_values.pop('attachment_ids'):
|
||||
new_attach_id = self.pool.get('ir.attachment').copy(cr, uid, attach_id, {'res_model': self._name, 'res_id': wizard.id}, context=context)
|
||||
attachment_ids.append(new_attach_id)
|
||||
mail_values['attachment_ids'] = attachment_ids
|
||||
# email_from: mass mailing only can specify another email_from
|
||||
if email_dict.get('email_from'):
|
||||
mail_values['email_from'] = email_dict.pop('email_from')
|
||||
# replies redirection: mass mailing only
|
||||
if not wizard.same_thread:
|
||||
mail_values['reply_to'] = email_dict.pop('reply_to')
|
||||
else:
|
||||
email_dict.pop('reply_to')
|
||||
mail_values.update(email_dict)
|
||||
# mass mailing without post: mail_mail values
|
||||
if mass_mail_mode and not wizard.post:
|
||||
if 'mail_auto_delete' in context:
|
||||
mail_values['auto_delete'] = context.get('mail_auto_delete')
|
||||
mail_values['body_html'] = mail_values.get('body', '')
|
||||
mail_values['recipient_ids'] = [(4, id) for id in mail_values.pop('partner_ids', [])]
|
||||
results[res_id] = mail_values
|
||||
return results
|
||||
|
||||
def render_message_batch(self, cr, uid, wizard, res_ids, context=None):
|
||||
"""Generate template-based values of wizard, for the document records given
|
||||
by res_ids. This method is meant to be inherited by email_template that
|
||||
will produce a more complete dictionary, using Jinja2 templates.
|
||||
|
||||
Each template is generated for all res_ids, allowing to parse the template
|
||||
once, and render it multiple times. This is useful for mass mailing where
|
||||
template rendering represent a significant part of the process.
|
||||
|
||||
:param browse wizard: current mail.compose.message browse record
|
||||
:param list res_ids: list of record ids
|
||||
|
||||
:return dict results: for each res_id, the generated template values for
|
||||
subject, body, email_from and reply_to
|
||||
"""
|
||||
subjects = self.render_template_batch(cr, uid, wizard.subject, wizard.model, res_ids, context)
|
||||
bodies = self.render_template_batch(cr, uid, wizard.body, wizard.model, res_ids, context)
|
||||
emails_from = self.render_template_batch(cr, uid, wizard.email_from, wizard.model, res_ids, context)
|
||||
replies_to = self.render_template_batch(cr, uid, wizard.reply_to, wizard.model, res_ids, context)
|
||||
|
||||
results = dict.fromkeys(res_ids, False)
|
||||
for res_id in res_ids:
|
||||
results[res_id] = {
|
||||
'subject': subjects[res_id],
|
||||
'body': bodies[res_id],
|
||||
'email_from': emails_from[res_id],
|
||||
'reply_to': replies_to[res_id],
|
||||
}
|
||||
return results
|
||||
|
||||
def render_template_batch(self, cr, uid, template, model, res_ids, context=None):
|
||||
""" Render the given template text, replace mako-like expressions ``${expr}``
|
||||
with the result of evaluating these expressions with an evaluation context
|
||||
containing:
|
||||
with the result of evaluating these expressions with an evaluation context
|
||||
containing:
|
||||
|
||||
* ``user``: browse_record of the current user
|
||||
* ``object``: browse_record of the document record this mail is
|
||||
related to
|
||||
* ``context``: the context passed to the mail composition wizard
|
||||
* ``user``: browse_record of the current user
|
||||
* ``object``: browse_record of the document record this mail is
|
||||
related to
|
||||
* ``context``: the context passed to the mail composition wizard
|
||||
|
||||
:param str template: the template text to render
|
||||
:param str model: model name of the document record this mail is related to.
|
||||
:param int res_id: id of the document record this mail is related to.
|
||||
:param str template: the template text to render
|
||||
:param str model: model name of the document record this mail is related to
|
||||
:param list res_ids: list of record ids
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
results = dict.fromkeys(res_ids, False)
|
||||
|
||||
def merge(match):
|
||||
exp = str(match.group()[2:-1]).strip()
|
||||
result = eval(exp, {
|
||||
'user': self.pool.get('res.users').browse(cr, uid, uid, context=context),
|
||||
'object': self.pool[model].browse(cr, uid, res_id, context=context),
|
||||
'context': dict(context), # copy context to prevent side-effects of eval
|
||||
for res_id in res_ids:
|
||||
def merge(match):
|
||||
exp = str(match.group()[2:-1]).strip()
|
||||
result = eval(exp, {
|
||||
'user': self.pool.get('res.users').browse(cr, uid, uid, context=context),
|
||||
'object': self.pool[model].browse(cr, uid, res_id, context=context),
|
||||
'context': dict(context), # copy context to prevent side-effects of eval
|
||||
})
|
||||
return result and tools.ustr(result) or ''
|
||||
return template and EXPRESSION_PATTERN.sub(merge, template)
|
||||
return result and tools.ustr(result) or ''
|
||||
results[res_id] = template and EXPRESSION_PATTERN.sub(merge, template)
|
||||
return results
|
||||
|
||||
# Compatibility methods
|
||||
def render_template(self, cr, uid, template, model, res_id, context=None):
|
||||
return self.render_template_batch(cr, uid, template, model, [res_id], context)[res_id]
|
||||
|
||||
def render_message(self, cr, uid, wizard, res_id, context=None):
|
||||
return self.render_message_batch(cr, uid, wizard, [res_id], context)[res_id]
|
||||
|
||||
@@ -19,15 +19,7 @@
|
||||
<field name="email_from"
|
||||
attrs="{'invisible':[('composition_mode', '!=', 'mass_mail')]}"/>
|
||||
<field name="subject" placeholder="Subject..." required="True"/>
|
||||
<field name="post"
|
||||
attrs="{'invisible':[('composition_mode', '!=', 'mass_mail')]}"/>
|
||||
<field name="notify"
|
||||
attrs="{'invisible':['|', ('post', '!=', True), ('composition_mode', '!=', 'mass_mail')]}"/>
|
||||
<field name="same_thread"
|
||||
attrs="{'invisible':[('composition_mode', '!=', 'mass_mail')]}"/>
|
||||
<field name="reply_to" placeholder="Email address te redirect replies..."
|
||||
attrs="{'invisible':['|', ('same_thread', '=', True), ('composition_mode', '!=', 'mass_mail')],
|
||||
'required':[('same_thread', '!=', True)]}"/>
|
||||
<!-- classic message composer -->
|
||||
<label for="partner_ids" string="Recipients"
|
||||
attrs="{'invisible':[('composition_mode', '=', 'mass_mail')]}"/>
|
||||
<div groups="base.group_user"
|
||||
@@ -40,6 +32,16 @@
|
||||
<field name="partner_ids" widget="many2many_tags_email" placeholder="Add contacts to notify..."
|
||||
context="{'force_email':True, 'show_email':True}"/>
|
||||
</div>
|
||||
<!-- mass post / mass mailing -->
|
||||
<field name="post"
|
||||
attrs="{'invisible':[('composition_mode', '!=', 'mass_mail')]}"/>
|
||||
<field name="notify"
|
||||
attrs="{'invisible':['|', ('post', '!=', True), ('composition_mode', '!=', 'mass_mail')]}"/>
|
||||
<field name="same_thread"
|
||||
attrs="{'invisible':[('composition_mode', '!=', 'mass_mail')]}"/>
|
||||
<field name="reply_to" placeholder="Email address te redirect replies..."
|
||||
attrs="{'invisible':['|', ('same_thread', '=', True), ('composition_mode', '!=', 'mass_mail')],
|
||||
'required':[('same_thread', '!=', True)]}"/>
|
||||
</group>
|
||||
<field name="body"/>
|
||||
<field name="attachment_ids" widget="many2many_binary" string="Attach a file"/>
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2013-today OpenERP SA (<http://www.openerp.com>)
|
||||
#
|
||||
# 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/>
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import mass_mailing
|
||||
import mail_mail
|
||||
import mail_thread
|
||||
import wizard
|
||||
import controllers
|
||||
@@ -0,0 +1,54 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2013-Today OpenERP SA (<http://www.openerp.com>)
|
||||
#
|
||||
# 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': 'Mass Mailing Campaigns',
|
||||
'description': """TODO""",
|
||||
'version': '1.0',
|
||||
'author': 'OpenERP',
|
||||
'website': 'http://www.openerp.com',
|
||||
'category': 'Marketing',
|
||||
'depends': [
|
||||
'mail',
|
||||
'email_template',
|
||||
'web_kanban_gauge',
|
||||
'web_kanban_sparkline',
|
||||
],
|
||||
'data': [
|
||||
'mail_data.xml',
|
||||
'mass_mailing_view.xml',
|
||||
'wizard/mail_compose_message_view.xml',
|
||||
'wizard/mail_mass_mailing_create_segment.xml',
|
||||
'security/ir.model.access.csv',
|
||||
],
|
||||
'js': [
|
||||
'static/src/js/mass_mailing.js',
|
||||
],
|
||||
'qweb': [],
|
||||
'css': [
|
||||
'static/src/css/mass_mailing.css'
|
||||
],
|
||||
'demo': [
|
||||
'mass_mailing_demo.xml',
|
||||
],
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import main
|
||||
|
||||
# vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
@@ -0,0 +1,12 @@
|
||||
|
||||
import openerp.addons.web.http as http
|
||||
from openerp.addons.web.http import request
|
||||
|
||||
|
||||
class MassMailController(http.Controller):
|
||||
@http.route('/mail/track/<int:mail_id>/blank.gif', type='http', auth='admin')
|
||||
def track_mail_open(self, mail_id):
|
||||
""" Email tracking. """
|
||||
mail_mail_stats = request.registry.get('mail.mail.statistics')
|
||||
mail_mail_stats.set_opened(request.cr, request.uid, mail_mail_ids=[mail_id])
|
||||
return "data:image/gif;base64,R0lGODlhAQABAIAAANvf7wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=="
|
||||
@@ -0,0 +1,9 @@
|
||||
.. _changelog:
|
||||
|
||||
Changelog
|
||||
=========
|
||||
|
||||
`trunk (saas-2)`
|
||||
----------------
|
||||
|
||||
- added module
|
||||
@@ -0,0 +1,13 @@
|
||||
Mass Mailing module documentation
|
||||
=================================
|
||||
|
||||
Mass Mailing documentation topics
|
||||
'''''''''''''''''''''''''''''''''
|
||||
|
||||
Changelog
|
||||
'''''''''
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
changelog.rst
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data noupdate="1">
|
||||
|
||||
<!-- Bounce Email Alias -->
|
||||
<record id="icp_mail_bounce_alias" model="ir.config_parameter">
|
||||
<field name="key">mail.bounce.alias</field>
|
||||
<field name="value">bounce</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
@@ -0,0 +1,65 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2013-Today OpenERP SA (<http://www.openerp.com>)
|
||||
#
|
||||
# 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/>
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from urlparse import urljoin
|
||||
|
||||
from openerp import tools
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.osv import osv, fields
|
||||
|
||||
|
||||
class MailMail(osv.Model):
|
||||
"""Add the mass mailing campaign data to mail"""
|
||||
_name = 'mail.mail'
|
||||
_inherit = ['mail.mail']
|
||||
|
||||
_columns = {
|
||||
'statistics_ids': fields.one2many(
|
||||
'mail.mail.statistics', 'mail_mail_id',
|
||||
string='Statistics',
|
||||
),
|
||||
}
|
||||
|
||||
def create(self, cr, uid, values, context=None):
|
||||
""" Override mail_mail creation to create an entry in mail.mail.statistics """
|
||||
# TDE note: should be after 'all values computed', to have values (FIXME after merging other branch holding create refactoring)
|
||||
mail_id = super(MailMail, self).create(cr, uid, values, context=context)
|
||||
if values.get('statistics_ids'):
|
||||
mail = self.browse(cr, SUPERUSER_ID, mail_id)
|
||||
for stat in mail.statistics_ids:
|
||||
self.pool['mail.mail.statistics'].write(cr, uid, [stat.id], {'message_id': mail.message_id}, context=context)
|
||||
return mail_id
|
||||
|
||||
def _get_tracking_url(self, cr, uid, mail, partner=None, context=None):
|
||||
base_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url')
|
||||
track_url = urljoin(base_url, 'mail/track/%d/blank.gif' % mail.id)
|
||||
return '<img src="%s" alt=""/>' % track_url
|
||||
|
||||
def send_get_mail_body(self, cr, uid, mail, partner=None, context=None):
|
||||
""" Override to add the tracking URL to the body. """
|
||||
body = super(MailMail, self).send_get_mail_body(cr, uid, mail, partner=partner, context=context)
|
||||
|
||||
# generate tracking URL
|
||||
if mail.statistics_ids:
|
||||
tracking_url = self._get_tracking_url(cr, uid, mail, partner, context=context)
|
||||
if tracking_url:
|
||||
body = tools.append_content_to_html(body, tracking_url, plaintext=False, container_tag='div')
|
||||
return body
|
||||
@@ -0,0 +1,86 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2013-Today OpenERP SA (<http://www.openerp.com>)
|
||||
#
|
||||
# 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/>
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import logging
|
||||
|
||||
from openerp import tools
|
||||
from openerp.addons.mail.mail_message import decode
|
||||
from openerp.addons.mail.mail_thread import decode_header
|
||||
from openerp.osv import osv
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MailThread(osv.Model):
|
||||
""" Update MailThread to add the feature of bounced emails and replied emails
|
||||
in message_process. """
|
||||
_name = 'mail.thread'
|
||||
_inherit = ['mail.thread']
|
||||
|
||||
def message_route_check_bounce(self, cr, uid, message, context=None):
|
||||
""" Override to verify that the email_to is the bounce alias. If it is the
|
||||
case, log the bounce, set the parent and related document as bounced and
|
||||
return False to end the routing process. """
|
||||
bounce_alias = self.pool['ir.config_parameter'].get_param(cr, uid, "mail.bounce.alias", context=context)
|
||||
message_id = message.get('Message-Id')
|
||||
email_from = decode_header(message, 'From')
|
||||
email_to = decode_header(message, 'To')
|
||||
|
||||
# 0. Verify whether this is a bounced email (wrong destination,...) -> use it to collect data, such as dead leads
|
||||
if bounce_alias in email_to:
|
||||
bounce_match = tools.bounce_re.search(email_to)
|
||||
if bounce_match:
|
||||
bounced_mail_id = bounce_match.group(1)
|
||||
stat_ids = self.pool['mail.mail.statistics'].set_bounced(cr, uid, mail_mail_ids=[bounced_mail_id], context=context)
|
||||
for stat in self.pool['mail.mail.statistics'].browse(cr, uid, stat_ids, context=context):
|
||||
bounced_model = stat.model
|
||||
bounced_thread_id = stat.res_id
|
||||
_logger.info('Routing mail from %s to %s with Message-Id %s: bounced mail from mail %s, model: %s, thread_id: %s',
|
||||
email_from, email_to, message_id, bounced_mail_id, bounced_model, bounced_thread_id)
|
||||
if bounced_model and bounced_model in self.pool and hasattr(self.pool[bounced_model], 'message_receive_bounce'):
|
||||
self.pool[bounced_model].message_receive_bounce(cr, uid, [bounced_thread_id], mail_id=bounced_mail_id, context=context)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def message_route(self, cr, uid, message, message_dict, model=None, thread_id=None,
|
||||
custom_values=None, context=None):
|
||||
if not self.message_route_check_bounce(cr, uid, message, context=context):
|
||||
return []
|
||||
return super(MailThread, self).message_route(cr, uid, message, message_dict, model, thread_id, custom_values, context)
|
||||
|
||||
def message_receive_bounce(self, cr, uid, ids, mail_id=None, context=None):
|
||||
"""Called by ``message_process`` when a bounce email (such as Undelivered
|
||||
Mail Returned to Sender) is received for an existing thread. The default
|
||||
behavior is to check is an integer ``message_bounce`` column exists.
|
||||
If it is the case, its content is incremented. """
|
||||
if self._all_columns.get('message_bounce'):
|
||||
for obj in self.browse(cr, uid, ids, context=context):
|
||||
self.write(cr, uid, [obj.id], {'message_bounce': obj.message_bounce + 1}, context=context)
|
||||
|
||||
def message_route_process(self, cr, uid, message, message_dict, routes, context=None):
|
||||
""" Override to update the parent mail statistics. The parent is found
|
||||
by using the References header of the incoming message and looking for
|
||||
matching message_id in mail.mail.statistics. """
|
||||
if message.get('References'):
|
||||
message_ids = [x.strip() for x in decode(message['References']).split()]
|
||||
self.pool['mail.mail.statistics'].set_replied(cr, uid, mail_message_ids=message_ids, context=context)
|
||||
return super(MailThread, self).message_route_process(cr, uid, message, message_dict, routes, context=context)
|
||||
@@ -0,0 +1,376 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2013-today OpenERP SA (<http://www.openerp.com>)
|
||||
#
|
||||
# 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/>
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from datetime import datetime
|
||||
from dateutil import relativedelta
|
||||
|
||||
from openerp import tools
|
||||
from openerp.tools.translate import _
|
||||
from openerp.osv import osv, fields
|
||||
|
||||
|
||||
class MassMailingCampaign(osv.Model):
|
||||
"""Model of mass mailing campaigns.
|
||||
"""
|
||||
_name = "mail.mass_mailing.campaign"
|
||||
_description = 'Mass Mailing Campaign'
|
||||
# number of embedded mailings in kanban view
|
||||
_kanban_mailing_nbr = 4
|
||||
|
||||
def _get_statistics(self, cr, uid, ids, name, arg, context=None):
|
||||
""" Compute statistics of the mass mailing campaign """
|
||||
results = dict.fromkeys(ids, False)
|
||||
for campaign in self.browse(cr, uid, ids, context=context):
|
||||
results[campaign.id] = {
|
||||
'sent': len(campaign.statistics_ids),
|
||||
# delivered: shouldn't be: all mails - (failed + bounced) ?
|
||||
'delivered': len([stat for stat in campaign.statistics_ids if not stat.bounced]), # stat.state == 'sent' and
|
||||
'opened': len([stat for stat in campaign.statistics_ids if stat.opened]),
|
||||
'replied': len([stat for stat in campaign.statistics_ids if stat.replied]),
|
||||
'bounced': len([stat for stat in campaign.statistics_ids if stat.bounced]),
|
||||
}
|
||||
return results
|
||||
|
||||
def _get_mass_mailing_kanban_ids(self, cr, uid, ids, name, arg, context=None):
|
||||
""" Gather data about mass mailings to display them in kanban view as
|
||||
nested kanban views is not possible currently. """
|
||||
results = dict.fromkeys(ids, '')
|
||||
for campaign in self.browse(cr, uid, ids, context=context):
|
||||
mass_mailing_results = []
|
||||
for mass_mailing in campaign.mass_mailing_ids[:self._kanban_mailing_nbr]:
|
||||
mass_mailing_object = {}
|
||||
for attr in ['name', 'sent', 'delivered', 'opened', 'replied', 'bounced']:
|
||||
mass_mailing_object[attr] = getattr(mass_mailing, attr)
|
||||
mass_mailing_results.append(mass_mailing_object)
|
||||
results[campaign.id] = mass_mailing_results
|
||||
return results
|
||||
|
||||
_columns = {
|
||||
'name': fields.char(
|
||||
'Campaign Name', required=True,
|
||||
),
|
||||
'user_id': fields.many2one(
|
||||
'res.users', 'Responsible',
|
||||
required=True,
|
||||
),
|
||||
'mass_mailing_ids': fields.one2many(
|
||||
'mail.mass_mailing', 'mass_mailing_campaign_id',
|
||||
'Mass Mailings',
|
||||
),
|
||||
'mass_mailing_kanban_ids': fields.function(
|
||||
_get_mass_mailing_kanban_ids,
|
||||
type='text', string='Mass Mailings (kanban data)',
|
||||
help='This field has for purpose to gather data about mass mailings '
|
||||
'to display them in kanban view as nested kanban views is not '
|
||||
'possible currently',
|
||||
),
|
||||
'statistics_ids': fields.one2many(
|
||||
'mail.mail.statistics', 'mass_mailing_campaign_id',
|
||||
'Sent Emails',
|
||||
),
|
||||
'color': fields.integer('Color Index'),
|
||||
# stat fields
|
||||
'sent': fields.function(
|
||||
_get_statistics,
|
||||
string='Sent Emails',
|
||||
type='integer', multi='_get_statistics'
|
||||
),
|
||||
'delivered': fields.function(
|
||||
_get_statistics,
|
||||
string='Delivered',
|
||||
type='integer', multi='_get_statistics',
|
||||
),
|
||||
'opened': fields.function(
|
||||
_get_statistics,
|
||||
string='Opened',
|
||||
type='integer', multi='_get_statistics',
|
||||
),
|
||||
'replied': fields.function(
|
||||
_get_statistics,
|
||||
string='Replied',
|
||||
type='integer', multi='_get_statistics'
|
||||
),
|
||||
'bounced': fields.function(
|
||||
_get_statistics,
|
||||
string='Bounced',
|
||||
type='integer', multi='_get_statistics'
|
||||
),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'user_id': lambda self, cr, uid, ctx=None: uid,
|
||||
}
|
||||
|
||||
def launch_mass_mailing_create_wizard(self, cr, uid, ids, context=None):
|
||||
ctx = dict(context)
|
||||
ctx.update({
|
||||
'default_mass_mailing_campaign_id': ids[0],
|
||||
})
|
||||
return {
|
||||
'name': _('Create a Mass Mailing for the Campaign'),
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'mail.mass_mailing.create',
|
||||
'views': [(False, 'form')],
|
||||
'view_id': False,
|
||||
'target': 'new',
|
||||
'context': ctx,
|
||||
}
|
||||
|
||||
|
||||
class MassMailing(osv.Model):
|
||||
""" MassMailing models a wave of emails for a mass mailign campaign.
|
||||
A mass mailing is an occurence of sending emails. """
|
||||
|
||||
_name = 'mail.mass_mailing'
|
||||
_description = 'Wave of sending emails'
|
||||
# number of periods for tracking mail_mail statistics
|
||||
_period_number = 6
|
||||
_order = 'date DESC'
|
||||
|
||||
def __get_bar_values(self, cr, uid, id, obj, domain, read_fields, value_field, groupby_field, context=None):
|
||||
""" Generic method to generate data for bar chart values using SparklineBarWidget.
|
||||
This method performs obj.read_group(cr, uid, domain, read_fields, groupby_field).
|
||||
|
||||
:param obj: the target model (i.e. crm_lead)
|
||||
:param domain: the domain applied to the read_group
|
||||
:param list read_fields: the list of fields to read in the read_group
|
||||
:param str value_field: the field used to compute the value of the bar slice
|
||||
:param str groupby_field: the fields used to group
|
||||
|
||||
:return list section_result: a list of dicts: [
|
||||
{ 'value': (int) bar_column_value,
|
||||
'tootip': (str) bar_column_tooltip,
|
||||
}
|
||||
]
|
||||
"""
|
||||
date_begin = datetime.strptime(self.browse(cr, uid, id, context=context).date, tools.DEFAULT_SERVER_DATETIME_FORMAT).date()
|
||||
section_result = [{'value': 0,
|
||||
'tooltip': (date_begin + relativedelta.relativedelta(days=i)).strftime('%d %B %Y'),
|
||||
} for i in range(0, self._period_number)]
|
||||
group_obj = obj.read_group(cr, uid, domain, read_fields, groupby_field, context=context)
|
||||
for group in group_obj:
|
||||
group_begin_date = datetime.strptime(group['__domain'][0][2], tools.DEFAULT_SERVER_DATE_FORMAT).date()
|
||||
timedelta = relativedelta.relativedelta(group_begin_date, date_begin)
|
||||
section_result[timedelta.days] = {'value': group.get(value_field, 0), 'tooltip': group.get(groupby_field)}
|
||||
return section_result
|
||||
|
||||
def _get_daily_statistics(self, cr, uid, ids, field_name, arg, context=None):
|
||||
""" Get the daily statistics of the mass mailing. This is done by a grouping
|
||||
on opened and replied fields. Using custom format in context, we obtain
|
||||
results for the next 6 days following the mass mailing date. """
|
||||
obj = self.pool['mail.mail.statistics']
|
||||
res = {}
|
||||
context['datetime_format'] = {
|
||||
'opened': {
|
||||
'interval': 'day',
|
||||
'groupby_format': 'yyyy-mm-dd',
|
||||
'display_format': 'dd MMMM YYYY'
|
||||
},
|
||||
'replied': {
|
||||
'interval': 'day',
|
||||
'groupby_format': 'yyyy-mm-dd',
|
||||
'display_format': 'dd MMMM YYYY'
|
||||
},
|
||||
}
|
||||
for id in ids:
|
||||
res[id] = {}
|
||||
date_begin = self.browse(cr, uid, id, context=context).date
|
||||
domain = [('mass_mailing_id', '=', id), ('opened', '>=', date_begin)]
|
||||
res[id]['opened_monthly'] = self.__get_bar_values(cr, uid, id, obj, domain, ['opened'], 'opened_count', 'opened', context=context)
|
||||
domain = [('mass_mailing_id', '=', id), ('replied', '>=', date_begin)]
|
||||
res[id]['replied_monthly'] = self.__get_bar_values(cr, uid, id, obj, domain, ['replied'], 'replied_count', 'replied', context=context)
|
||||
return res
|
||||
|
||||
def _get_statistics(self, cr, uid, ids, name, arg, context=None):
|
||||
""" Compute statistics of the mass mailing campaign """
|
||||
results = dict.fromkeys(ids, False)
|
||||
for mass_mailing in self.browse(cr, uid, ids, context=context):
|
||||
results[mass_mailing.id] = {
|
||||
'sent': len(mass_mailing.statistics_ids),
|
||||
'delivered': len([stat for stat in mass_mailing.statistics_ids if not stat.bounced]), # mail.state == 'sent' and
|
||||
'opened': len([stat for stat in mass_mailing.statistics_ids if stat.opened]),
|
||||
'replied': len([stat for stat in mass_mailing.statistics_ids if stat.replied]),
|
||||
'bounced': len([stat for stat in mass_mailing.statistics_ids if stat.bounced]),
|
||||
}
|
||||
return results
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Name', required=True),
|
||||
'mass_mailing_campaign_id': fields.many2one(
|
||||
'mail.mass_mailing.campaign', 'Mass Mailing Campaign',
|
||||
ondelete='cascade', required=True,
|
||||
),
|
||||
'template_id': fields.many2one(
|
||||
'email.template', 'Email Template',
|
||||
ondelete='set null',
|
||||
),
|
||||
'domain': fields.char('Domain'),
|
||||
'date': fields.datetime('Date'),
|
||||
'color': fields.related(
|
||||
'mass_mailing_campaign_id', 'color',
|
||||
type='integer', string='Color Index',
|
||||
),
|
||||
# statistics data
|
||||
'statistics_ids': fields.one2many(
|
||||
'mail.mail.statistics', 'mass_mailing_id',
|
||||
'Emails Statistics',
|
||||
),
|
||||
'sent': fields.function(
|
||||
_get_statistics,
|
||||
string='Sent Emails',
|
||||
type='integer', multi='_get_statistics'
|
||||
),
|
||||
'delivered': fields.function(
|
||||
_get_statistics,
|
||||
string='Delivered',
|
||||
type='integer', multi='_get_statistics',
|
||||
),
|
||||
'opened': fields.function(
|
||||
_get_statistics,
|
||||
string='Opened',
|
||||
type='integer', multi='_get_statistics',
|
||||
),
|
||||
'replied': fields.function(
|
||||
_get_statistics,
|
||||
string='Replied',
|
||||
type='integer', multi='_get_statistics'
|
||||
),
|
||||
'bounced': fields.function(
|
||||
_get_statistics,
|
||||
string='Bounce',
|
||||
type='integer', multi='_get_statistics'
|
||||
),
|
||||
# monthly ratio
|
||||
'opened_monthly': fields.function(
|
||||
_get_daily_statistics,
|
||||
string='Opened',
|
||||
type='char', multi='_get_daily_statistics',
|
||||
),
|
||||
'replied_monthly': fields.function(
|
||||
_get_daily_statistics,
|
||||
string='Replied',
|
||||
type='char', multi='_get_daily_statistics',
|
||||
),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'date': fields.datetime.now(),
|
||||
}
|
||||
|
||||
|
||||
class MailMailStats(osv.Model):
|
||||
""" MailMailStats models the statistics collected about emails. Those statistics
|
||||
are stored in a separated model and table to avoid bloating the mail_mail table
|
||||
with statistics values. This also allows to delete emails send with mass mailing
|
||||
without loosing the statistics about them. """
|
||||
|
||||
_name = 'mail.mail.statistics'
|
||||
_description = 'Email Statistics'
|
||||
_rec_name = 'message_id'
|
||||
_order = 'message_id'
|
||||
|
||||
_columns = {
|
||||
'mail_mail_id': fields.integer(
|
||||
'Mail ID',
|
||||
help='ID of the related mail_mail. This field is an integer field because'
|
||||
'the related mail_mail can be deleted separately from its statistics.'
|
||||
),
|
||||
'message_id': fields.char(
|
||||
'Message-ID',
|
||||
),
|
||||
'model': fields.char(
|
||||
'Document model',
|
||||
),
|
||||
'res_id': fields.integer(
|
||||
'Document ID',
|
||||
),
|
||||
# campaign / wave data
|
||||
'mass_mailing_id': fields.many2one(
|
||||
'mail.mass_mailing', 'Mass Mailing',
|
||||
ondelete='set null',
|
||||
),
|
||||
'mass_mailing_campaign_id': fields.related(
|
||||
'mass_mailing_id', 'mass_mailing_campaign_id',
|
||||
type='many2one', ondelete='set null',
|
||||
relation='mail.mass_mailing.campaign',
|
||||
string='Mass Mailing Campaign',
|
||||
store=True, readonly=True,
|
||||
),
|
||||
'template_id': fields.related(
|
||||
'mass_mailing_id', 'template_id',
|
||||
type='many2one', ondelete='set null',
|
||||
relation='email.template',
|
||||
string='Email Template',
|
||||
store=True, readonly=True,
|
||||
),
|
||||
# Bounce and tracking
|
||||
'opened': fields.datetime(
|
||||
'Opened',
|
||||
help='Date when this email has been opened for the first time.'),
|
||||
'replied': fields.datetime(
|
||||
'Replied',
|
||||
help='Date when this email has been replied for the first time.'),
|
||||
'bounced': fields.datetime(
|
||||
'Bounced',
|
||||
help='Date when this email has bounced.'
|
||||
),
|
||||
}
|
||||
|
||||
def set_opened(self, cr, uid, ids=None, mail_mail_ids=None, mail_message_ids=None, context=None):
|
||||
""" Set as opened """
|
||||
if not ids and mail_mail_ids:
|
||||
ids = self.search(cr, uid, [('mail_mail_id', 'in', mail_mail_ids)], context=context)
|
||||
elif not ids and mail_message_ids:
|
||||
ids = self.search(cr, uid, [('message_id', 'in', mail_message_ids)], context=context)
|
||||
else:
|
||||
ids = []
|
||||
for stat in self.browse(cr, uid, ids, context=context):
|
||||
if not stat.opened:
|
||||
self.write(cr, uid, [stat.id], {'opened': fields.datetime.now()}, context=context)
|
||||
return ids
|
||||
|
||||
def set_replied(self, cr, uid, ids=None, mail_mail_ids=None, mail_message_ids=None, context=None):
|
||||
""" Set as replied """
|
||||
if not ids and mail_mail_ids:
|
||||
ids = self.search(cr, uid, [('mail_mail_id', 'in', mail_mail_ids)], context=context)
|
||||
elif not ids and mail_message_ids:
|
||||
ids = self.search(cr, uid, [('message_id', 'in', mail_message_ids)], context=context)
|
||||
else:
|
||||
ids = []
|
||||
for stat in self.browse(cr, uid, ids, context=context):
|
||||
if not stat.replied:
|
||||
self.write(cr, uid, [stat.id], {'replied': fields.datetime.now()}, context=context)
|
||||
return ids
|
||||
|
||||
def set_bounced(self, cr, uid, ids=None, mail_mail_ids=None, mail_message_ids=None, context=None):
|
||||
""" Set as bounced """
|
||||
if not ids and mail_mail_ids:
|
||||
ids = self.search(cr, uid, [('mail_mail_id', 'in', mail_mail_ids)], context=context)
|
||||
elif not ids and mail_message_ids:
|
||||
ids = self.search(cr, uid, [('message_id', 'in', mail_message_ids)], context=context)
|
||||
else:
|
||||
ids = []
|
||||
for stat in self.browse(cr, uid, ids, context=context):
|
||||
if not stat.bounced:
|
||||
self.write(cr, uid, [stat.id], {'bounced': fields.datetime.now()}, context=context)
|
||||
return ids
|
||||
@@ -0,0 +1,90 @@
|
||||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<!-- <data noupdate="1"> -->
|
||||
<data>
|
||||
|
||||
<record id="mass_mail_template_1" model="email.template">
|
||||
<field name="name">Partner Newsletter 1</field>
|
||||
<field name="model_id" ref="base.model_res_partner"/>
|
||||
<field name="auto_delete" eval="False"/>
|
||||
<field name="partner_to">${object.id}</field>
|
||||
<field name="body_html"><![CDATA[<p>Hello</p>]]></field>
|
||||
</record>
|
||||
<record id="mass_mail_template_2" model="email.template">
|
||||
<field name="name">Partner Newsletter 2</field>
|
||||
<field name="model_id" ref="base.model_res_partner"/>
|
||||
<field name="auto_delete" eval="False"/>
|
||||
<field name="partner_to">${object.id}</field>
|
||||
<field name="body_html"><![CDATA[<p>Hello</p>]]></field>
|
||||
</record>
|
||||
|
||||
<record id="mass_mail_campaign_1" model="mail.mass_mailing.campaign">
|
||||
<field name="name">Partners Newsletter</field>
|
||||
<field name="user_id" eval="ref('base.user_root')"/>
|
||||
</record>
|
||||
|
||||
<record id="mass_mail_1" model="mail.mass_mailing">
|
||||
<field name="name">First Newsletter</field>
|
||||
<field name="template_id" eval="ref('mass_mail_template_1')"/>
|
||||
<field name="date" eval="(DateTime.today() - relativedelta(days=3)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
<field name="mass_mailing_campaign_id" eval="ref('mass_mail_campaign_1')"/>
|
||||
</record>
|
||||
<record id="mass_mail_2" model="mail.mass_mailing">
|
||||
<field name="name">Second Newsletter</field>
|
||||
<field name="template_id" eval="ref('mass_mail_template_2')"/>
|
||||
<field name="date" eval="(DateTime.today() - relativedelta(days=3)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
<field name="mass_mailing_campaign_id" eval="ref('mass_mail_campaign_1')"/>
|
||||
</record>
|
||||
|
||||
<record id="mass_mail_email_1" model="mail.mail.statistics">
|
||||
<field name="mass_mailing_id" eval="ref('mass_mail_1')"/>
|
||||
<field name="message_id">1111000@OpenERP.com</field>
|
||||
<field name="opened" eval="(DateTime.today() - relativedelta(days=2)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
<field name="replied" eval="(DateTime.today() - relativedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
<field name="state">sent</field>
|
||||
</record>
|
||||
<record id="mass_mail_email_2" model="mail.mail.statistics">
|
||||
<field name="mass_mailing_id" eval="ref('mass_mail_1')"/>
|
||||
<field name="message_id">1111001@OpenERP.com</field>
|
||||
<field name="opened" eval="(DateTime.today() - relativedelta(days=2)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
<field name="replied" eval="(DateTime.today() - relativedelta(days=0)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
<field name="state">sent</field>
|
||||
</record>
|
||||
<record id="mass_mail_email_3" model="mail.mail.statistics">
|
||||
<field name="mass_mailing_id" eval="ref('mass_mail_1')"/>
|
||||
<field name="message_id">1111002@OpenERP.com</field>
|
||||
<field name="opened" eval="(DateTime.today() - relativedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
<field name="state">sent</field>
|
||||
</record>
|
||||
<record id="mass_mail_email_4" model="mail.mail.statistics">
|
||||
<field name="mass_mailing_id" eval="ref('mass_mail_1')"/>
|
||||
<field name="message_id">1111003@OpenERP.com</field>
|
||||
<field name="state">sent</field>
|
||||
</record>
|
||||
<record id="mass_mail_email_5" model="mail.mail.statistics">
|
||||
<field name="mass_mailing_id" eval="ref('mass_mail_1')"/>
|
||||
<field name="message_id">1111004@OpenERP.com</field>
|
||||
<field name="bounced" eval="(DateTime.today() - relativedelta(days=3)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
<field name="state">sent</field>
|
||||
</record>
|
||||
|
||||
<record id="mass_mail_email_2_1" model="mail.mail.statistics">
|
||||
<field name="mass_mailing_id" eval="ref('mass_mail_2')"/>
|
||||
<field name="message_id">1111005@OpenERP.com</field>
|
||||
<field name="opened" eval="(DateTime.today() - relativedelta(days=3)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
<field name="state">sent</field>
|
||||
</record>
|
||||
<record id="mass_mail_email_2_2" model="mail.mail.statistics">
|
||||
<field name="mass_mailing_id" eval="ref('mass_mail_2')"/>
|
||||
<field name="message_id">1111006@OpenERP.com</field>
|
||||
<field name="opened" eval="(DateTime.today() - relativedelta(days=2)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
<field name="state">sent</field>
|
||||
</record>
|
||||
<record id="mass_mail_email_2_3" model="mail.mail.statistics">
|
||||
<field name="mass_mailing_id" eval="ref('mass_mail_2')"/>
|
||||
<field name="message_id">1111007@OpenERP.com</field>
|
||||
<field name="state">sent</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
@@ -0,0 +1,376 @@
|
||||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<!-- MASS MAILING !-->
|
||||
<record model="ir.ui.view" id="view_mail_mass_mailing_search">
|
||||
<field name="name">mail.mass_mailing.search</field>
|
||||
<field name="model">mail.mass_mailing</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Mass Mailings">
|
||||
<field name="name" string="Mailings"/>
|
||||
<field name="mass_mailing_campaign_id"/>
|
||||
<field name="template_id"/>
|
||||
<group expand="0" string="Group By...">
|
||||
<filter string="Campaign" name="group_mass_mailing_campaign_id"
|
||||
context="{'group_by': 'mass_mailing_campaign_id'}"/>
|
||||
<filter string="Template" name="group_template_id"
|
||||
context="{'group_by': 'template_id'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_mail_mass_mailing_tree">
|
||||
<field name="name">mail.mass_mailing.tree</field>
|
||||
<field name="model">mail.mass_mailing</field>
|
||||
<field name="priority">10</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Mass Mailings">
|
||||
<field name="name"/>
|
||||
<field name="sent"/>
|
||||
<field name="delivered"/>
|
||||
<field name="opened"/>
|
||||
<field name="replied"/>
|
||||
<field name="mass_mailing_campaign_id" invisible="1"/>
|
||||
<field name="template_id" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_mail_mass_mailing_form">
|
||||
<field name="name">mail.mass_mailing.form</field>
|
||||
<field name="model">mail.mass_mailing</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Mass Mailing" version="7.0">
|
||||
<sheet>
|
||||
<group>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="mass_mailing_campaign_id" readonly="True"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="template_id"/>
|
||||
<field name="domain"/>
|
||||
<field name="date"/>
|
||||
</group>
|
||||
</group>
|
||||
<group string="Email Statistics">
|
||||
<field name="statistics_ids" nolabel="1" colspan="2"/>
|
||||
<group>
|
||||
<field name="sent"/>
|
||||
<field name="opened"/>
|
||||
<field name="bounced"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="delivered"/>
|
||||
<field name="replied"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_mail_mass_mailing_kanban">
|
||||
<field name="name">mail.mass_mailing.kanban</field>
|
||||
<field name="model">mail.mass_mailing</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban>
|
||||
<field name='color'/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_card oe_kanban_global_click oe_kanban_mass_mailing oe_kanban_mass_mailing_segment">
|
||||
<div class="oe_kanban_content">
|
||||
<div>
|
||||
<h3>
|
||||
<field name="name"/>
|
||||
</h3>
|
||||
<p style="margin-left: 10px; margin-top: 8px;">
|
||||
Sent: <field name="date"/><br />
|
||||
Campaign: <field name="mass_mailing_campaign_id"/>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="oe_mail_stats">
|
||||
<span class="oe_mail_result"><field name="sent"/></span><br />
|
||||
Sent
|
||||
</p>
|
||||
<p class="oe_mail_stats">
|
||||
<span class="oe_mail_result"><field name="delivered"/></span><br />
|
||||
Delivered
|
||||
</p>
|
||||
<p class="oe_mail_stats">
|
||||
<span class="oe_mail_result"><field name="opened"/></span><br />
|
||||
Opened
|
||||
</p>
|
||||
<p class="oe_mail_stats">
|
||||
<span class="oe_mail_result"><field name="replied"/></span><br />
|
||||
Replied
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="oe_sparkline_container">
|
||||
<h4 class="oe_sparkline_bar_title">Opened</h4><br />
|
||||
<field name="opened_monthly" widget="sparkline_bar" options="{'height': '50px', 'barWidth': 10, 'barSpacing': 5}"/>
|
||||
</div>
|
||||
<div class="oe_sparkline_container">
|
||||
<h4 class="oe_sparkline_bar_title">Replied</h4><br />
|
||||
<field name="replied_monthly" widget="sparkline_bar" options="{'height': '50px', 'barWidth': 10, 'barSpacing': 5}"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oe_clear"></div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_view_mass_mailings" model="ir.actions.act_window">
|
||||
<field name="name">Mass Mailings</field>
|
||||
<field name="res_model">mail.mass_mailing</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">kanban,tree,form</field>
|
||||
</record>
|
||||
|
||||
<record id="action_view_mass_mailings_from_campaign" model="ir.actions.act_window">
|
||||
<field name="name">Mass Mailings</field>
|
||||
<field name="res_model">mail.mass_mailing</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">kanban,tree,form</field>
|
||||
<field name="context">{
|
||||
'search_default_mass_mailing_campaign_id': [active_id],
|
||||
'default_mass_mailing_campaign_id': active_id,
|
||||
}
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- MASS MAILING CAMPAIGNS !-->
|
||||
<record model="ir.ui.view" id="view_mail_mass_mailing_campaign_search">
|
||||
<field name="name">mail.mass_mailing.campaign.search</field>
|
||||
<field name="model">mail.mass_mailing.campaign</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Mass Mailing Campaigns">
|
||||
<field name="name" string="Campaigns"/>
|
||||
<field name="user_id"/>
|
||||
<group expand="0" string="Group By...">
|
||||
<filter string="Responsibles" name="group_user_id"
|
||||
context="{'group_by': 'user_id'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_mail_mass_mailing_campaign_tree">
|
||||
<field name="name">mail.mass_mailing.campaign.tree</field>
|
||||
<field name="model">mail.mass_mailing.campaign</field>
|
||||
<field name="priority">10</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Mass Mailing Campaigns">
|
||||
<field name="name"/>
|
||||
<field name="user_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_mail_mass_mailing_campaign_form">
|
||||
<field name="name">mail.mass_mailing.campaign.form</field>
|
||||
<field name="model">mail.mass_mailing.campaign</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Mass Mailing Campaign" version="7.0">
|
||||
<header>
|
||||
<button name="launch_mass_mailing_create_wizard" type="object"
|
||||
class="oe_highlight" string="Create a New Mailing"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="user_id"/>
|
||||
</group>
|
||||
<group>
|
||||
<group>
|
||||
<field name="sent"/>
|
||||
<field name="opened"/>
|
||||
<field name="bounced"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="delivered"/>
|
||||
<field name="replied"/>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<field name="mass_mailing_ids" readonly="1"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_mail_mass_mailing_campaign_kanban">
|
||||
<field name="name">mail.mass_mailing.campaign.kanban</field>
|
||||
<field name="model">mail.mass_mailing.campaign</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban>
|
||||
<field name="mass_mailing_kanban_ids"/>
|
||||
<field name='sent'/>
|
||||
<field name='color'/>
|
||||
<templates>
|
||||
<t t-name="mass_mailing.mass_mailing">
|
||||
<div class="oe_mass_mailings">
|
||||
<div>
|
||||
<a name="%(action_view_mass_mailings_from_campaign)d" type="action">
|
||||
<h4><t t-raw="mass_mailing.name"/></h4>
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<p class="oe_mail_stats">
|
||||
<span class="oe_mail_result"><t t-raw="mass_mailing.sent"/></span><br />
|
||||
Sent
|
||||
</p>
|
||||
<p class="oe_mail_stats">
|
||||
<span class="oe_mail_result"><t t-raw="mass_mailing.delivered"/></span><br />
|
||||
Delivered
|
||||
</p>
|
||||
<p class="oe_mail_stats">
|
||||
<span class="oe_mail_result"><t t-raw="mass_mailing.opened"/></span><br />
|
||||
Opened
|
||||
</p>
|
||||
<p class="oe_mail_stats">
|
||||
<span class="oe_mail_result"><t t-raw="mass_mailing.replied"/></span><br />
|
||||
Replied
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="kanban-box">
|
||||
<div t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_card oe_kanban_global_click oe_kanban_mass_mailing oe_kanban_mass_mailing_campaign">
|
||||
<div class="oe_dropdown_toggle oe_dropdown_kanban">
|
||||
<span class="oe_e">i</span>
|
||||
<ul class="oe_dropdown_menu">
|
||||
<t t-if="widget.view.is_action_enabled('edit')">
|
||||
<li><a type="edit">Settings</a></li>
|
||||
</t>
|
||||
<t t-if="widget.view.is_action_enabled('delete')">
|
||||
<li><a type="delete">Delete</a></li>
|
||||
</t>
|
||||
<li><ul class="oe_kanban_colorpicker" data-field="color"/></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="oe_kanban_content">
|
||||
<h3>
|
||||
<field name="name"/>
|
||||
</h3>
|
||||
<div>
|
||||
<field name="delivered" widget="gauge" style="width:160px; height: 120px;"
|
||||
options="{'max_field': 'sent'}"/>
|
||||
<field name="opened" widget="gauge" style="width:160px; height: 120px;"
|
||||
options="{'max_field': 'sent'}"/>
|
||||
<field name="replied" widget="gauge" style="width:160px; height: 120px;"
|
||||
options="{'max_field': 'sent'}"/>
|
||||
</div>
|
||||
<t t-foreach='record.mass_mailing_kanban_ids.value' t-as='mass_mailing'>
|
||||
<t t-call="mass_mailing.mass_mailing"/>
|
||||
</t>
|
||||
</div>
|
||||
<div class="oe_clear"></div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_view_mass_mailing_campaigns" model="ir.actions.act_window">
|
||||
<field name="name">Mass Mailing Campaigns</field>
|
||||
<field name="res_model">mail.mass_mailing.campaign</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">kanban,tree,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to define a new mass mailing campaign.
|
||||
</p><p>
|
||||
Create a campaign to structure mass mailing and get analysis from email status.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- MAIL MAIL STATISTICS !-->
|
||||
<record model="ir.ui.view" id="view_mail_mail_statistics_search">
|
||||
<field name="name">mail.mail.statistics.search</field>
|
||||
<field name="model">mail.mail.statistics</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Mail Statistics">
|
||||
<field name="mail_mail_id"/>
|
||||
<field name="message_id"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_mail_mail_statistics_tree">
|
||||
<field name="name">mail.mail.statistics.tree</field>
|
||||
<field name="model">mail.mail.statistics</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Mail Statistics">
|
||||
<field name="mail_mail_id"/>
|
||||
<field name="message_id"/>
|
||||
<field name="opened"/>
|
||||
<field name="replied"/>
|
||||
<field name="bounced"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_mail_mail_statistics_form">
|
||||
<field name="name">mail.mail.statistics.form</field>
|
||||
<field name="model">mail.mail.statistics</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Mail Statistics" version="7.0">
|
||||
<group>
|
||||
<group>
|
||||
<field name="mail_mail_id"/>
|
||||
<field name="message_id"/>
|
||||
<field name="opened"/>
|
||||
<field name="replied"/>
|
||||
<field name="bounced"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="mass_mailing_id"/>
|
||||
<field name="mass_mailing_campaign_id"/>
|
||||
<field name="template_id"/>
|
||||
<field name="model"/>
|
||||
<field name="res_id"/>
|
||||
</group>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_view_mail_mail_statistics" model="ir.actions.act_window">
|
||||
<field name="name">Mail Statistics</field>
|
||||
<field name="res_model">mail.mail.statistics</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
|
||||
<!-- Top menu item -->
|
||||
<menuitem name="Marketing" id="base.marketing_menu" sequence="85"/>
|
||||
|
||||
<!-- Add in marketing -->
|
||||
<menuitem name="Mass Mailing" id="mass_mailing_campaign"
|
||||
parent="base.marketing_menu" sequence="1"/>
|
||||
<menuitem name="Campaigns" id="menu_email_campaigns"
|
||||
parent="mass_mailing_campaign" sequence="1"
|
||||
action="action_view_mass_mailing_campaigns"/>
|
||||
<menuitem name="Mass Mailings" id="menu_email_mass_mailings"
|
||||
parent="mass_mailing_campaign" sequence="2"
|
||||
action="action_view_mass_mailings"/>
|
||||
|
||||
<!-- Add in Technical/Email -->
|
||||
<menuitem name="Mail Statistics" id="menu_email_statistics"
|
||||
parent="base.menu_email" sequence="50"
|
||||
action="action_view_mail_mail_statistics"/>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
@@ -0,0 +1,6 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_mass_mailing_campaign,mail.mass_mailing.campaign,model_mail_mass_mailing_campaign,,1,1,1,0
|
||||
access_mass_mailing_campaign_system,mail.mass_mailing.campaign.system,model_mail_mass_mailing_campaign,base.group_system,1,1,1,1
|
||||
access_mass_mailing,mail.mass_mailing,model_mail_mass_mailing,,1,1,1,0
|
||||
access_mass_mailing_system,mail.mass_mailing.system,model_mail_mass_mailing,base.group_system,1,1,1,1
|
||||
access_mail_mail_statistics,mail.mail.statistics,model_mail_mail_statistics,,1,1,1,1
|
||||
|
@@ -0,0 +1,55 @@
|
||||
.openerp .oe_kanban_view .oe_kanban_mass_mailing.oe_kanban_mass_mailing_campaign {
|
||||
width: 540px;
|
||||
}
|
||||
|
||||
.openerp .oe_kanban_view .oe_kanban_mass_mailing.oe_kanban_mass_mailing_segment {
|
||||
width: 270px;
|
||||
}
|
||||
|
||||
.openerp .oe_kanban_view .oe_kanban_mass_mailing .oe_mail_stats {
|
||||
width: 120px;
|
||||
display: inline-block;
|
||||
margin: 2px 5px 0px 5px;
|
||||
text-align: center;
|
||||
border: 1px solid rgba(0, 0, 0, 0.16);
|
||||
-webkit-border-radius: 2px;
|
||||
border-radius: 2px;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
.openerp .oe_kanban_view .oe_kanban_mass_mailing .oe_mail_result {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.openerp .oe_kanban_view .oe_kanban_mass_mailing .oe_gauge {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.openerp .oe_kanban_view .oe_kanban_mass_mailing .oe_kanban_content div.oe_sparkline_container {
|
||||
height: 60px;
|
||||
width: 120px;
|
||||
display: inline-block;
|
||||
margin: 8px 5px 0px 5px;
|
||||
}
|
||||
|
||||
.openerp .oe_kanban_view .oe_kanban_mass_mailing .oe_sparkline_bar_title {
|
||||
text-align: center;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.openerp .oe_kanban_view .oe_kanban_mass_mailing .oe_sparkline_bar {
|
||||
width: 100px;
|
||||
height: 60px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/*
|
||||
* Campaign related CSS
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Segment related CSS
|
||||
*/
|
||||
@@ -0,0 +1,13 @@
|
||||
openerp.mass_mailing = function(openerp) {
|
||||
|
||||
openerp.web_kanban.KanbanRecord.include({
|
||||
on_card_clicked: function (event) {
|
||||
if (this.view.dataset.model === 'mail.mass_mailing.campaign') {
|
||||
this.$('.oe_mass_mailings a').first().click();
|
||||
} else {
|
||||
this._super.apply(this, arguments);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Business Applications
|
||||
# Copyright (C) 2013-Today OpenERP SA (<http://www.openerp.com>)
|
||||
#
|
||||
# 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/>
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.addons.mass_mailing.tests import test_mail
|
||||
|
||||
checks = [
|
||||
test_mail,
|
||||
]
|
||||
@@ -0,0 +1,29 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Business Applications
|
||||
# Copyright (C) 2013-Today OpenERP SA (<http://www.openerp.com>)
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.addons.mail.tests.common import TestMail
|
||||
|
||||
|
||||
class test_message_compose(TestMail):
|
||||
|
||||
def test_OO_mail_mail_tracking(self):
|
||||
""" Tests designed for mail_mail tracking (opened, replied, bounced) """
|
||||
pass
|
||||
@@ -0,0 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2013-today OpenERP SA (<http://www.openerp.com>)
|
||||
#
|
||||
# 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/>
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import mail_compose_message
|
||||
import mail_mass_mailing_create_segment
|
||||
@@ -0,0 +1,61 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2013-today OpenERP SA (<http://www.openerp.com>)
|
||||
#
|
||||
# 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/>
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.osv import osv, fields
|
||||
|
||||
|
||||
class MailComposeMessage(osv.TransientModel):
|
||||
"""Add concept of mass mailing campaign to the mail.compose.message wizard
|
||||
"""
|
||||
_inherit = 'mail.compose.message'
|
||||
|
||||
_columns = {
|
||||
'mass_mailing_campaign_id': fields.many2one(
|
||||
'mail.mass_mailing.campaign', 'Mass mailing campaign',
|
||||
),
|
||||
'mass_mailing_id': fields.many2one(
|
||||
'mail.mass_mailing', 'Mass mailing',
|
||||
domain="[('mass_mailing_campaign_id', '=', mass_mailing_campaign_id)]",
|
||||
),
|
||||
}
|
||||
|
||||
def get_mail_values(self, cr, uid, wizard, res_ids, context=None):
|
||||
""" Override method that generated the mail content by creating the
|
||||
mail.mail.statistics values in the o2m of mail_mail, when doing pure
|
||||
email mass mailing. """
|
||||
res = super(MailComposeMessage, self).get_mail_values(cr, uid, wizard, res_ids, context=context)
|
||||
if wizard.composition_mode == 'mass_mail' and wizard.mass_mailing_campaign_id: # TODO: which kind of mass mailing ?
|
||||
current_date = fields.datetime.now()
|
||||
mass_mailing_id = self.pool['mail.mass_mailing'].create(
|
||||
cr, uid, {
|
||||
'mass_mailing_campaign_id': wizard.mass_mailing_campaign_id.id,
|
||||
'name': '%s-%s' % (wizard.mass_mailing_campaign_id.name, current_date),
|
||||
'date': current_date,
|
||||
'domain': wizard.active_domain,
|
||||
'template_id': wizard.template_id and wizard.template_id.id or False,
|
||||
}, context=context)
|
||||
for res_id in res_ids:
|
||||
res[res_id]['statistics_ids'] = [(0, 0, {
|
||||
'model': wizard.model,
|
||||
'res_id': res_id,
|
||||
'mass_mailing_id': mass_mailing_id,
|
||||
})]
|
||||
return res
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<!-- Add mass mail campaign to the mail.compose.message form view -->
|
||||
<record model="ir.ui.view" id="email_compose_form_mass_mailing">
|
||||
<field name="name">mail.compose.message.form.mass_mailing</field>
|
||||
<field name="model">mail.compose.message</field>
|
||||
<field name="inherit_id" ref="mail.email_compose_message_wizard_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='notify']" position="after">
|
||||
<field name="mass_mailing_campaign_id"
|
||||
attrs="{'invisible': [('composition_mode', '!=', 'mass_mail')]}"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
@@ -0,0 +1,133 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2013-Today OpenERP SA (<http://www.openerp.com>)
|
||||
#
|
||||
# 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/>
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.osv import osv, fields
|
||||
|
||||
from openerp.tools.translate import _
|
||||
|
||||
|
||||
class MailMassMailingCreate(osv.TransientModel):
|
||||
"""Wizard to help creating mass mailing waves for a campaign. """
|
||||
|
||||
_name = 'mail.mass_mailing.create'
|
||||
_description = 'Mass mailing creation'
|
||||
|
||||
_columns = {
|
||||
'mass_mailing_campaign_id': fields.many2one(
|
||||
'mail.mass_mailing.campaign', 'Mass mailing campaign',
|
||||
required=True,
|
||||
),
|
||||
'model_id': fields.many2one(
|
||||
'ir.model', 'Document',
|
||||
required=True,
|
||||
help='Document on which the mass mailing will run. This must be a '
|
||||
'valid OpenERP model.',
|
||||
),
|
||||
'model_model': fields.related(
|
||||
'model_id', 'name',
|
||||
type='char', string='Model Name'
|
||||
),
|
||||
'filter_id': fields.many2one(
|
||||
'ir.filters', 'Filter',
|
||||
required=True,
|
||||
domain="[('model_id', '=', model_model)]",
|
||||
help='Filter to be applied on the document to find the records to be '
|
||||
'mailed.',
|
||||
),
|
||||
'domain': fields.related(
|
||||
'filter_id', 'domain',
|
||||
type='char', string='Domain',
|
||||
),
|
||||
'template_id': fields.many2one(
|
||||
'email.template', 'Template', required=True,
|
||||
domain="[('model_id', '=', model_id)]",
|
||||
),
|
||||
'name': fields.char(
|
||||
'Mailing Name', required=True,
|
||||
help='Name of the mass mailing.',
|
||||
),
|
||||
'mass_mailing_id': fields.many2one(
|
||||
'mail.mass_mailing', 'Mass Mailing',
|
||||
),
|
||||
}
|
||||
|
||||
def _get_default_model_id(self, cr, uid, context=None):
|
||||
model_ids = self.pool['ir.model'].search(cr, uid, [('model', '=', 'res.partner')], context=context)
|
||||
return model_ids and model_ids[0] or False
|
||||
|
||||
_defaults = {
|
||||
'model_id': lambda self, cr, uid, ctx=None: self._get_default_model_id(cr, uid, context=ctx),
|
||||
}
|
||||
|
||||
def on_change_model_id(self, cr, uid, ids, model_id, context=None):
|
||||
if model_id:
|
||||
model_model = self.pool['ir.model'].browse(cr, uid, model_id, context=context).model
|
||||
else:
|
||||
model_model = False
|
||||
return {'value': {'model_model': model_model}}
|
||||
|
||||
def on_change_filter_id(self, cr, uid, ids, filter_id, context=None):
|
||||
if filter_id:
|
||||
domain = self.pool['ir.filters'].browse(cr, uid, filter_id, context=context).domain
|
||||
else:
|
||||
domain = False
|
||||
return {'value': {'domain': domain}}
|
||||
|
||||
def create_mass_mailing(self, cr, uid, ids, context=None):
|
||||
""" Create a mass mailing based on wizard data, and update the wizard """
|
||||
for wizard in self.browse(cr, uid, ids, context=context):
|
||||
mass_mailing_values = {
|
||||
'name': wizard.name,
|
||||
'mass_mailing_campaign_id': wizard.mass_mailing_campaign_id.id,
|
||||
'domain': wizard.domain,
|
||||
'template_id': wizard.template_id.id,
|
||||
}
|
||||
mass_mailing_id = self.pool['mail.mass_mailing'].create(cr, uid, mass_mailing_values, context=context)
|
||||
self.write(cr, uid, [wizard.id], {'mass_mailing_id': mass_mailing_id}, context=context)
|
||||
return True
|
||||
|
||||
def launch_composer(self, cr, uid, ids, context=None):
|
||||
""" Main wizard action: create a new mailing and launch the mail.compose.message
|
||||
email composer with wizard data. """
|
||||
self.create_mass_mailing(cr, uid, ids, context=context)
|
||||
|
||||
wizard = self.browse(cr, uid, ids[0], context=context)
|
||||
ctx = dict(context)
|
||||
ctx.update({
|
||||
'default_composition_mode': 'mass_mail',
|
||||
'default_template_id': wizard.template_id.id,
|
||||
'default_use_mass_mailing_campaign': True,
|
||||
'default_use_active_domain': True,
|
||||
'default_active_domain': wizard.domain,
|
||||
'default_mass_mailing_campaign_id': wizard.mass_mailing_campaign_id.id,
|
||||
'default_mass_mailing_id': wizard.mass_mailing_id.id,
|
||||
})
|
||||
return {
|
||||
'name': _('Compose Email for Mass Mailing'),
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'mail.compose.message',
|
||||
'views': [(False, 'form')],
|
||||
'view_id': False,
|
||||
'target': 'new',
|
||||
'context': ctx,
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<!-- Wizard form view -->
|
||||
<record model="ir.ui.view" id="view_mail_mass_mailing_create_form">
|
||||
<field name="name">mail.mass_mailing.create.form</field>
|
||||
<field name="model">mail.mass_mailing.create</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Create a Mass Mailing" version="7.0">
|
||||
<group>
|
||||
<field name="model_model" invisible="1"/>
|
||||
<field name="domain" invisible="1"/>
|
||||
|
||||
<label for="mass_mailing_campaign_id"/>
|
||||
<div>
|
||||
<field name="mass_mailing_campaign_id"/>
|
||||
<p class="oe_grey"
|
||||
attrs="{'invisible': [('mass_mailing_campaign_id', '!=', False)]}">
|
||||
Please choose a mass mailing campaign that will hold the new mailing.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<label for="model_id"/>
|
||||
<div>
|
||||
<field name="model_id"
|
||||
on_change="on_change_model_id(model_id, context)"/>
|
||||
<p class="oe_grey"
|
||||
attrs="{'invisible': [('model_id', '!=', False)]}">
|
||||
Please choose a model on which you will run the mass mailing.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<label for="filter_id"/>
|
||||
<div>
|
||||
<field name="filter_id"
|
||||
on_change="on_change_filter_id(filter_id, context)"/>
|
||||
<p class="oe_grey"
|
||||
attrs="{'invisible': [('filter_id', '!=', False)]}">
|
||||
Please choose a filter that will be applied on the model
|
||||
to find the records on which you will run the mass mailing.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<label for="model_id"/>
|
||||
<div>
|
||||
<field name="template_id"/>
|
||||
<p class="oe_grey"
|
||||
attrs="{'invisible': [('template_id', '!=', False)]}">
|
||||
Please choose the template to use to render the emails
|
||||
to send.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<label for="name"/>
|
||||
<div>
|
||||
<field name="name"/>
|
||||
<p class="oe_grey"
|
||||
attrs="{'invisible': [('name', '!=', False)]}">
|
||||
Please choose the name of the mailing.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button name="launch_composer" type="object"
|
||||
string="Create mailing and launch email composer"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="action_mail_mass_mailing_create">
|
||||
<field name="name">Create Mass Mailing</field>
|
||||
<field name="res_model">mail.mass_mailing.create</field>
|
||||
<field name="src_model">mail.mass_mailing.campaign</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
@@ -19,12 +19,12 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.addons.mail.tests.test_mail_base import TestMailBase
|
||||
from openerp.addons.mail.tests.common import TestMail
|
||||
from openerp.osv.orm import except_orm
|
||||
from openerp.tools.misc import mute_logger
|
||||
|
||||
|
||||
class test_portal(TestMailBase):
|
||||
class test_portal(TestMail):
|
||||
|
||||
def setUp(self):
|
||||
super(test_portal, self).setUp()
|
||||
|
||||
@@ -19,10 +19,10 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.addons.mail.tests.test_mail_base import TestMailBase
|
||||
from openerp.addons.mail.tests.common import TestMail
|
||||
|
||||
|
||||
class TestProjectBase(TestMailBase):
|
||||
class TestProjectBase(TestMail):
|
||||
|
||||
def setUp(self):
|
||||
super(TestProjectBase, self).setUp()
|
||||
|
||||
@@ -14,9 +14,6 @@
|
||||
<menuitem id="menu_purchase_config_purchase" name="Configuration"
|
||||
groups="group_purchase_manager"
|
||||
parent="base.menu_purchase_root" sequence="100"/>
|
||||
<menuitem
|
||||
action="product.product_pricelist_action_for_purchase" id="menu_product_pricelist_action2_purchase"
|
||||
parent="menu_purchase_config_purchase" sequence="10" groups="product.group_purchase_pricelist" />
|
||||
|
||||
<record id="purchase_pricelist_version_action" model="ir.actions.act_window">
|
||||
<field name="name">Pricelist Versions</field>
|
||||
@@ -40,10 +37,13 @@
|
||||
id="menu_purchase_config_pricelist" name="Pricelists"
|
||||
parent="menu_purchase_config_purchase" sequence="50" groups="product.group_purchase_pricelist"/>
|
||||
|
||||
<menuitem
|
||||
action="product.product_pricelist_action_for_purchase" id="menu_product_pricelist_action2_purchase"
|
||||
parent="menu_purchase_config_pricelist" sequence="1" groups="product.group_purchase_pricelist" />
|
||||
|
||||
<menuitem
|
||||
action="purchase_pricelist_version_action" id="menu_purchase_pricelist_version_action"
|
||||
parent="menu_purchase_config_pricelist" sequence="2" groups="product.group_purchase_pricelist"/>
|
||||
|
||||
|
||||
<menuitem
|
||||
action="product.product_price_type_action" id="menu_product_pricelist_action2_purchase_type"
|
||||
@@ -73,7 +73,7 @@
|
||||
parent="menu_purchase_config_purchase"/>
|
||||
|
||||
<menuitem
|
||||
action="base.action_partner_category_form" id="menu_partner_categories_in_form" name="Partner Categories"
|
||||
action="base.action_partner_category_form" id="menu_partner_categories_in_form" name="Partner Tags"
|
||||
parent="purchase.menu_purchase_partner_cat" groups="base.group_no_one"/>
|
||||
|
||||
<!--Supplier menu-->
|
||||
|
||||
Reference in New Issue
Block a user