summaryrefslogtreecommitdiff
path: root/addons/mail
diff options
context:
space:
mode:
Diffstat (limited to 'addons/mail')
-rw-r--r--addons/mail/mail_alias.py101
-rw-r--r--addons/mail/mail_alias_view.xml26
-rw-r--r--addons/mail/mail_group.py22
-rw-r--r--addons/mail/mail_group_view.xml24
-rw-r--r--addons/mail/mail_thread.py344
-rw-r--r--addons/mail/res_users.py29
-rw-r--r--addons/mail/res_users_view.xml34
-rw-r--r--addons/mail/static/src/css/mail.css10
-rw-r--r--addons/mail/static/src/js/mail.js11
-rw-r--r--addons/mail/tests/test_mail_base.py6
-rw-r--r--addons/mail/tests/test_mail_features.py6
-rw-r--r--addons/mail/tests/test_mail_gateway.py230
12 files changed, 509 insertions, 334 deletions
diff --git a/addons/mail/mail_alias.py b/addons/mail/mail_alias.py
index 870695e074e..97d2842d15c 100644
--- a/addons/mail/mail_alias.py
+++ b/addons/mail/mail_alias.py
@@ -39,6 +39,7 @@ def remove_accents(input_str):
nkfd_form = unicodedata.normalize('NFKD', input_str)
return u''.join([c for c in nkfd_form if not unicodedata.combining(c)])
+
class mail_alias(osv.Model):
"""A Mail Alias is a mapping of an email address with a given OpenERP Document
model. It is used by OpenERP's mail gateway when processing incoming emails
@@ -47,7 +48,7 @@ class mail_alias(osv.Model):
of that alias. If the message is a reply it will be attached to the
existing discussion on the corresponding record, otherwise a new
record of the corresponding model will be created.
-
+
This is meant to be used in combination with a catch-all email configuration
on the company's mail server, so that as soon as a new mail.alias is
created, it becomes immediately usable and OpenERP will accept email for it.
@@ -63,9 +64,8 @@ class mail_alias(osv.Model):
return dict.fromkeys(ids, domain or "")
_columns = {
- 'alias_name': fields.char('Alias', required=True,
- help="The name of the email alias, e.g. 'jobs' "
- "if you want to catch emails for <jobs@example.my.openerp.com>",),
+ 'alias_name': fields.char('Alias',
+ help="The name of the email alias, e.g. 'jobs' if you want to catch emails for <jobs@example.my.openerp.com>",),
'alias_model_id': fields.many2one('ir.model', 'Aliased Model', required=True, ondelete="cascade",
help="The model (OpenERP Document Kind) to which this alias "
"corresponds. Any incoming email that does not reply to an "
@@ -87,13 +87,29 @@ class mail_alias(osv.Model):
"messages will be attached, even if they did not reply to it. "
"If set, this will disable the creation of new records completely."),
'alias_domain': fields.function(_get_alias_domain, string="Alias domain", type='char', size=None),
+ 'alias_parent_model_id': fields.many2one('ir.model', 'Parent Model',
+ help="Parent model holding the alias. The model holding the alias reference\n"
+ "is not necessarily the model given by alias_model_id\n"
+ "(example: project (parent_model) and task (model))"),
+ 'alias_parent_thread_id': fields.integer('Parent Record Thread ID',
+ help="ID of the parent record holding the alias (example: project holding the task creation alias)"),
+ 'alias_contact': fields.selection([
+ ('everyone', 'Everyone'),
+ ('partners', 'Authenticated Partners'),
+ ('followers', 'Followers only'),
+ ], string='Alias Contact Security', required=True,
+ help="Policy to post a message on the document using the mailgateway.\n"
+ "- everyone: everyone can post\n"
+ "- partners: only authenticated partners\n"
+ "- followers: only followers of the related document\n"),
}
_defaults = {
'alias_defaults': '{}',
- 'alias_user_id': lambda self,cr,uid,context: uid,
+ 'alias_user_id': lambda self, cr, uid, context: uid,
# looks better when creating new aliases - even if the field is informative only
- 'alias_domain': lambda self,cr,uid,context: self._get_alias_domain(cr, SUPERUSER_ID,[1],None,None)[1]
+ 'alias_domain': lambda self, cr, uid, context: self._get_alias_domain(cr, SUPERUSER_ID, [1], None, None)[1],
+ 'alias_contact': 'everyone',
}
_sql_constraints = [
@@ -139,13 +155,15 @@ class mail_alias(osv.Model):
return new_name
def migrate_to_alias(self, cr, child_model_name, child_table_name, child_model_auto_init_fct,
- alias_id_column, alias_key, alias_prefix='', alias_force_key='', alias_defaults={}, context=None):
+ alias_model_name, alias_id_column, alias_key, alias_prefix='', alias_force_key='', alias_defaults={},
+ alias_generate_name=False, context=None):
""" Installation hook to create aliases for all users and avoid constraint errors.
:param child_model_name: model name of the child class (i.e. res.users)
:param child_table_name: table name of the child class (i.e. res_users)
:param child_model_auto_init_fct: pointer to the _auto_init function
(i.e. super(res_users,self)._auto_init(cr, context=context))
+ :param alias_model_name: name of the aliased model
:param alias_id_column: alias_id column (i.e. self._columns['alias_id'])
:param alias_key: name of the column used for the unique name (i.e. 'login')
:param alias_prefix: prefix for the unique name (i.e. 'jobs' + ...)
@@ -153,6 +171,8 @@ class mail_alias(osv.Model):
if empty string, not taken into account
:param alias_defaults: dict, keys = mail.alias columns, values = child
model column name used for default values (i.e. {'job_id': 'id'})
+ :param alias_generate_name: automatically generate alias name using prefix / alias key;
+ default alias_name value is False because since 8.0 it is not required anymore
"""
if context is None:
context = {}
@@ -170,13 +190,17 @@ class mail_alias(osv.Model):
no_alias_ids = child_class_model.search(cr, SUPERUSER_ID, [('alias_id', '=', False)], context={'active_test': False})
# Use read() not browse(), to avoid prefetching uninitialized inherited fields
for obj_data in child_class_model.read(cr, SUPERUSER_ID, no_alias_ids, [alias_key]):
- alias_vals = {'alias_name': '%s%s' % (alias_prefix, obj_data[alias_key])}
+ alias_vals = {'alias_name': False}
+ if alias_generate_name:
+ alias_vals['alias_name'] = '%s%s' % (alias_prefix, obj_data[alias_key])
if alias_force_key:
alias_vals['alias_force_thread_id'] = obj_data[alias_force_key]
alias_vals['alias_defaults'] = dict((k, obj_data[v]) for k, v in alias_defaults.iteritems())
- alias_id = mail_alias.create_unique_alias(cr, SUPERUSER_ID, alias_vals, model_name=context.get('alias_model_name', child_model_name))
+ alias_vals['alias_parent_thread_id'] = obj_data['id']
+ alias_create_ctx = dict(context, alias_model_name=alias_model_name, alias_parent_model_name=child_model_name)
+ alias_id = mail_alias.create(cr, SUPERUSER_ID, alias_vals, context=alias_create_ctx)
child_class_model.write(cr, SUPERUSER_ID, obj_data['id'], {'alias_id': alias_id})
- _logger.info('Mail alias created for %s %s (uid %s)', child_model_name, obj_data[alias_key], obj_data['id'])
+ _logger.info('Mail alias created for %s %s (id %s)', child_model_name, obj_data[alias_key], obj_data['id'])
# Finally attempt to reinstate the missing constraint
try:
@@ -189,22 +213,53 @@ class mail_alias(osv.Model):
# set back the unique alias_id constraint
alias_id_column.required = True
-
return res
- def create_unique_alias(self, cr, uid, vals, model_name=None, context=None):
- """Creates an email.alias record according to the values provided in ``vals``,
- with 2 alterations: the ``alias_name`` value may be suffixed in order to
- make it unique (and certain unsafe characters replaced), and
- he ``alias_model_id`` value will set to the model ID of the ``model_name``
- value, if provided,
+ def create(self, cr, uid, vals, context=None):
+ """ Creates an email.alias record according to the values provided in ``vals``,
+ with 2 alterations: the ``alias_name`` value may be suffixed in order to
+ make it unique (and certain unsafe characters replaced), and
+ he ``alias_model_id`` value will set to the model ID of the ``model_name``
+ context value, if provided.
"""
- # when an alias name appears to already be an email, we keep the local part only
- alias_name = remove_accents(vals['alias_name']).lower().split('@')[0]
- alias_name = re.sub(r'[^\w+.]+', '-', alias_name)
- alias_name = self._find_unique(cr, uid, alias_name, context=context)
- vals['alias_name'] = alias_name
+ if context is None:
+ context = {}
+ model_name = context.get('alias_model_name')
+ parent_model_name = context.get('alias_parent_model_name')
+ if vals.get('alias_name'):
+ # when an alias name appears to already be an email, we keep the local part only
+ alias_name = remove_accents(vals['alias_name']).lower().split('@')[0]
+ alias_name = re.sub(r'[^\w+.]+', '-', alias_name)
+ alias_name = self._find_unique(cr, uid, alias_name, context=context)
+ vals['alias_name'] = alias_name
if model_name:
model_id = self.pool.get('ir.model').search(cr, uid, [('model', '=', model_name)], context=context)[0]
vals['alias_model_id'] = model_id
- return self.create(cr, uid, vals, context=context)
+ if parent_model_name:
+ model_id = self.pool.get('ir.model').search(cr, uid, [('model', '=', parent_model_name)], context=context)[0]
+ vals['alias_parent_model_id'] = model_id
+ return super(mail_alias, self).create(cr, uid, vals, context=context)
+
+ def open_document(self, cr, uid, ids, context=None):
+ alias = self.browse(cr, uid, ids, context=context)[0]
+ if not alias.alias_model_id or not alias.alias_force_thread_id:
+ return False
+ return {
+ 'view_type': 'form',
+ 'view_mode': 'form',
+ 'res_model': alias.alias_model_id.model,
+ 'res_id': alias.alias_force_thread_id,
+ 'type': 'ir.actions.act_window',
+ }
+
+ def open_parent_document(self, cr, uid, ids, context=None):
+ alias = self.browse(cr, uid, ids, context=context)[0]
+ if not alias.alias_parent_model_id or not alias.alias_parent_thread_id:
+ return False
+ return {
+ 'view_type': 'form',
+ 'view_mode': 'form',
+ 'res_model': alias.alias_parent_model_id.model,
+ 'res_id': alias.alias_parent_thread_id,
+ 'type': 'ir.actions.act_window',
+ }
diff --git a/addons/mail/mail_alias_view.xml b/addons/mail/mail_alias_view.xml
index e0d8e173ebc..248bf7e8f3a 100644
--- a/addons/mail/mail_alias_view.xml
+++ b/addons/mail/mail_alias_view.xml
@@ -9,13 +9,23 @@
<field name="arch" type="xml">
<form string="Alias" version="7.0">
<sheet>
- <label for="alias_name" class="oe_edit_only"/>
<h2><field name="alias_name" class="oe_inline"/>@<field name="alias_domain" class="oe_inline"/></h2>
+ <div class="oe_right oe_button_box">
+ <button name="open_document" string="Open Document"
+ type="object" class="oe_link"
+ attrs="{'invisible': ['|', ('alias_model_id', '=', False), ('alias_force_thread_id', '=', 0)]}"/>
+ <button name="open_parent_document" string="Open Parent Document"
+ type="object" class="oe_link"
+ attrs="{'invisible': ['|', ('alias_parent_model_id', '=', False), ('alias_parent_thread_id', '=', 0)]}"/>
+ </div>
<group>
<field name="alias_model_id"/>
- <field name="alias_user_id"/>
<field name="alias_force_thread_id"/>
<field name="alias_defaults"/>
+ <field name="alias_contact"/>
+ <field name="alias_user_id"/>
+ <field name="alias_parent_model_id"/>
+ <field name="alias_parent_thread_id"/>
</group>
</sheet>
</form>
@@ -32,6 +42,7 @@
<field name="alias_model_id"/>
<field name="alias_user_id"/>
<field name="alias_defaults"/>
+ <field name="alias_contact"/>
</tree>
</field>
</record>
@@ -44,8 +55,13 @@
<search string="Search Alias">
<field name="alias_name"/>
<field name="alias_model_id"/>
+ <field name="alias_force_thread_id"/>
+ <field name="alias_parent_model_id"/>
+ <field name="alias_parent_thread_id"/>
+ <separator/>
+ <filter string="Active" name="active" domain="[('alias_name', '!=', False)]"/>
<group expand="0" string="Group By...">
- <filter string="User" name="User" icon="terp-personal" context="{'group_by':'alias_user_id'}"/>
+ <filter string="User" name="User" context="{'group_by':'alias_user_id'}"/>
<filter string="Model" name="Model" context="{'group_by':'alias_model_id'}"/>
</group>
</search>
@@ -55,6 +71,10 @@
<record id="action_view_mail_alias" model="ir.actions.act_window">
<field name="name">Aliases</field>
<field name="res_model">mail.alias</field>
+ <field name="context">{
+ 'search_default_active': True,
+ }
+ </field>
</record>
<menuitem id="mail_alias_menu"
diff --git a/addons/mail/mail_group.py b/addons/mail/mail_group.py
index 9ca3738bcb2..9e363065481 100644
--- a/addons/mail/mail_group.py
+++ b/addons/mail/mail_group.py
@@ -93,17 +93,16 @@ class mail_group(osv.Model):
'public': 'groups',
'group_public_id': _get_default_employee_group,
'image': _get_default_image,
- 'alias_domain': False, # always hide alias during creation
}
def _generate_header_description(self, cr, uid, group, context=None):
header = ''
if group.description:
header = '%s' % group.description
- if group.alias_id and group.alias_id.alias_name and group.alias_id.alias_domain:
+ if group.alias_id and group.alias_name and group.alias_domain:
if header:
header = '%s<br/>' % header
- return '%sGroup email gateway: %s@%s' % (header, group.alias_id.alias_name, group.alias_id.alias_domain)
+ return '%sGroup email gateway: %s@%s' % (header, group.alias_name, group.alias_domain)
return header
def _subscribe_users(self, cr, uid, ids, context=None):
@@ -114,15 +113,8 @@ class mail_group(osv.Model):
self.message_subscribe(cr, uid, ids, partner_ids, context=context)
def create(self, cr, uid, vals, context=None):
- mail_alias = self.pool.get('mail.alias')
- if not vals.get('alias_id'):
- vals.pop('alias_name', None) # prevent errors during copy()
- alias_id = mail_alias.create_unique_alias(cr, uid,
- # Using '+' allows using subaddressing for those who don't
- # have a catchall domain setup.
- {'alias_name': "group+" + vals['name']},
- model_name=self._name, context=context)
- vals['alias_id'] = alias_id
+ if context is None:
+ context = {}
# get parent menu
menu_parent = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'mail', 'mail_group_root')
@@ -134,8 +126,10 @@ class mail_group(osv.Model):
vals['menu_id'] = menu_id
# Create group and alias
- mail_group_id = super(mail_group, self).create(cr, uid, vals, context=context)
- mail_alias.write(cr, uid, [vals['alias_id']], {"alias_force_thread_id": mail_group_id}, context)
+ create_context = dict(context, alias_model_name=self._name, alias_parent_model_name=self._name)
+ mail_group_id = super(mail_group, self).create(cr, uid, vals, context=create_context)
+ group = self.browse(cr, uid, mail_group_id, context=context)
+ self.pool.get('mail.alias').write(cr, uid, [group.alias_id.id], {"alias_force_thread_id": mail_group_id, 'alias_parent_thread_id': mail_group_id}, context)
group = self.browse(cr, uid, mail_group_id, context=context)
# Create client action for this group and link the menu to it
diff --git a/addons/mail/mail_group_view.xml b/addons/mail/mail_group_view.xml
index 04212b2f58a..8bf61dbdb1e 100644
--- a/addons/mail/mail_group_view.xml
+++ b/addons/mail/mail_group_view.xml
@@ -44,7 +44,7 @@
<div class="oe_group_details">
<h4><a type="open"><field name="name"/></a></h4>
<div class="oe_kanban_alias" t-if="record.alias_id.value">
- <span class="oe_e">%%</span><small><field name="alias_id"/></small>
+ <span class="oe_e oe_e_alias">%%</span><small><field name="alias_id"/></small>
</div>
<div class="oe_grey">
<field name="description"/>
@@ -78,17 +78,19 @@
<label for="name" string="Group Name"/>
</div>
<h1><field name="name" readonly="0"/></h1>
- <div name="group_alias"
+ <group colspan="2" name="group_alias"
attrs="{'invisible': [('alias_domain', '=', False)]}">
- <label for="alias_id" string="Email Alias"/>
- <field name="alias_id" class="oe_inline oe_read_only" required="0" nolabel="1"/>
- <span name="edit_alias" class="oe_edit_only">
- <field name="alias_name" class="oe_inline"
- attrs="{'required': [('alias_id', '!=', False)]}"/>
- @
- <field name="alias_domain" class="oe_inline" readonly="1"/>
- </span>
- </div>
+ <label for="alias_id" string="%%" class="oe_e oe_e_alias" style="min-width: 20px;"/>
+ <div name="alias_def">
+ <field name="alias_id" class="oe_read_only oe_inline"
+ string="Email Alias" required="0"/>
+ <div class="oe_edit_only oe_inline" name="edit_alias" style="display: inline;" >
+ <field name="alias_name" class="oe_inline"/>@<field name="alias_domain" class="oe_inline" readonly="1"/>
+ </div>
+ </div>
+ <label for="alias_contact" string="V" class="oe_e oe_e_alias" style="min-width: 20px;"/>
+ <field name="alias_contact" class="oe_inline" nolabel="1"/>
+ </group>
</div>
<field name="description" placeholder="Topics discussed in this group..."/>
<div class="oe_clear"/>
diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py
index c6b0dd9f5fa..403cfab7d61 100644
--- a/addons/mail/mail_thread.py
+++ b/addons/mail/mail_thread.py
@@ -25,7 +25,6 @@ import dateutil
import email
import logging
import pytz
-import re
import time
import xmlrpclib
from email.message import Message
@@ -102,21 +101,22 @@ class mail_thread(osv.AbstractModel):
if catchall_domain and model and res_id: # specific res_id -> find its alias (i.e. section_id specified)
object_id = self.pool.get(model).browse(cr, uid, res_id, context=context)
# check that the alias effectively creates new records
- if object_id.alias_id and object_id.alias_id.alias_model_id and \
+ if object_id.alias_id and object_id.alias_id.alias_name and \
+ object_id.alias_id.alias_model_id and \
object_id.alias_id.alias_model_id.model == self._name and \
object_id.alias_id.alias_force_thread_id == 0:
alias = object_id.alias_id
elif catchall_domain and model: # no specific res_id given -> generic help message, take an example alias (i.e. alias of some section_id)
model_id = self.pool.get('ir.model').search(cr, uid, [("model", "=", self._name)], context=context)[0]
alias_obj = self.pool.get('mail.alias')
- alias_ids = alias_obj.search(cr, uid, [("alias_model_id", "=", model_id), ('alias_force_thread_id', '=', 0)], context=context, order='id ASC')
+ alias_ids = alias_obj.search(cr, uid, [("alias_model_id", "=", model_id), ("alias_name", "!=", False), ('alias_force_thread_id', '=', 0)], context=context, order='id ASC')
if alias_ids and len(alias_ids) == 1: # if several aliases -> incoherent to propose one guessed from nowhere, therefore avoid if several aliases
alias = alias_obj.browse(cr, uid, alias_ids[0], context=context)
if alias:
alias_email = alias.name_get()[0][1]
return _("""<p class='oe_view_nocontent_create'>
- Click here to add a new %(document)s or send an email to: <a href='mailto:%(email)s'>%(email)s</a>
+ Click here to add new %(document)s or send an email to: <a href='mailto:%(email)s'>%(email)s</a>
</p>
%(static_help)s"""
) % {
@@ -126,7 +126,7 @@ class mail_thread(osv.AbstractModel):
}
if document_name != 'document' and help and help.find("oe_view_nocontent_create") == -1:
- return _("<p class='oe_view_nocontent_create'>Click here to add a new %(document)s</p>%(static_help)s") % {
+ return _("<p class='oe_view_nocontent_create'>Click here to add new %(document)s</p>%(static_help)s") % {
'document': document_name,
'static_help': help or '',
}
@@ -257,7 +257,6 @@ class mail_thread(osv.AbstractModel):
def _search_is_follower(self, cr, uid, obj, name, args, context):
"""Search function for message_is_follower"""
- fol_obj = self.pool.get('mail.followers')
res = []
for field, operator, value in args:
assert field == name
@@ -564,6 +563,8 @@ class mail_thread(osv.AbstractModel):
#------------------------------------------------------
def message_get_reply_to(self, cr, uid, ids, context=None):
+ """ Returns the preferred reply-to email address that is basically
+ the alias of the document, if it exists. """
if not self._inherits.get('mail.alias'):
return [False for id in ids]
return ["%s@%s" % (record['alias_name'], record['alias_domain'])
@@ -587,27 +588,123 @@ class mail_thread(osv.AbstractModel):
def _message_find_partners(self, cr, uid, message, header_fields=['From'], context=None):
""" Find partners related to some header fields of the message.
- TDE TODO: merge me with other partner finding methods in 8.0 """
- partner_obj = self.pool.get('res.partner')
- partner_ids = []
+ :param string message: an email.message instance """
s = ', '.join([decode(message.get(h)) for h in header_fields if message.get(h)])
- for email_address in tools.email_split(s):
- related_partners = partner_obj.search(cr, uid, [('email', 'ilike', email_address), ('user_ids', '!=', False)], limit=1, context=context)
- if not related_partners:
- related_partners = partner_obj.search(cr, uid, [('email', 'ilike', email_address)], limit=1, context=context)
- partner_ids += related_partners
- return partner_ids
+ return filter(lambda x: x, self._find_partner_from_emails(cr, uid, None, tools.email_split(s), context=context))
+
+ def message_route_verify(self, cr, uid, message, message_dict, route, update_author=True, assert_model=True, create_fallback=True, context=None):
+ """ Verify route validity. Check and rules:
+ 1 - if thread_id -> check that document effectively exists; otherwise
+ fallback on a message_new by resetting thread_id
+ 2 - check that message_update exists if thread_id is set; or at least
+ that message_new exist
+ [ - find author_id if udpate_author is set]
+ 3 - if there is an alias, check alias_contact:
+ 'followers' and thread_id:
+ check on target document that the author is in the followers
+ 'followers' and alias_parent_thread_id:
+ check on alias parent document that the author is in the
+ followers
+ 'partners': check that author_id id set
+ """
- def _message_find_user_id(self, cr, uid, message, context=None):
- """ TDE TODO: check and maybe merge me with other user finding methods in 8.0 """
- from_local_part = tools.email_split(decode(message.get('From')))[0]
- # FP Note: canonification required, the minimu: .lower()
- user_ids = self.pool.get('res.users').search(cr, uid, ['|',
- ('login', '=', from_local_part),
- ('email', '=', from_local_part)], context=context)
- return user_ids[0] if user_ids else uid
+ assert isinstance(route, (list, tuple)), 'A route should be a list or a tuple'
+ assert len(route) == 5, 'A route should contain 5 elements: model, thread_id, custom_values, uid, alias record'
- def message_route(self, cr, uid, message, model=None, thread_id=None,
+ message_id = message.get('Message-Id')
+ email_from = decode_header(message, 'From')
+ author_id = message_dict.get('author_id')
+ model, thread_id, alias = route[0], route[1], route[4]
+ model_pool = None
+
+ def _create_bounce_email():
+ mail_mail = self.pool.get('mail.mail')
+ mail_id = mail_mail.create(cr, uid, {
+ 'body_html': '<div><p>Hello,</p>'
+ '<p>The following email sent to %s cannot be accepted because this is '
+ 'a private email address. Only allowed people can contact us at this address.</p></div>'
+ '<blockquote>%s</blockquote>' % (message.get('to'), message_dict.get('body')),
+ 'subject': 'Re: %s' % message.get('subject'),
+ 'email_to': message.get('from'),
+ 'auto_delete': True,
+ }, context=context)
+ mail_mail.send(cr, uid, [mail_id], context=context)
+
+ def _warn(message):
+ _logger.warning('Routing mail with Message-Id %s: route %s: %s',
+ message_id, route, message)
+
+ # Wrong model
+ if model and not model in self.pool:
+ if assert_model:
+ assert model in self.pool, 'Routing: unknown target model %s' % model
+ _warn('unknown target model %s' % model)
+ return ()
+ elif model:
+ model_pool = self.pool[model]
+
+ # Private message: should not contain any thread_id
+ if not model and thread_id:
+ if assert_model:
+ assert thread_id == 0, 'Routing: posting a message without model should be with a null res_id (private message).'
+ _warn('posting a message without model should be with a null res_id (private message), resetting thread_id')
+ thread_id = 0
+
+ # Existing Document: check if exists; if not, fallback on create if allowed
+ if thread_id and not model_pool.exists(cr, uid, thread_id):
+ if create_fallback:
+ _warn('reply to missing document (%s,%s), fall back on new document creation' % (model, thread_id))
+ thread_id = None
+ elif assert_model:
+ assert model_pool.exists(cr, uid, thread_id), 'Routing: reply to missing document (%s,%s)' % (model, thread_id)
+ else:
+ _warn('reply to missing document (%s,%s), skipping' % (model, thread_id))
+ return ()
+
+ # Existing Document: check model accepts the mailgateway
+ if thread_id and not hasattr(model_pool, 'message_update'):
+ if create_fallback:
+ _warn('model %s does not accept document update, fall back on document creation' % model)
+ thread_id = None
+ elif assert_model:
+ assert hasattr(model_pool, 'message_update'), 'Routing: model %s does not accept document update, crashing' % model
+ else:
+ _warn('model %s does not accept document update, skipping' % model)
+ return ()
+
+ # New Document: check model accepts the mailgateway
+ if not thread_id and not hasattr(model_pool, 'message_new'):
+ if assert_model:
+ assert hasattr(model_pool, 'message_new'), 'Model %s does not accept document creation, crashing' % model
+ _warn('model %s does not accept document creation, skipping' % model)
+ return ()
+
+ # Update message author if asked
+ # We do it now because we need it for aliases (contact settings)
+ if not author_id and update_author:
+ author_ids = self._find_partner_from_emails(cr, uid, thread_id, [email_from], model=model, context=context)
+ if author_ids:
+ author_id = author_ids[0]
+ message_dict['author_id'] = author_id
+
+ # Alias: check alias_contact settings
+ if alias and alias.alias_contact == 'followers' and (thread_id or alias.alias_parent_thread_id):
+ if thread_id:
+ obj = self.pool[model].browse(cr, uid, thread_id, context=context)
+ else:
+ obj = self.pool[alias.alias_parent_model_id.model].browse(cr, uid, alias.alias_parent_thread_id, context=context)
+ if not author_id or not author_id in [fol.id for fol in obj.message_follower_ids]:
+ _warn('alias %s restricted to internal followers, skipping' % alias.alias_name)
+ _create_bounce_email()
+ return ()
+ elif alias and alias.alias_contact == 'partners' and not author_id:
+ _warn('alias %s does not accept unknown author, skipping' % alias.alias_name)
+ _create_bounce_email()
+ return ()
+
+ return (model, thread_id, route[2], route[3], route[4])
+
+ def message_route(self, cr, uid, message, message_dict, model=None, thread_id=None,
custom_values=None, context=None):
"""Attempt to figure out the correct target model, thread_id,
custom_values and user_id to use for an incoming message.
@@ -627,6 +724,7 @@ class mail_thread(osv.AbstractModel):
4. If all the above fails, raise an exception.
:param string message: an email.message instance
+ :param dict message_dict: dictionary holding message variables
:param string model: the fallback model to use if the message
does not match any of the currently configured mail aliases
(may be None if a matching alias is supposed to be present)
@@ -637,9 +735,12 @@ class mail_thread(osv.AbstractModel):
:param int thread_id: optional ID of the record/thread from ``model``
to which this mail should be attached. Only used if the message
does not reply to an existing thread and does not match any mail alias.
- :return: list of [model, thread_id, custom_values, user_id]
+ :return: list of [model, thread_id, custom_values, user_id, alias]
"""
assert isinstance(message, Message), 'message must be an email.message.Message at this point'
+ fallback_model = model
+
+ # Get email.message.Message variables for future processing
message_id = message.get('Message-Id')
email_from = decode_header(message, 'From')
email_to = decode_header(message, 'To')
@@ -649,18 +750,20 @@ class mail_thread(osv.AbstractModel):
# 1. Verify if this is a reply to an existing thread
thread_references = references or in_reply_to
ref_match = thread_references and tools.reference_re.search(thread_references)
-
if ref_match:
thread_id = int(ref_match.group(1))
- model = ref_match.group(2) or model
+ model = ref_match.group(2) or fallback_model
if thread_id and model in self.pool:
model_obj = self.pool[model]
if model_obj.exists(cr, uid, thread_id) and hasattr(model_obj, 'message_update'):
_logger.info('Routing mail from %s to %s with Message-Id %s: direct reply to model: %s, thread_id: %s, custom_values: %s, uid: %s',
email_from, email_to, message_id, model, thread_id, custom_values, uid)
- return [(model, thread_id, custom_values, uid)]
+ route = self.message_route_verify(cr, uid, message, message_dict,
+ (model, thread_id, custom_values, uid, None),
+ update_author=True, assert_model=True, create_fallback=True, context=context)
+ return route and [route] or []
- # Verify whether this is a reply to a private message
+ # 2. Reply to a private message
if in_reply_to:
message_ids = self.pool.get('mail.message').search(cr, uid, [
('message_id', '=', in_reply_to),
@@ -670,9 +773,12 @@ class mail_thread(osv.AbstractModel):
message = self.pool.get('mail.message').browse(cr, uid, message_ids[0], context=context)
_logger.info('Routing mail from %s to %s with Message-Id %s: direct reply to a private message: %s, custom_values: %s, uid: %s',
email_from, email_to, message_id, message.id, custom_values, uid)
- return [(message.model, message.res_id, custom_values, uid)]
+ route = self.message_route_verify(cr, uid, message, message_dict,
+ (message.model, message.res_id, custom_values, uid, None),
+ update_author=True, assert_model=True, create_fallback=True, context=context)
+ return route and [route] or []
- # 2. Look for a matching mail.alias entry
+ # 3. Look for a matching mail.alias entry
# Delivered-To is a safe bet in most modern MTAs, but we have to fallback on To + Cc values
# for all the odd MTAs out there, as there is no standard header for the envelope's `rcpt_to` value.
rcpt_tos = \
@@ -697,14 +803,16 @@ class mail_thread(osv.AbstractModel):
# user_id = self._message_find_user_id(cr, uid, message, context=context)
user_id = uid
_logger.info('No matching user_id for the alias %s', alias.alias_name)
- routes.append((alias.alias_model_id.model, alias.alias_force_thread_id, \
- eval(alias.alias_defaults), user_id))
- _logger.info('Routing mail from %s to %s with Message-Id %s: direct alias match: %r',
- email_from, email_to, message_id, routes)
+ route = (alias.alias_model_id.model, alias.alias_force_thread_id, eval(alias.alias_defaults), user_id, alias)
+ _logger.info('Routing mail from %s to %s with Message-Id %s: direct alias match: %r',
+ email_from, email_to, message_id, route)
+ route = self.message_route_verify(cr, uid, message, message_dict, route,
+ update_author=True, assert_model=True, create_fallback=True, context=context)
+ if route:
+ routes.append(route)
return routes
- # 3. Fallback to the provided parameters, if they work
- model_pool = self.pool.get(model)
+ # 4. Fallback to the provided parameters, if they work
if not thread_id:
# Legacy: fallback to matching [ID] in the Subject
match = tools.res_re.search(decode_header(message, 'Subject'))
@@ -714,16 +822,18 @@ class mail_thread(osv.AbstractModel):
thread_id = int(thread_id)
except:
thread_id = False
- assert thread_id and hasattr(model_pool, 'message_update') or hasattr(model_pool, 'message_new'), \
+ _logger.info('Routing mail from %s to %s with Message-Id %s: fallback to model:%s, thread_id:%s, custom_values:%s, uid:%s',
+ email_from, email_to, message_id, fallback_model, thread_id, custom_values, uid)
+ route = self.message_route_verify(cr, uid, message, message_dict,
+ (fallback_model, thread_id, custom_values, uid, None),
+ update_author=True, assert_model=True, context=context)
+ if route:
+ return [route]
+
+ # AssertionError if no routes found and if no bounce occured
+ assert False, \
"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)
- if thread_id and not model_pool.exists(cr, uid, thread_id):
- _logger.warning('Received mail reply to missing document %s! Ignoring and creating new document instead for Message-Id %s',
- thread_id, message_id)
- thread_id = None
- _logger.info('Routing mail from %s to %s with Message-Id %s: fallback to model:%s, thread_id:%s, custom_values:%s, uid:%s',
- email_from, email_to, message_id, model, thread_id, custom_values, uid)
- return [(model, thread_id, custom_values, uid)]
def message_process(self, cr, uid, model, message, custom_values=None,
save_original=False, strip_attachments=False,
@@ -777,25 +887,21 @@ 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')),
], context=context)
if existing_msg_ids:
- _logger.info('Ignored mail from %s to %s with Message-Id %s:: found duplicated Message-Id during processing',
+ _logger.info('Ignored mail from %s to %s with Message-Id %s: found duplicated Message-Id during processing',
msg.get('from'), msg.get('to'), msg.get('message_id'))
return False
# find possible routes for the message
- routes = self.message_route(cr, uid, msg_txt, model,
- thread_id, custom_values,
- context=context)
-
- # postpone setting msg.partner_ids after message_post, to avoid double notifications
- partner_ids = msg.pop('partner_ids', [])
-
+ 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 in routes:
+ for model, thread_id, custom_values, user_id, alias in routes:
if self._name == 'mail.thread':
context.update({'thread_model': model})
if model:
@@ -806,11 +912,10 @@ class mail_thread(osv.AbstractModel):
# 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)
+ 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:
- nosub_ctx = dict(nosub_ctx, mail_create_nolog=True)
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."
@@ -947,7 +1052,6 @@ class mail_thread(osv.AbstractModel):
"""
msg_dict = {
'type': 'email',
- 'author_id': False,
}
if not isinstance(message, Message):
if isinstance(message, unicode):
@@ -970,12 +1074,7 @@ class mail_thread(osv.AbstractModel):
msg_dict['from'] = decode(message.get('from'))
msg_dict['to'] = decode(message.get('to'))
msg_dict['cc'] = decode(message.get('cc'))
-
- if message.get('From'):
- author_ids = self._message_find_partners(cr, uid, message, ['From'], context=context)
- if author_ids:
- msg_dict['author_id'] = author_ids[0]
- msg_dict['email_from'] = decode(message.get('from'))
+ msg_dict['email_from'] = decode(message.get('from'))
partner_ids = self._message_find_partners(cr, uid, message, ['To', 'Cc'], context=context)
msg_dict['partner_ids'] = [(4, partner_id) for partner_id in partner_ids]
@@ -1029,7 +1128,7 @@ class mail_thread(osv.AbstractModel):
partner_id, partner_name<partner_email> or partner_name, reason """
if email and not partner:
# get partner info from email
- partner_info = self.message_get_partner_info_from_emails(cr, uid, [email], context=context, res_id=obj.id)[0]
+ partner_info = self.message_partner_info_from_emails(cr, uid, obj.id, [email], context=context)[0]
if partner_info.get('partner_id'):
partner = self.pool.get('res.partner').browse(cr, SUPERUSER_ID, [partner_info.get('partner_id')], context=context)[0]
if email and email in [val[1] for val in result[obj.id]]: # already existing email -> skip
@@ -1057,53 +1156,76 @@ class mail_thread(osv.AbstractModel):
self._message_add_suggested_recipient(cr, uid, result, obj, partner=obj.user_id.partner_id, reason=self._all_columns['user_id'].column.string, context=context)
return result
- def message_get_partner_info_from_emails(self, cr, uid, emails, link_mail=False, context=None, res_id=None):
- """ Wrapper with weird order parameter because of 7.0 fix.
+ def _find_partner_from_emails(self, cr, uid, id, emails, model=None, context=None, check_followers=True):
+ """ Utility method to find partners from email addresses. The rules are :
+ 1 - check in document (model | self, id) followers
+ 2 - try to find a matching partner that is also an user
+ 3 - try to find a matching partner
- TDE TODO: remove me in 8.0 """
- return self.message_find_partner_from_emails(cr, uid, res_id, emails, link_mail=link_mail, context=context)
+ :param list emails: list of email addresses
+ :param string model: model to fetch related record; by default self
+ is used.
+ :param boolean check_followers: check in document followers
+ """
+ partner_obj = self.pool['res.partner']
+ partner_ids = []
+ obj = None
+ if id and (model or self._name != 'mail.thread') and check_followers:
+ if model:
+ obj = self.pool[model].browse(cr, uid, id, context=context)
+ else:
+ obj = self.browse(cr, uid, id, context=context)
+ for contact in emails:
+ partner_id = False
+ email_address = tools.email_split(contact)
+ if not email_address:
+ partner_ids.append(partner_id)
+ continue
+ email_address = email_address[0]
+ # first try: check in document's followers
+ if obj:
+ for follower in obj.message_follower_ids:
+ if follower.email == email_address:
+ partner_id = follower.id
+ # second try: check in partners that are also users
+ if not partner_id:
+ ids = partner_obj.search(cr, SUPERUSER_ID, [
+ ('email', 'ilike', email_address),
+ ('user_ids', '!=', False)
+ ], limit=1, context=context)
+ if ids:
+ partner_id = ids[0]
+ # third try: check in partners
+ if not partner_id:
+ ids = partner_obj.search(cr, SUPERUSER_ID, [
+ ('email', 'ilike', email_address)
+ ], limit=1, context=context)
+ if ids:
+ partner_id = ids[0]
+ partner_ids.append(partner_id)
+ return partner_ids
- def message_find_partner_from_emails(self, cr, uid, id, emails, link_mail=False, context=None):
+ def message_partner_info_from_emails(self, cr, uid, id, emails, link_mail=False, context=None):
""" Convert a list of emails into a list partner_ids and a list
new_partner_ids. The return value is non conventional because
it is meant to be used by the mail widget.
- :return dict: partner_ids and new_partner_ids
-
- TDE TODO: merge me with other partner finding methods in 8.0 """
+ :return dict: partner_ids and new_partner_ids """
mail_message_obj = self.pool.get('mail.message')
- partner_obj = self.pool.get('res.partner')
+ partner_ids = self._find_partner_from_emails(cr, uid, id, emails, context=context)
result = list()
- if id and self._name != 'mail.thread':
- obj = self.browse(cr, SUPERUSER_ID, id, context=context)
- else:
- obj = None
- for email in emails:
- partner_info = {'full_name': email, 'partner_id': False}
- m = re.search(r"((.+?)\s*<)?([^<>]+@[^<>]+)>?", email, re.IGNORECASE | re.DOTALL)
- if not m:
- continue
- email_address = m.group(3)
- # first try: check in document's followers
- if obj:
- for follower in obj.message_follower_ids:
- if follower.email == email_address:
- partner_info['partner_id'] = follower.id
- # second try: check in partners
- if not partner_info.get('partner_id'):
- ids = partner_obj.search(cr, SUPERUSER_ID, [('email', 'ilike', email_address), ('user_ids', '!=', False)], limit=1, context=context)
- if not ids:
- ids = partner_obj.search(cr, SUPERUSER_ID, [('email', 'ilike', email_address)], limit=1, context=context)
- if ids:
- partner_info['partner_id'] = ids[0]
+ for idx in range(len(emails)):
+ email_address = emails[idx]
+ partner_id = partner_ids[idx]
+ partner_info = {'full_name': email_address, 'partner_id': partner_id}
result.append(partner_info)
# link mail with this from mail to the new partner id
if link_mail and partner_info['partner_id']:
message_ids = mail_message_obj.search(cr, SUPERUSER_ID, [
'|',
- ('email_from', '=', email),
- ('email_from', 'ilike', '<%s>' % email),
+ ('email_from', '=', email_address),
+ ('email_from', 'ilike', '<%s>' % email_address),
('author_id', '=', False)
], context=context)
if message_ids:
@@ -1156,18 +1278,7 @@ class mail_thread(osv.AbstractModel):
del context['thread_model']
return self.pool[model].message_post(cr, uid, thread_id, body=body, subject=subject, type=type, subtype=subtype, parent_id=parent_id, attachments=attachments, context=context, content_subtype=content_subtype, **kwargs)
- # 0: Parse email-from, try to find a better author_id based on document's followers for incoming emails
- email_from = kwargs.get('email_from')
- if email_from and thread_id and type == 'email' and kwargs.get('author_id'):
- email_list = tools.email_split(email_from)
- doc = self.browse(cr, uid, thread_id, context=context)
- if email_list and doc:
- author_ids = self.pool.get('res.partner').search(cr, uid, [
- ('email', 'ilike', email_list[0]),
- ('id', 'in', [f.id for f in doc.message_follower_ids])
- ], limit=1, context=context)
- if author_ids:
- kwargs['author_id'] = author_ids[0]
+ #0: Find the message's author, because we need it for private discussion
author_id = kwargs.get('author_id')
if author_id is None: # keep False values
author_id = self.pool.get('mail.message')._get_default_author(cr, uid, context=context)
@@ -1279,21 +1390,6 @@ class mail_thread(osv.AbstractModel):
return msg_id
#------------------------------------------------------
- # Compatibility methods: do not use
- # TDE TODO: remove me in 8.0
- #------------------------------------------------------
-
- def message_create_partners_from_emails(self, cr, uid, emails, context=None):
- return {'partner_ids': [], 'new_partner_ids': []}
-
- def message_post_user_api(self, cr, uid, thread_id, body='', parent_id=False,
- attachment_ids=None, content_subtype='plaintext',
- context=None, **kwargs):
- return self.message_post(cr, uid, thread_id, body=body, parent_id=parent_id,
- attachment_ids=attachment_ids, content_subtype=content_subtype,
- context=context, **kwargs)
-
- #------------------------------------------------------
# Followers API
#------------------------------------------------------
diff --git a/addons/mail/res_users.py b/addons/mail/res_users.py
index b9883489ca6..7bc39c438d1 100644
--- a/addons/mail/res_users.py
+++ b/addons/mail/res_users.py
@@ -23,6 +23,7 @@ from openerp.osv import fields, osv
from openerp import SUPERUSER_ID
from openerp.tools.translate import _
+
class res_users(osv.Model):
""" Update of res.users class
- add a preference about sending emails about notifications
@@ -42,7 +43,6 @@ class res_users(osv.Model):
}
_defaults = {
- 'alias_domain': False, # always hide alias during creation
'display_groups_suggestions': True,
}
@@ -63,25 +63,20 @@ class res_users(osv.Model):
def _auto_init(self, cr, context=None):
""" Installation hook: aliases, partner following themselves """
# create aliases for all users and avoid constraint errors
- res = self.pool.get('mail.alias').migrate_to_alias(cr, self._name, self._table, super(res_users, self)._auto_init,
- self._columns['alias_id'], 'login', alias_force_key='id', context=context)
- return res
+ return self.pool.get('mail.alias').migrate_to_alias(cr, self._name, self._table, super(res_users, self)._auto_init,
+ self._name, self._columns['alias_id'], 'login', alias_force_key='id', context=context)
def create(self, cr, uid, data, context=None):
- # create default alias same as the login
if not data.get('login', False):
raise osv.except_osv(_('Invalid Action!'), _('You may not create a user. To create new users, you should use the "Settings > Users" menu.'))
+ if context is None:
+ context = {}
- mail_alias = self.pool.get('mail.alias')
- alias_id = mail_alias.create_unique_alias(cr, uid, {'alias_name': data['login']}, model_name=self._name, context=context)
- data['alias_id'] = alias_id
- data.pop('alias_name', None) # prevent errors during copy()
-
- # create user
- user_id = super(res_users, self).create(cr, uid, data, context=context)
+ create_context = dict(context, alias_model_name=self._name, alias_parent_model_name=self._name)
+ user_id = super(res_users, self).create(cr, uid, data, context=create_context)
user = self.browse(cr, uid, user_id, context=context)
- # alias
- mail_alias.write(cr, SUPERUSER_ID, [alias_id], {"alias_force_thread_id": user_id}, context)
+ self.pool.get('mail.alias').write(cr, SUPERUSER_ID, [user.alias_id.id], {"alias_force_thread_id": user_id, "alias_parent_thread_id": user_id}, context)
+
# create a welcome message
self._create_welcome_message(cr, uid, user, context=context)
return user_id
@@ -95,12 +90,6 @@ class res_users(osv.Model):
return self.pool.get('res.partner').message_post(cr, SUPERUSER_ID, [user.partner_id.id],
body=body, context=context)
- def write(self, cr, uid, ids, vals, context=None):
- # User alias is sync'ed with login
- if vals.get('login'):
- vals['alias_name'] = vals['login']
- return super(res_users, self).write(cr, uid, ids, vals, context=context)
-
def unlink(self, cr, uid, ids, context=None):
# Cascade-delete mail aliases as well, as they should not exist without the user.
alias_pool = self.pool.get('mail.alias')
diff --git a/addons/mail/res_users_view.xml b/addons/mail/res_users_view.xml
index babe922d975..2bf54b4bafd 100644
--- a/addons/mail/res_users_view.xml
+++ b/addons/mail/res_users_view.xml
@@ -22,21 +22,25 @@
<field name="model">res.users</field>
<field name="inherit_id" ref="base.view_users_form"/>
<field name="arch" type="xml">
- <data>
- <field name="signature" position="before">
- <field name="notification_email_send"/>
- </field>
- <field name="signature" position="before">
- <field name="alias_domain" invisible="1"/>
- <field name="alias_id" readonly="1" required="0" attrs="{'invisible': [('alias_domain', '=', False)]}"/>
- </field>
- <group string="Email preferences" position="after">
- <group name="misc" string="Miscellaneous"
- groups="base.group_no_one">
- <field name="display_groups_suggestions"/>
- </group>
- </group>
- </data>
+ <data>
+ <field name="signature" position="before">
+ <field name="notification_email_send"/>
+ </field>
+ <field name="signature" position="before">
+ <label for="alias_id" string="Messaging Alias" class="oe_read_only"
+ attrs="{'invisible': [('alias_domain', '=', False)]}"/>
+ <field name="alias_id" class="oe_read_only" required="0" nolabel="1"
+ attrs="{'invisible': [('alias_domain', '=', False)]}"/>
+ <label for="alias_name" string="Messaging Alias" class="oe_edit_only"
+ attrs="{'invisible': [('alias_domain', '=', False)]}"/>
+ <div class="oe_edit_only" attrs="{'invisible': [('alias_domain', '=', False)]}">
+ <field name="alias_name" class="oe_inline"/>@<field name="alias_domain" class="oe_inline" readonly="1"/>
+ </div>
+ <field name="alias_contact" string="Alias Accepts Emails From"
+ attrs="{'invisible': [('alias_domain', '=', False)]}"/>
+ <field name="display_groups_suggestions" groups="base.group_no_one"/>
+ </field>
+ </data>
</field>
</record>
diff --git a/addons/mail/static/src/css/mail.css b/addons/mail/static/src/css/mail.css
index c969d3f3b16..211133afaf8 100644
--- a/addons/mail/static/src/css/mail.css
+++ b/addons/mail/static/src/css/mail.css
@@ -26,6 +26,16 @@
border-radius: 0px;
}
+/* ---- GENERIC FOR MAIL-RELATED STUFF ---- */
+.openerp .oe_e.oe_e_alias {
+ font-size: 30px;
+ line-height: 15px;
+ vertical-align: top;
+ margin-right: 3px;
+ color: white;
+ text-shadow: 0px 0px 2px black;
+}
+
/* ------------ MAIL WIDGET --------------- */
.openerp .oe_mail, .openerp .oe_mail *{
-webkit-box-sizing: border-box;
diff --git a/addons/mail/static/src/js/mail.js b/addons/mail/static/src/js/mail.js
index 83ac92404f1..21b0f5a9a87 100644
--- a/addons/mail/static/src/js/mail.js
+++ b/addons/mail/static/src/js/mail.js
@@ -632,10 +632,7 @@ openerp.mail = function (session) {
// have unknown names -> call message_get_partner_info_from_emails to try to find partner_id
var find_done = $.Deferred();
if (names_to_find.length > 0) {
- var values = {
- 'res_id': this.context.default_res_id,
- }
- find_done = self.parent_thread.ds_thread._model.call('message_get_partner_info_from_emails', [names_to_find], values);
+ find_done = self.parent_thread.ds_thread._model.call('message_partner_info_from_emails', [this.context.default_res_id, names_to_find]);
}
else {
find_done.resolve([]);
@@ -681,11 +678,7 @@ openerp.mail = function (session) {
var new_names_to_find = _.difference(names_to_find, names_to_remove);
find_done = $.Deferred();
if (new_names_to_find.length > 0) {
- var values = {
- 'link_mail': true,
- 'res_id': self.context.default_res_id,
- }
- find_done = self.parent_thread.ds_thread._model.call('message_get_partner_info_from_emails', [new_names_to_find], values);
+ find_done = self.parent_thread.ds_thread._model.call('message_partner_info_from_emails', [self.context.default_res_id, new_names_to_find, true]);
}
else {
find_done.resolve([]);
diff --git a/addons/mail/tests/test_mail_base.py b/addons/mail/tests/test_mail_base.py
index 7ca8b93135c..b0f9f72b6f7 100644
--- a/addons/mail/tests/test_mail_base.py
+++ b/addons/mail/tests/test_mail_base.py
@@ -71,9 +71,9 @@ class TestMailBase(common.TransactionCase):
# 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', 'groups_id': [(6, 0, [self.group_employee_id])]})
+ {'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', 'groups_id': [(6, 0, [])]})
+ {'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)
@@ -83,7 +83,7 @@ class TestMailBase(common.TransactionCase):
# Test 'pigs' group to use through the various tests
self.group_pigs_id = self.mail_group.create(cr, uid,
- {'name': 'Pigs', 'description': 'Fans of Pigs, unite !'},
+ {'name': 'Pigs', 'description': 'Fans of Pigs, unite !', 'alias_name': 'group+pigs'},
{'mail_create_nolog': True})
self.group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
diff --git a/addons/mail/tests/test_mail_features.py b/addons/mail/tests/test_mail_features.py
index ca9c156c8cb..91ede8b8449 100644
--- a/addons/mail/tests/test_mail_features.py
+++ b/addons/mail/tests/test_mail_features.py
@@ -32,17 +32,17 @@ class test_mail(TestMailBase):
""" Test basic mail.alias setup works, before trying to use them for routing """
cr, uid = self.cr, self.uid
self.user_valentin_id = self.res_users.create(cr, uid,
- {'name': 'Valentin Cognito', 'email': 'valentin.cognito@gmail.com', 'login': 'valentin.cognito'})
+ {'name': 'Valentin Cognito', 'email': 'valentin.cognito@gmail.com', 'login': 'valentin.cognito', 'alias_name': 'valentin.cognito'})
self.user_valentin = self.res_users.browse(cr, uid, self.user_valentin_id)
self.assertEquals(self.user_valentin.alias_name, self.user_valentin.login, "Login should be used as alias")
self.user_pagan_id = self.res_users.create(cr, uid,
- {'name': 'Pagan Le Marchant', 'email': 'plmarchant@gmail.com', 'login': 'plmarchant@gmail.com'})
+ {'name': 'Pagan Le Marchant', 'email': 'plmarchant@gmail.com', 'login': 'plmarchant@gmail.com', 'alias_name': 'plmarchant@gmail.com'})
self.user_pagan = self.res_users.browse(cr, uid, self.user_pagan_id)
self.assertEquals(self.user_pagan.alias_name, 'plmarchant', "If login is an email, the alias should keep only the local part")
self.user_barty_id = self.res_users.create(cr, uid,
- {'name': 'Bartholomew Ironside', 'email': 'barty@gmail.com', 'login': 'b4r+_#_R3wl$$'})
+ {'name': 'Bartholomew Ironside', 'email': 'barty@gmail.com', 'login': 'b4r+_#_R3wl$$', 'alias_name': 'b4r+_#_R3wl$$'})
self.user_barty = self.res_users.browse(cr, uid, self.user_barty_id)
self.assertEquals(self.user_barty.alias_name, 'b4r+_-_r3wl-', 'Disallowed chars should be replaced by hyphens')
diff --git a/addons/mail/tests/test_mail_gateway.py b/addons/mail/tests/test_mail_gateway.py
index 9e05bb049c1..2e0ba336dc4 100644
--- a/addons/mail/tests/test_mail_gateway.py
+++ b/addons/mail/tests/test_mail_gateway.py
@@ -99,20 +99,20 @@ class TestMailgateway(TestMailBase):
# --------------------------------------------------
# Do: find partner with email -> first partner should be found
- partner_info = self.mail_thread.message_find_partner_from_emails(cr, uid, None, ['Maybe Raoul <test@test.fr>'], link_mail=False)[0]
+ 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_find_partner_from_emails did not handle email')
+ 'mail_thread: message_partner_info_from_emails did not handle email')
self.assertEqual(partner_info['partner_id'], p_a_id,
- 'mail_thread: message_find_partner_from_emails wrong partner found')
+ '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_find_partner_from_emails(cr, uid, None, ['Maybe Raoul <test@test.fr>'], link_mail=False)[0]
+ 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_find_partner_from_emails wrong partner found')
+ 'mail_thread: message_partner_info_from_emails wrong partner found')
# --------------------------------------------------
# CASE1: with object
@@ -120,9 +120,9 @@ class TestMailgateway(TestMailBase):
# 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_find_partner_from_emails(cr, uid, group_pigs.id, ['Maybe Raoul <test@test.fr>'], link_mail=False)[0]
+ 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_find_partner_from_emails wrong partner found')
+ '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, ... """
@@ -189,6 +189,7 @@ class TestMailgateway(TestMailBase):
# 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})
@@ -220,87 +221,6 @@ class TestMailgateway(TestMailBase):
self.assertEqual(mail.reply_to, msg.email_from,
'mail_mail: incorrect reply_to: should be message email_from')
- def test_05_mail_message_mail_mail(self):
- """ Tests designed for testing email values based on mail.message, aliases, ... """
- cr, uid = self.cr, self.uid
-
- # Data: clean catchall 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: create a mail_message with a reply_to, without message-id
- msg_id = self.mail_message.create(cr, uid, {'subject': 'Subject', 'body': 'Body', 'reply_to': 'custom@example.com'})
- msg = self.mail_message.browse(cr, uid, 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('custom@example.com', msg.reply_to,
- 'mail_message: incorrect reply_to')
- # Do: create a mail_mail with the previous mail_message and specified reply_to
- mail_id = self.mail_mail.create(cr, uid, {'mail_message_id': msg_id, 'reply_to': 'other@example.com', 'state': 'cancel'})
- mail = self.mail_mail.browse(cr, uid, mail_id)
- # Test: mail_mail content
- self.assertEqual(mail.reply_to, 'other@example.com',
- 'mail_mail: reply_to should be equal to the one coming from creation values')
- # Do: create a mail_mail with the previous mail_message
- self.mail_message.write(cr, uid, [msg_id], {'reply_to': 'custom@example.com'})
- msg.refresh()
- 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
- self.assertEqual(mail.reply_to, msg.reply_to,
- 'mail_mail: reply_to should be equal to the one coming from the mail_message')
-
- # Do: create a mail_message without a reply_to
- msg_id = self.mail_message.create(cr, uid, {'subject': 'Subject', 'body': 'Body', 'model': 'mail.group', 'res_id': self.group_pigs_id, 'email_from': False})
- msg = self.mail_message.browse(cr, uid, 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: should not generate a reply_to address when not specified')
- # 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
- self.assertFalse(mail.reply_to,
- 'mail_mail: reply_to should not have been guessed')
- # Update message
- self.mail_message.write(cr, uid, [msg_id], {'email_from': 'someone@example.com'})
- 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
- self.assertEqual(email_split(mail.reply_to), email_split(msg.email_from),
- 'mail_mail: reply_to should be equal to mail_message.email_from when having no document or default alias')
-
- # Data: set catchall domain
- self.registry('ir.config_parameter').set_param(cr, uid, 'mail.catchall.domain', 'schlouby.fr')
- 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, 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
- self.assertEqual(mail.reply_to, '"Followers of Pigs" <group+pigs@schlouby.fr>',
- 'mail_mail: reply_to should equal the mail.group alias')
-
- # Update message
- self.mail_message.write(cr, uid, [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, uid, {'mail_message_id': msg_id, 'state': 'cancel'})
- mail = self.mail_mail.browse(cr, uid, mail_id)
- # Test: mail_mail content
- self.assertEqual(mail.reply_to, msg.email_from,
- 'mail_mail: reply_to should equal the mail_message email_from')
-
# Data: set catchall alias
self.registry('ir.config_parameter').set_param(self.cr, self.uid, 'mail.catchall.alias', 'gateway')
@@ -310,7 +230,7 @@ class TestMailgateway(TestMailBase):
# 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
+ # Test: mail_mail Content-Type
self.assertEqual(mail.reply_to, 'gateway@schlouby.fr',
'mail_mail: reply_to should equal the catchall email alias')
@@ -351,7 +271,10 @@ class TestMailgateway(TestMailBase):
alias_id = self.mail_alias.create(cr, uid, {
'alias_name': 'groups',
'alias_user_id': False,
- 'alias_model_id': self.mail_group_model_id})
+ 'alias_model_id': self.mail_group_model_id,
+ 'alias_parent_model_id': self.mail_group_model_id,
+ 'alias_parent_thread_id': self.group_pigs_id,
+ 'alias_contact': 'everyone'})
# --------------------------------------------------
# Test1: new record creation
@@ -392,12 +315,42 @@ class TestMailgateway(TestMailBase):
# Data: unlink group
frog_group.unlink()
- # Do: incoming email from a known partner on an alias with known recipients, alias is owned by user that can create a group
- self.mail_alias.write(cr, uid, [alias_id], {'alias_user_id': self.user_raoul_id})
+ # Do: incoming email from an unknown partner on a Partners only alias -> bounce
+ self._init_mock_build_email()
+ self.mail_alias.write(cr, uid, [alias_id], {'alias_contact': 'partners'})
+ frog_groups = format_and_process(MAIL_TEMPLATE, to='groups@example.com, other2@gmail.com')
+ # Test: no group created
+ self.assertTrue(len(frog_groups) == 0)
+ # Test: email bounced
+ sent_emails = self._build_email_kwargs_list
+ self.assertEqual(len(sent_emails), 1,
+ 'message_process: incoming email on Partners alias should send a bounce email')
+ self.assertIn('Frogs', sent_emails[0].get('subject'),
+ 'message_process: bounce email on Partners alias should contain the original subject')
+ self.assertIn('test.sylvie.lelitre@agrolait.com', sent_emails[0].get('email_to'),
+ 'message_process: bounce email on Partners alias should have original email sender as recipient')
+
+ # Do: incoming email from an unknown partner on a Followers only alias -> bounce
+ self._init_mock_build_email()
+ self.mail_alias.write(cr, uid, [alias_id], {'alias_contact': 'followers'})
+ frog_groups = format_and_process(MAIL_TEMPLATE, to='groups@example.com, other3@gmail.com')
+ # Test: no group created
+ self.assertTrue(len(frog_groups) == 0)
+ # Test: email bounced
+ sent_emails = self._build_email_kwargs_list
+ self.assertEqual(len(sent_emails), 1,
+ 'message_process: incoming email on Followers alias should send a bounce email')
+ self.assertIn('Frogs', sent_emails[0].get('subject'),
+ 'message_process: bounce email on Followers alias should contain the original subject')
+ self.assertIn('test.sylvie.lelitre@agrolait.com', sent_emails[0].get('email_to'),
+ 'message_process: bounce email on Followers alias should have original email sender as recipient')
+
+ # Do: incoming email from a known partner on a Partners alias -> ok (+ test on alias.user_id)
+ self.mail_alias.write(cr, uid, [alias_id], {'alias_user_id': self.user_raoul_id, 'alias_contact': 'partners'})
p1id = self.res_partner.create(cr, uid, {'name': 'Sylvie Lelitre', 'email': 'test.sylvie.lelitre@agrolait.com'})
- p2id = self.res_partner.create(cr, uid, {'name': 'Other Poilvache', 'email': 'other@gmail.com'})
+ p2id = self.res_partner.create(cr, uid, {'name': 'Other Poilvache', 'email': 'other4@gmail.com'})
self._init_mock_build_email()
- frog_groups = format_and_process(MAIL_TEMPLATE, to='groups@example.com, other@gmail.com')
+ frog_groups = format_and_process(MAIL_TEMPLATE, to='groups@example.com, other4@gmail.com')
sent_emails = self._build_email_kwargs_list
# Test: one group created by Raoul
self.assertEqual(len(frog_groups), 1, 'message_process: a new mail.group should have been created')
@@ -409,24 +362,37 @@ class TestMailgateway(TestMailBase):
self.assertEqual(len(frog_group.message_ids), 1,
'message_process: newly created group should have the incoming email in message_ids')
msg = frog_group.message_ids[0]
- # Test: message: unknown email address -> message has email_from, not author_id
+ # Test: message: author found
self.assertEqual(p1id, msg.author_id.id,
'message_process: message on created group should have Sylvie as author_id')
self.assertIn('Sylvie Lelitre <test.sylvie.lelitre@agrolait.com>', msg.email_from,
'message_process: message on created group should have have an email_from')
- # Test: author (not recipient and not raoul (as alias owner)) added as follower
+ # Test: author (not recipient and not Raoul (as alias owner)) added as follower
frog_follower_ids = set([p.id for p in frog_group.message_follower_ids])
self.assertEqual(frog_follower_ids, set([p1id]),
'message_process: newly created group should have 1 follower (author, not creator, not recipients)')
# Test: sent emails: no-one, no bounce effet
+ sent_emails = self._build_email_kwargs_list
self.assertEqual(len(sent_emails), 0,
'message_process: should not bounce incoming emails')
# Data: unlink group
frog_group.unlink()
- # Do: incoming email from a known partner that is also an user that can create a mail.group
- self.res_users.create(cr, uid, {'partner_id': p1id, 'login': 'sylvie', 'groups_id': [(6, 0, [self.group_employee_id])]})
- frog_groups = format_and_process(MAIL_TEMPLATE, to='groups@example.com, other@gmail.com')
+ # Do: incoming email from a not follower Partner on a Followers only alias -> bounce
+ self._init_mock_build_email()
+ self.mail_alias.write(cr, uid, [alias_id], {'alias_user_id': False, 'alias_contact': 'followers'})
+ frog_groups = format_and_process(MAIL_TEMPLATE, to='groups@example.com, other5@gmail.com')
+ # Test: no group created
+ self.assertTrue(len(frog_groups) == 0)
+ # Test: email bounced
+ sent_emails = self._build_email_kwargs_list
+ self.assertEqual(len(sent_emails), 1,
+ 'message_process: incoming email on Partners alias should send a bounce email')
+
+ # Do: incoming email from a parent document follower on a Followers only alias -> ok
+ self._init_mock_build_email()
+ self.mail_group.message_subscribe(cr, uid, [self.group_pigs_id], [p1id])
+ frog_groups = format_and_process(MAIL_TEMPLATE, to='groups@example.com, other6@gmail.com')
# Test: one group created by Raoul (or Sylvie maybe, if we implement it)
self.assertEqual(len(frog_groups), 1, 'message_process: a new mail.group should have been created')
frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
@@ -438,15 +404,63 @@ class TestMailgateway(TestMailBase):
self.assertEqual(frog_follower_ids, set([p1id]),
'message_process: newly created group should have 1 follower (author, not creator, not recipients)')
# Test: sent emails: no-one, no bounce effet
+ sent_emails = self._build_email_kwargs_list
self.assertEqual(len(sent_emails), 0,
'message_process: should not bounce incoming emails')
# --------------------------------------------------
- # Test2: discussion update
+ # Test2: update-like alias
+ # --------------------------------------------------
+
+ # Do: Pigs alias is restricted, should bounce
+ self._init_mock_build_email()
+ self.mail_group.write(cr, uid, [frog_group.id], {'alias_name': 'frogs', 'alias_contact': 'followers', 'alias_force_thread_id': frog_group.id})
+ frog_groups = format_and_process(MAIL_TEMPLATE, email_from='other4@gmail.com',
+ msg_id='<1198923581.41972151344608186760.JavaMail.diff1@agrolait.com>',
+ to='frogs@example.com>', subject='Re: news')
+ # Test: no group 'Re: news' created, still only 1 Frogs group
+ self.assertEqual(len(frog_groups), 0,
+ 'message_process: reply on Frogs should not have created a new group with new subject')
+ frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
+ self.assertEqual(len(frog_groups), 1,
+ 'message_process: reply on Frogs should not have created a duplicate group with old subject')
+ frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
+ # Test: email bounced
+ sent_emails = self._build_email_kwargs_list
+ self.assertEqual(len(sent_emails), 1,
+ 'message_process: incoming email on Followers alias should send a bounce email')
+ self.assertIn('Re: news', sent_emails[0].get('subject'),
+ 'message_process: bounce email on Followers alias should contain the original subject')
+
+ # Do: Pigs alias is restricted, should accept Followers
+ self._init_mock_build_email()
+ self.mail_group.message_subscribe(cr, uid, [frog_group.id], [p2id])
+ frog_groups = format_and_process(MAIL_TEMPLATE, email_from='other4@gmail.com',
+ msg_id='<1198923581.41972151344608186799.JavaMail.diff1@agrolait.com>',
+ to='frogs@example.com>', subject='Re: cats')
+ # Test: no group 'Re: news' created, still only 1 Frogs group
+ self.assertEqual(len(frog_groups), 0,
+ 'message_process: reply on Frogs should not have created a new group with new subject')
+ frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
+ self.assertEqual(len(frog_groups), 1,
+ 'message_process: reply on Frogs should not have created a duplicate group with old subject')
+ frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
+ # Test: one new message
+ self.assertEqual(len(frog_group.message_ids), 2, 'message_process: group should contain 2 messages after reply')
+ # Test: sent emails: 1 (Sylvie copy of the incoming email, but no bounce)
+ sent_emails = self._build_email_kwargs_list
+ self.assertEqual(len(sent_emails), 1,
+ 'message_process: one email should have been generated')
+ self.assertIn('test.sylvie.lelitre@agrolait.com', sent_emails[0].get('email_to')[0],
+ 'message_process: email should be sent to Sylvie')
+ self.mail_group.message_unsubscribe(cr, uid, [frog_group.id], [p2id])
+
+ # --------------------------------------------------
+ # Test3: discussion and replies
# --------------------------------------------------
# Do: even with a wrong destination, a reply should end up in the correct thread
- frog_groups = format_and_process(MAIL_TEMPLATE, email_from='other@gmail.com',
+ frog_groups = format_and_process(MAIL_TEMPLATE, email_from='other4@gmail.com',
msg_id='<1198923581.41972151344608186760.JavaMail.diff1@agrolait.com>',
to='erroneous@example.com>', subject='Re: news',
extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>\n' % frog_group.id)
@@ -458,14 +472,14 @@ class TestMailgateway(TestMailBase):
'message_process: reply on Frogs should not have created a duplicate group with old subject')
frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
# Test: one new message
- self.assertEqual(len(frog_group.message_ids), 2, 'message_process: group should contain 2 messages after reply')
+ self.assertEqual(len(frog_group.message_ids), 3, 'message_process: group should contain 2 messages after reply')
# Test: author (and not recipient) added as follower
frog_follower_ids = set([p.id for p in frog_group.message_follower_ids])
self.assertEqual(frog_follower_ids, set([p1id, p2id]),
'message_process: after reply, group should have 2 followers')
# Do: due to some issue, same email goes back into the mailgateway
- frog_groups = format_and_process(MAIL_TEMPLATE, email_from='other@gmail.com',
+ frog_groups = format_and_process(MAIL_TEMPLATE, email_from='other4@gmail.com',
msg_id='<1198923581.41972151344608186760.JavaMail.diff1@agrolait.com>',
subject='Re: news', extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>\n' % frog_group.id)
# Test: no group 'Re: news' created, still only 1 Frogs group
@@ -476,20 +490,18 @@ class TestMailgateway(TestMailBase):
'message_process: reply on Frogs should not have created a duplicate group with old subject')
frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
# Test: no new message
- self.assertEqual(len(frog_group.message_ids), 2, 'message_process: message with already existing message_id should not have been duplicated')
+ self.assertEqual(len(frog_group.message_ids), 3, 'message_process: message with already existing message_id should not have been duplicated')
# Test: message_id is still unique
msg_ids = self.mail_message.search(cr, uid, [('message_id', 'ilike', '<1198923581.41972151344608186760.JavaMail.diff1@agrolait.com>')])
self.assertEqual(len(msg_ids), 1,
'message_process: message with already existing message_id should not have been duplicated')
# --------------------------------------------------
- # Test3: email_from and partner finding
+ # Test4: email_from and partner finding
# --------------------------------------------------
# Data: extra partner with Raoul's email -> test the 'better author finding'
extra_partner_id = self.res_partner.create(cr, uid, {'name': 'A-Raoul', 'email': 'test_raoul@email.com'})
- # extra_user_id = self.res_users.create(cr, uid, {'name': 'B-Raoul', 'email': self.user_raoul.email})
- # extra_user_pid = self.res_users.browse(cr, uid, extra_user_id).partner_id.id
# Do: post a new message, with a known partner -> duplicate emails -> partner
format_and_process(MAIL_TEMPLATE, email_from='Lombrik Lubrik <test_raoul@email.com>',
@@ -534,7 +546,7 @@ class TestMailgateway(TestMailBase):
self.res_users.write(cr, uid, self.user_raoul_id, {'email': raoul_email})
# --------------------------------------------------
- # Test4: misc gateway features
+ # Test5: misc gateway features
# --------------------------------------------------
# Do: incoming email with model that does not accepts incoming emails must raise
@@ -568,7 +580,7 @@ class TestMailgateway(TestMailBase):
frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
msg = frog_group.message_ids[0]
# Test: plain text content should be wrapped and stored as html
- self.assertEqual(msg.body, '<pre>\nPlease call me as soon as possible this afternoon!\n\n--\nSylvie\n</pre>',
+ self.assertIn('<pre>\nPlease call me as soon as possible this afternoon!\n\n--\nSylvie\n</pre>', msg.body,
'message_process: plaintext incoming email incorrectly parsed')
@mute_logger('openerp.addons.mail.mail_thread', 'openerp.osv.orm')