diff --git a/addons/mail/mail_followers.py b/addons/mail/mail_followers.py
index c813eaa5dc0..6e2bdfb329c 100644
--- a/addons/mail/mail_followers.py
+++ b/addons/mail/mail_followers.py
@@ -104,11 +104,12 @@ class mail_notification(osv.Model):
if signature:
body_html = tools.append_content_to_html(body_html, signature)
- towrite = {
+ mail_values = {
'mail_message_id': msg.id,
'email_to': [],
- 'auto_delete': False,
+ 'auto_delete': True,
'body_html': body_html,
+ 'state': 'outgoing',
}
for partner in self.pool.get('res.partner').browse(cr, uid, partner_ids, context=context):
@@ -127,10 +128,10 @@ class mail_notification(osv.Model):
# Partner wants to receive only emails
if partner.notification_email_send == 'email' and msg.type != 'email':
continue
- if partner.email not in towrite['email_to']:
- towrite['email_to'].append(partner.email)
- if towrite['email_to']:
- towrite['email_to'] = ', '.join(towrite['email_to'])
- email_notif_id = mail_mail.create(cr, uid, towrite, context=context)
+ if partner.email not in mail_values['email_to']:
+ mail_values['email_to'].append(partner.email)
+ if mail_values['email_to']:
+ mail_values['email_to'] = ', '.join(mail_values['email_to'])
+ email_notif_id = mail_mail.create(cr, uid, mail_values, context=context)
mail_mail.send(cr, uid, [email_notif_id], context=context)
return True
diff --git a/addons/mail/mail_group_view.xml b/addons/mail/mail_group_view.xml
index 51a7a926dc9..31beba80ecc 100644
--- a/addons/mail/mail_group_view.xml
+++ b/addons/mail/mail_group_view.xml
@@ -25,7 +25,7 @@
-
+
diff --git a/addons/mail/mail_mail.py b/addons/mail/mail_mail.py
index 36b71653556..7b593c5b998 100644
--- a/addons/mail/mail_mail.py
+++ b/addons/mail/mail_mail.py
@@ -54,8 +54,12 @@ class mail_mail(osv.Model):
'email_from': fields.char('From', help='Message sender, taken from user preferences.'),
'email_to': fields.text('To', help='Message recipients'),
'email_cc': fields.char('Cc', help='Carbon copy message recipients'),
- 'reply_to':fields.char('Reply-To', help='Preferred response address for the message'),
+ 'reply_to': fields.char('Reply-To', help='Preferred response address for the message'),
'body_html': fields.text('Rich-text Contents', help="Rich-text/HTML message"),
+
+ # Auto-detected based on create() - if 'mail_message_id' was passed then this mail is a notification
+ # and during unlink() we will cascade delete the parent and its attachments
+ 'notification': fields.boolean('Is Notification')
}
def _get_default_from(self, cr, uid, context=None):
@@ -69,6 +73,19 @@ class mail_mail(osv.Model):
'email_from': lambda self, cr, uid, ctx=None: self._get_default_from(cr, uid, ctx),
}
+ def create(self, cr, uid, values, context=None):
+ if 'notification' not in values and values.get('mail_message_id'):
+ values['notification'] = True
+ 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
+ ids_to_cascade = self.search(cr, uid, [('notification','=',False),('id','in',ids)])
+ parent_msg_ids = [m.mail_message_id.id for m in self.browse(cr, uid, ids_to_cascade, context=context)]
+ res = super(mail_mail,self).unlink(cr, uid, ids, context=context)
+ self.pool.get('mail.message').unlink(cr, uid, parent_msg_ids, context=context)
+ return res
+
def mark_outgoing(self, cr, uid, ids, context=None):
return self.write(cr, uid, ids, {'state':'outgoing'}, context=context)
@@ -106,20 +123,17 @@ class mail_mail(osv.Model):
_logger.exception("Failed processing mail queue")
return res
- def _postprocess_sent_message(self, cr, uid, message, context=None):
- """Perform any post-processing necessary after sending ``message``
+ def _postprocess_sent_message(self, cr, uid, mail, context=None):
+ """Perform any post-processing necessary after sending ``mail``
successfully, including deleting it completely along with its
- attachment if the ``auto_delete`` flag of the message was set.
+ attachment if the ``auto_delete`` flag of the mail was set.
Overridden by subclasses for extra post-processing behaviors.
- :param browse_record message: the message that was just sent
+ :param browse_record mail: the mail that was just sent
:return: True
"""
- if message.auto_delete:
- self.pool.get('ir.attachment').unlink(cr, uid,
- [x.id for x in message.attachment_ids],
- context=context)
- message.unlink()
+ if mail.auto_delete:
+ mail.unlink()
return True
def _send_get_mail_subject(self, cr, uid, mail, force=False, context=None):
diff --git a/addons/mail/mail_message.py b/addons/mail/mail_message.py
index 1559a412e44..3ef731b766b 100644
--- a/addons/mail/mail_message.py
+++ b/addons/mail/mail_message.py
@@ -20,10 +20,12 @@
##############################################################################
import logging
-from email.header import decode_header
-from osv import osv, fields
import tools
+from email.header import decode_header
+from operator import itemgetter
+from osv import osv, fields
+
_logger = logging.getLogger(__name__)
""" Some tools for parsing / creating email fields """
@@ -45,7 +47,7 @@ class mail_message(osv.Model):
_message_record_name_length = 18
def _shorten_name(self, name):
- if len(name) <= (self._message_record_name_length+3):
+ if len(name) <= (self._message_record_name_length + 3):
return name
return name[:self._message_record_name_length] + '...'
@@ -73,7 +75,7 @@ class mail_message(osv.Model):
return res
def _search_unread(self, cr, uid, obj, name, domain, context=None):
- """ Search for messages unread by the current user. Condition is
+ """ Search for messages unread by the current user. Condition is
inversed because we search unread message on a read column. """
if domain[0][2]:
read_cond = '(read = false or read is null)'
@@ -136,7 +138,7 @@ class mail_message(osv.Model):
_defaults = {
'type': 'email',
'date': lambda *a: fields.datetime.now(),
- 'author_id': lambda self,cr,uid,ctx: self._get_default_author(cr, uid, ctx),
+ 'author_id': lambda self, cr, uid, ctx={}: self._get_default_author(cr, uid, ctx),
'body': '',
}
@@ -170,12 +172,13 @@ class mail_message(osv.Model):
""" Given a tree with several roots of following structure :
[ {'id': 1, 'child_ids': [
{'id': 11, 'child_ids': [...] },],
- },
{...} ]
- Flatten it to have a maximum number of level, with 0 being
- completely flat.
+ Flatten it to have a maximum number of levels, 0 being flat and
+ sort messages in a level according to a key of the messages.
Perform the flattening at leafs if above the maximum depth, then get
back in the tree.
+ :param context: ``sort_key``: key for sorting (id by default)
+ :param context: ``sort_reverse``: reverser order for sorting (True by default)
"""
def _flatten(msg_dict):
""" from {'id': x, 'child_ids': [{child1}, {child2}]}
@@ -184,19 +187,22 @@ class mail_message(osv.Model):
child_ids = msg_dict.pop('child_ids', [])
msg_dict['child_ids'] = []
return [msg_dict] + child_ids
+ # return sorted([msg_dict] + child_ids, key=itemgetter('id'), reverse=True)
+ context = context or {}
# Depth-first flattening
for message in messages:
if message.get('type') == 'expandable':
continue
- message['child_ids'] = self.message_read_tree_flatten(cr, uid, message['child_ids'], current_level+1, level, context=context)
+ message['child_ids'] = self.message_read_tree_flatten(cr, uid, message['child_ids'], current_level + 1, level, context=context)
# Flatten if above maximum depth
if current_level < level:
- return messages
- new_list = []
- for message in messages:
- for flat_message in _flatten(message):
- new_list.append(flat_message)
- return new_list
+ return_list = messages
+ else:
+ return_list = []
+ for message in messages:
+ for flat_message in _flatten(message):
+ return_list.append(flat_message)
+ return sorted(return_list, key=itemgetter(context.get('sort_key', 'id')), reverse=context.get('sort_reverse', True))
def message_read(self, cr, uid, ids=False, domain=[], thread_level=0, limit=None, context=None):
""" If IDs are provided, fetch these records. Otherwise use the domain
@@ -214,7 +220,7 @@ class mail_message(osv.Model):
result = []
tree = {} # key: ID, value: record
for msg in messages:
- if len(result)<(limit-1):
+ if len(result) < (limit - 1):
record = self._message_dict_get(cr, uid, msg, context=context)
if thread_level and msg.parent_id:
while msg.parent_id:
@@ -234,9 +240,10 @@ class mail_message(osv.Model):
else:
result.append({
'type': 'expandable',
- 'domain': [('id','<=', msg.id)]+domain,
+ 'domain': [('id', '<=', msg.id)] + domain,
'context': context,
- 'thread_level': thread_level # should be improve accodting to level of records
+ 'thread_level': thread_level, # should be improve accodting to level of records
+ 'id': -1,
})
break
@@ -280,7 +287,7 @@ class mail_message(osv.Model):
if not (rmod and rid):
continue
document_ids.append(id)
- model_record_ids.setdefault(rmod,set()).add(rid)
+ model_record_ids.setdefault(rmod, set()).add(rid)
for model, mids in model_record_ids.items():
model_obj = self.pool.get(model)
mids = model_obj.exists(cr, uid, mids)
@@ -293,11 +300,23 @@ class mail_message(osv.Model):
def create(self, cr, uid, values, context=None):
if not values.get('message_id') and values.get('res_id') and values.get('model'):
- values['message_id'] = tools.generate_tracking_message_id('%(model)s-%(res_id)s'% values)
+ values['message_id'] = tools.generate_tracking_message_id('%(model)s-%(res_id)s' % values)
newid = super(mail_message, self).create(cr, uid, values, context)
self.notify(cr, uid, newid, context=context)
return newid
+ def unlink(self, cr, uid, ids, context=None):
+ # cascade-delete attachments that are directly attached to the message (should only happen
+ # for mail.messages that act as parent for a standalone mail.mail record.
+ attachments_to_delete = []
+ for mail in self.browse(cr, uid, ids, context=context):
+ for attach in mail.attachment_ids:
+ if attach.res_model == 'mail.message' and attach.res_id == mail.id:
+ attachments_to_delete.append(attach.id)
+ if attachments_to_delete:
+ self.pool.get('ir.attachment').unlink(cr, uid, attachments_to_delete, context=context)
+ return super(mail_message,self).unlink(cr, uid, ids, context=context)
+
def notify(self, cr, uid, newid, context=None):
""" Add the related record followers to the destination partner_ids.
Call mail_notification.notify to manage the email sending
@@ -328,4 +347,4 @@ class mail_message(osv.Model):
if default is None:
default = {}
default.update(message_id=False, headers=False)
- return super(mail_message,self).copy(cr, uid, id, default=default, context=context)
+ return super(mail_message, self).copy(cr, uid, id, default=default, context=context)
diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py
index 2422f556446..7fc6c79bd9a 100644
--- a/addons/mail/mail_thread.py
+++ b/addons/mail/mail_thread.py
@@ -24,7 +24,6 @@ import dateutil
import email
import logging
import pytz
-import re
import time
import tools
import xmlrpclib
@@ -32,20 +31,19 @@ import xmlrpclib
from email.message import Message
from mail_message import decode
from osv import osv, fields
-from tools.translate import _
from tools.safe_eval import safe_eval as eval
_logger = logging.getLogger(__name__)
def decode_header(message, header, separator=' '):
- return separator.join(map(decode,message.get_all(header, [])))
+ return separator.join(map(decode, message.get_all(header, [])))
class many2many_reference(fields.many2many):
""" many2many_reference manages many2many fields where one id is found
by a reference-like key (a char column in addition to the foreign id).
The reference_column attribute on the many2many fields is used;
if not defined, ``res_model`` is used. """
-
+
def _get_query_and_where_params(self, cr, model, ids, values, where_params):
""" Add in where condition like mail_followers.res_model = 'crm.lead' """
reference_column = self.reference_column if self.reference_column else 'res_model'
@@ -119,7 +117,7 @@ class mail_thread(osv.AbstractModel):
_description = 'Email Thread'
def _get_message_data(self, cr, uid, ids, name, args, context=None):
- res = dict( (id, dict(message_unread=False, message_summary='')) for id in ids)
+ res = dict((id, dict(message_unread=False, message_summary='')) for id in ids)
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
notif_obj = self.pool.get('mail.notification')
@@ -149,7 +147,7 @@ class mail_thread(osv.AbstractModel):
], context=context)
for notif in notif_obj.browse(cr, uid, notif_ids, context=context):
res[notif.message_id.res_id] = True
- return [('id','in',res.keys())]
+ return [('id', 'in', res.keys())]
_columns = {
'message_is_follower': fields.function(_get_message_data,
@@ -159,13 +157,13 @@ class mail_thread(osv.AbstractModel):
reference_column='res_model', string='Followers'),
'message_comment_ids': fields.one2many('mail.message', 'res_id',
domain=lambda self: [('model', '=', self._name), ('type', 'in', ('comment', 'email'))],
- string='Comments and emails',
+ string='Comments and emails',
help="Comments and emails"),
'message_ids': fields.one2many('mail.message', 'res_id',
- domain=lambda self: [('model','=',self._name)],
- string='Messages',
+ domain=lambda self: [('model', '=', self._name)],
+ string='Messages',
help="Messages and communication history"),
- 'message_unread': fields.function(_get_message_data, fnct_search=_search_unread,
+ 'message_unread': fields.function(_get_message_data, fnct_search=_search_unread,
type='boolean', string='Unread Messages', multi="_get_message_data",
help="If checked new messages require your attention."),
'message_summary': fields.function(_get_message_data, method=True,
@@ -229,7 +227,7 @@ class mail_thread(osv.AbstractModel):
def _message_find_user_id(self, cr, uid, message, context=None):
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, ['|',
+ 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
@@ -445,6 +443,9 @@ class mail_thread(osv.AbstractModel):
encoding = message.get_content_charset()
body = message.get_payload(decode=True)
body = tools.ustr(body, encoding, errors='replace')
+ if message.get_content_type() == 'text/plain':
+ # text/plain ->
+ body = tools.append_content_to_html(u'', body)
else:
alternative = (message.get_content_type() == 'multipart/alternative')
for part in message.walk():
@@ -453,7 +454,7 @@ class mail_thread(osv.AbstractModel):
filename = part.get_filename() # None if normal part
encoding = part.get_content_charset() # None if attachment
# 1) Explicit Attachments -> attachments
- if filename or part.get('content-disposition','').strip().startswith('attachment'):
+ if filename or part.get('content-disposition', '').strip().startswith('attachment'):
attachments.append((filename or 'attachment', part.get_payload(decode=True)))
continue
# 2) text/plain ->
@@ -515,7 +516,7 @@ class mail_thread(osv.AbstractModel):
if 'Subject' in message:
msg_dict['subject'] = decode(message.get('Subject'))
- # Envelope fields not stored in mail.message but made available for message_new()
+ # Envelope fields not stored in mail.message but made available for message_new()
msg_dict['from'] = decode(message.get('from'))
msg_dict['to'] = decode(message.get('to'))
msg_dict['cc'] = decode(message.get('cc'))
@@ -524,7 +525,7 @@ class mail_thread(osv.AbstractModel):
author_ids = self._message_find_partners(cr, uid, message, ['From'], context=context)
if author_ids:
msg_dict['author_id'] = author_ids[0]
- partner_ids = self._message_find_partners(cr, uid, message, ['From','To','Cc'], context=context)
+ partner_ids = self._message_find_partners(cr, uid, message, ['From', 'To', 'Cc'], context=context)
msg_dict['partner_ids'] = partner_ids
if 'Date' in message:
@@ -535,16 +536,16 @@ class mail_thread(osv.AbstractModel):
msg_dict['date'] = date_server_datetime_str
if 'In-Reply-To' in message:
- parent_ids = self.pool.get('mail.message').search(cr, uid, [('message_id','=',decode(message['In-Reply-To']))])
+ parent_ids = self.pool.get('mail.message').search(cr, uid, [('message_id', '=', decode(message['In-Reply-To']))])
if parent_ids:
msg_dict['parent_id'] = parent_ids[0]
if 'References' in message and 'parent_id' not in msg_dict:
- parent_ids = self.pool.get('mail.message').search(cr, uid, [('message_id','in',
+ parent_ids = self.pool.get('mail.message').search(cr, uid, [('message_id', 'in',
[x.strip() for x in decode(message['References']).split()])])
if parent_ids:
msg_dict['parent_id'] = parent_ids[0]
-
+
msg_dict['body'], msg_dict['attachments'] = self._message_extract_payload(message)
return msg_dict
@@ -573,7 +574,7 @@ class mail_thread(osv.AbstractModel):
:param int parent_id: optional ID of parent message in this thread
:param tuple(str,str) attachments: list of attachment tuples in the form
``(name,content)``, where content is NOT base64 encoded
- :return: ID of newly created mail.message
+ :return: ID of newly created mail.message
"""
context = context or {}
attachments = attachments or []
@@ -594,7 +595,7 @@ class mail_thread(osv.AbstractModel):
'res_model': context.get('thread_model') or self._name,
'res_id': thread_id,
}
- attachment_ids.append((0,0, data_attach))
+ attachment_ids.append((0, 0, data_attach))
values = kwargs
subtype_obj = self.pool.get('mail.message.subtype')
@@ -604,7 +605,7 @@ class mail_thread(osv.AbstractModel):
subtype_browse = subtype_obj.browse(cr, uid, subtypes[0][0])
if self._name in [model.model for model in subtype_browse.model_ids]:
values['subtype_id']=subtype_browse.id
- values.update( {
+ values.update({
'model': context.get('thread_model', self._name) if thread_id else False,
'res_id': thread_id or False,
'body': body,
@@ -613,7 +614,7 @@ class mail_thread(osv.AbstractModel):
'parent_id': parent_id,
'attachment_ids': attachment_ids,
})
- for x in ('from','to','cc'): values.pop(x, None) # Avoid warnings
+ for x in ('from', 'to', 'cc'): values.pop(x, None) # Avoid warnings
return self.pool.get('mail.message').create(cr, uid, values, context=context)
#------------------------------------------------------
@@ -641,7 +642,6 @@ class mail_thread(osv.AbstractModel):
if context and context.get('read_back'):
return [follower.id for thread in self.browse(cr, uid, ids, context=context) for follower in thread.message_follower_ids]
return []
-
def message_unsubscribe_users(self, cr, uid, ids, user_ids=None, context=None):
""" Wrapper on message_subscribe, using users. If user_ids is not
@@ -666,10 +666,9 @@ class mail_thread(osv.AbstractModel):
def message_mark_as_unread(self, cr, uid, ids, context=None):
""" Set as unread. """
- notobj = self.pool.get('mail.notification')
partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id
cr.execute('''
- UPDATE mail_notification SET
+ UPDATE mail_notification SET
read=false
WHERE
message_id IN (SELECT id from mail_message where res_id=any(%s) and model=%s limit 1) and
@@ -679,10 +678,9 @@ class mail_thread(osv.AbstractModel):
def message_mark_as_read(self, cr, uid, ids, context=None):
""" Set as read. """
- notobj = self.pool.get('mail.notification')
partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id
cr.execute('''
- UPDATE mail_notification SET
+ UPDATE mail_notification SET
read=true
WHERE
message_id IN (SELECT id FROM mail_message WHERE res_id=ANY(%s) AND model=%s) AND
diff --git a/addons/mail/static/src/xml/mail.xml b/addons/mail/static/src/xml/mail.xml
index e31a23ae517..f6be765c603 100644
--- a/addons/mail/static/src/xml/mail.xml
+++ b/addons/mail/static/src/xml/mail.xml
@@ -79,7 +79,7 @@
container, holding the composition form. Then come the various
messages. Then comes the 'more' button.
-->
-
+
@@ -94,7 +94,7 @@
-
+
diff --git a/addons/mail/static/src/xml/mail_followers.xml b/addons/mail/static/src/xml/mail_followers.xml
index 66173dd47ee..e5fcf802de1 100644
--- a/addons/mail/static/src/xml/mail_followers.xml
+++ b/addons/mail/static/src/xml/mail_followers.xml
@@ -5,7 +5,7 @@
followers main template
Template used to display the followers, the actions and the subtypes in a record.
-->
-
+
diff --git a/addons/mail/tests/test_mail.py b/addons/mail/tests/test_mail.py
index 45583d46fa3..a8c1d6b7cf1 100644
--- a/addons/mail/tests/test_mail.py
+++ b/addons/mail/tests/test_mail.py
@@ -19,10 +19,8 @@
#
##############################################################################
-import base64
-import tools
-
from openerp.tests import common
+from openerp.tools.html_sanitize import html_sanitize
MAIL_TEMPLATE = """Return-Path:
To: {to}
@@ -65,6 +63,25 @@ Content-Transfer-Encoding: quoted-printable
------=_Part_4200734_24778174.1344608186754--
"""
+MAIL_TEMPLATE_PLAINTEXT = """Return-Path:
+To: {to}
+Received: by mail1.openerp.com (Postfix, from userid 10002)
+ id 5DF9ABFB2A; Fri, 10 Aug 2012 16:16:39 +0200 (CEST)
+From: Sylvie Lelitre
+Subject: {subject}
+MIME-Version: 1.0
+Content-Type: text/plain
+Date: Fri, 10 Aug 2012 14:16:26 +0000
+Message-ID: {msg_id}
+{extra}
+
+Please call me as soon as possible this afternoon!
+
+--
+Sylvie
+"""
+
+
class test_mail(common.TransactionCase):
def _mock_smtp_gateway(self, *args, **kwargs):
@@ -95,10 +112,9 @@ class test_mail(common.TransactionCase):
self.registry('ir.mail_server').send_email = self._mock_smtp_gateway
# groups@.. will cause the creation of new mail groups
- self.mail_group_model_id = self.ir_model.search(self.cr, self.uid, [('model','=', 'mail.group')])[0]
+ self.mail_group_model_id = self.ir_model.search(self.cr, self.uid, [('model', '=', 'mail.group')])[0]
self.mail_alias.create(self.cr, self.uid, {'alias_name': 'groups',
'alias_model_id': self.mail_group_model_id})
-
# create a 'pigs' group that will be used through the various tests
self.group_pigs_id = self.mail_group.create(self.cr, self.uid,
{'name': 'Pigs', 'description': 'Fans of Pigs, unite !'})
@@ -106,10 +122,10 @@ class test_mail(common.TransactionCase):
def test_00_message_process(self):
cr, uid = self.cr, self.uid
# Incoming mail creates a new mail_group "frogs"
- self.assertEqual(self.mail_group.search(cr, uid, [('name','=','frogs')]), [])
+ self.assertEqual(self.mail_group.search(cr, uid, [('name', '=', 'frogs')]), [])
mail_frogs = MAIL_TEMPLATE.format(to='groups@example.com, other@gmail.com', subject='frogs', extra='')
self.mail_thread.message_process(cr, uid, None, mail_frogs)
- frog_groups = self.mail_group.search(cr, uid, [('name','=','frogs')])
+ frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'frogs')])
self.assertTrue(len(frog_groups) == 1)
# Previously-created group can be emailed now - it should have an implicit alias group+frogs@...
@@ -123,17 +139,25 @@ class test_mail(common.TransactionCase):
# Even with a wrong destination, a reply should end up in the correct thread
mail_reply = MAIL_TEMPLATE.format(to='erroneous@example.com>', subject='Re: news',
- extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>\n'%frog_group.id)
+ extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>\n' % frog_group.id)
self.mail_thread.message_process(cr, uid, None, mail_reply)
frog_group.refresh()
self.assertTrue(len(frog_group.message_ids) == 3, 'Group should contain 3 messages now')
-
+
# No model passed and no matching alias must raise
mail_spam = MAIL_TEMPLATE.format(to='noone@example.com', subject='spam', extra='')
self.assertRaises(Exception,
self.mail_thread.message_process,
cr, uid, None, mail_spam)
+ # plain text content should be wrapped and stored as html
+ test_msg_id = ''
+ mail_text = MAIL_TEMPLATE_PLAINTEXT.format(to='groups@example.com', subject='frogs', extra='', msg_id=test_msg_id)
+ self.mail_thread.message_process(cr, uid, None, mail_text)
+ new_mail = self.mail_message.browse(cr, uid, self.mail_message.search(cr, uid, [('message_id','=',test_msg_id)])[0])
+ self.assertEqual(new_mail.body, '\n
\nPlease call me as soon as possible this afternoon!\n\n--\nSylvie\n
\n',
+ 'plaintext mail incorrectly parsed')
+
def test_10_many2many_reference_field(self):
""" Tests designed for the many2many_reference field (follower_ids).
We will test to perform writes using the many2many commands 0, 3, 4,
@@ -194,7 +218,7 @@ class test_mail(common.TransactionCase):
self.assertEqual(follower_ids, set([partner_bert_id]), 'Bert should be the follower of dummy mail.thread data')
fol_obj_ids = self.mail_followers.search(cr, uid, [('res_model', '=', 'mail.group'), ('res_id', '=', group_dummy_id)])
follower_ids = set([follower.partner_id.id for follower in self.mail_followers.browse(cr, uid, fol_obj_ids)])
- self.assertEqual(follower_ids,set([partner_bert_id, user_admin.partner_id.id]), 'Bert and Admin should be the followers of dummy mail.group data')
+ self.assertEqual(follower_ids, set([partner_bert_id, user_admin.partner_id.id]), 'Bert and Admin should be the followers of dummy mail.group data')
def test_11_message_followers(self):
""" Tests designed for the subscriber API. """
@@ -246,24 +270,19 @@ class test_mail(common.TransactionCase):
_mail_body1 = 'Pigs rules\n
' % (reply_header, message_data.body)
+ 'sender_name': message_data.author_id.name,
+ 'body': message_data.body,
+ }
# get partner_ids from original message
partner_ids = [partner.id for partner in message_data.partner_ids] if message_data.partner_ids else []
# update the result
- result.update({
+ result = {
'model': message_data.model,
'res_id': message_data.res_id,
'parent_id': message_data.id,
@@ -175,11 +172,11 @@ class mail_compose_message(osv.TransientModel):
'subject': reply_subject,
'partner_ids': partner_ids,
'content_subtype': 'html',
- })
+ }
return result
def toggle_content_subtype(self, cr, uid, ids, context=None):
- """ hit toggle formatting mode button: calls onchange_formatting to
+ """ hit toggle formatting mode button: calls onchange_formatting to
emulate an on_change, then writes the value to update the form. """
for record in self.browse(cr, uid, ids, context=context):
content_st_new_value = 'plain' if record.content_subtype == 'html' else 'html'
@@ -209,17 +206,18 @@ class mail_compose_message(osv.TransientModel):
warning_msg += '\n- %s' % (partner.name)
return {'warning': {
'title': _('Partners email addresses not found'),
- 'message': warning_msg }
+ 'message': warning_msg,
+ }
}
def onchange_partner_ids(self, cr, uid, ids, value, context=None):
- """ onchange_partner_ids (value format: [[6, False, [3, 4]]]). The
+ """ onchange_partner_ids (value format: [[6, 0, [3, 4]]]). The
basic purpose of this method is to check that destination partners
effectively have email addresses. Otherwise a warning is thrown.
"""
res = {'value': {}}
if not value or not value[0] or not value[0][0] == 6:
- return
+ return
res.update(self._verify_partner_email(cr, uid, value[0][2], context=context))
return res
@@ -227,7 +225,7 @@ class mail_compose_message(osv.TransientModel):
# Cascade delete all attachments, as they are owned by the composition wizard
for wizard in self.read(cr, uid, ids, ['attachment_ids'], context=context):
self.pool.get('ir.attachment').unlink(cr, uid, wizard['attachment_ids'], context=context)
- return super(mail_compose_message,self).unlink(cr, uid, ids, context=context)
+ return super(mail_compose_message, self).unlink(cr, uid, ids, context=context)
def dummy(self, cr, uid, ids, context=None):
""" TDE: defined to have buttons that do basically nothing. It is
@@ -258,13 +256,13 @@ class mail_compose_message(osv.TransientModel):
'subject': wizard.subject if wizard.content_subtype == 'html' else False,
'body': wizard.body if wizard.content_subtype == 'html' else '
%s
' % tools.ustr(wizard.body_text),
'partner_ids': [(4, partner.id) for partner in wizard.partner_ids],
- 'attachments': [(attach.name or attach.datas_fname, base64.b64decode(attach.datas)) for attach in wizard.attachment_ids],
+ 'attachments': [(attach.datas_fname or attach.name, base64.b64decode(attach.datas)) 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, wizard.model, res_id, context=context)
+ email_dict = self.render_message(cr, uid, wizard, res_id, context=context)
new_partner_ids = email_dict.pop('partner_ids', [])
- post_values['partner_ids'] += new_partner_ids
+ post_values['partner_ids'] += [(4, partner_id) for partner_id in new_partner_ids]
new_attachments = email_dict.pop('attachments', [])
post_values['attachments'] += new_attachments
post_values.update(email_dict)
@@ -273,14 +271,13 @@ class mail_compose_message(osv.TransientModel):
return {'type': 'ir.actions.act_window_close'}
- def render_message(self, cr, uid, wizard, model, res_id, context=None):
- """ Generate an email from the template for given (model, res_id) pair.
- This method is meant to be inherited by email_template that will
- produce a more complete dictionary, with email_to, ...
- """
+ 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, model, res_id, context),
- 'body': self.render_template(cr, uid, wizard.body, model, res_id, context),
+ '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),
}
def render_template(self, cr, uid, template, model, res_id, context=None):
@@ -302,8 +299,8 @@ class mail_compose_message(osv.TransientModel):
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.get(model).browse(cr, uid, res_id, context=context),
+ 'user': self.pool.get('res.users').browse(cr, uid, uid, context=context),
+ 'object': self.pool.get(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 ''
diff --git a/addons/note/note.py b/addons/note/note.py
index f6a46c72c88..bce54566e64 100644
--- a/addons/note/note.py
+++ b/addons/note/note.py
@@ -46,6 +46,9 @@ class note_note(osv.osv):
_pad_fields = ['note_pad']
_description = "Note"
+ def _set_note_first_line(self, cr, uid, id, name, value, args, context=None):
+ return self.write(cr, uid, [id], {'note': value}, context=context)
+
def _get_note_first_line(self, cr, uid, ids, name, args, context=None):
res = {}
for note in self.browse(cr, uid, ids, context=context):
@@ -68,7 +71,7 @@ class note_note(osv.osv):
return result
_columns = {
- 'name': fields.function(_get_note_first_line, string='Note Summary', type="text", store=True),
+ 'name': fields.function(_get_note_first_line, fnct_inv=_set_note_first_line, string='Note Summary', type="text"),
'note': fields.text('Pad Content'),
'note_pad_url': fields.char('Pad Url', size=250),
'sequence': fields.integer('Sequence'),
diff --git a/addons/note/note_view.xml b/addons/note/note_view.xml
index 5283874f0c6..73636ce1a9a 100644
--- a/addons/note/note_view.xml
+++ b/addons/note/note_view.xml
@@ -57,7 +57,7 @@
-
- Please configure your etherpad server.
- OpenERP Entreprise customers may safely use pad.openerp.com
+ You must configure the etherpad through the menu Setting > Companies > Companies, in the configuration tab of your company.
diff --git a/addons/plugin_outlook/plugin_outlook.xml b/addons/plugin_outlook/plugin_outlook.xml
index e99fbf72b6d..c2c035f532d 100644
--- a/addons/plugin_outlook/plugin_outlook.xml
+++ b/addons/plugin_outlook/plugin_outlook.xml
@@ -19,7 +19,7 @@
Click on the link above to download the installer for either 32 or 64 bits, and execute it.
2. From the Thunderbird menubar: Tools > Add-ons -> Screwdriver/Wrench Icon -> Install add-on from file...
3. Select the plug-in (the file named openerp_plugin.xpi).
diff --git a/addons/point_of_sale/point_of_sale.py b/addons/point_of_sale/point_of_sale.py
index 24c482bd53c..2d6d8b92da1 100644
--- a/addons/point_of_sale/point_of_sale.py
+++ b/addons/point_of_sale/point_of_sale.py
@@ -422,7 +422,7 @@ class pos_session(osv.osv):
self.pool.get('pos.order')._create_account_move_line(cr, uid, order_ids, session, move_id, context=context)
for order in session.order_ids:
- if order.state != 'paid':
+ if order.state not in ('paid', 'invoiced'):
raise osv.except_osv(
_('Error!'),
_("You cannot confirm all orders of this session, because they have not the 'paid' status"))
diff --git a/addons/point_of_sale/point_of_sale_view.xml b/addons/point_of_sale/point_of_sale_view.xml
index 4a56c0dda3c..6dffbd82d19 100644
--- a/addons/point_of_sale/point_of_sale_view.xml
+++ b/addons/point_of_sale/point_of_sale_view.xml
@@ -897,7 +897,7 @@
-
+
@@ -906,7 +906,7 @@
-
+
@@ -916,14 +916,14 @@
-
+
-
+
You can define another list of available currencies on the
Cash Registers tab of the
@@ -933,7 +933,7 @@