diff --git a/addons/crm/crm_lead_demo.xml b/addons/crm/crm_lead_demo.xml
index 6d392097d84..746936e28cd 100644
--- a/addons/crm/crm_lead_demo.xml
+++ b/addons/crm/crm_lead_demo.xml
@@ -666,7 +666,6 @@ Andrew
comment
-
crm.lead
@@ -676,7 +675,6 @@ Andrew
-
Inquiry
@@ -694,6 +692,9 @@ Andrew
Want to know features and benefits to use the new software.
comment
+
diff --git a/addons/mail/mail_followers.py b/addons/mail/mail_followers.py
index 8d3876ec91f..9793e036351 100644
--- a/addons/mail/mail_followers.py
+++ b/addons/mail/mail_followers.py
@@ -19,7 +19,6 @@
#
##############################################################################
-from openerp import SUPERUSER_ID
from osv import osv
from osv import fields
import tools
@@ -63,18 +62,21 @@ class mail_notification(osv.Model):
'partner_id': fields.many2one('res.partner', string='Contact',
ondelete='cascade', required=True, select=1),
'read': fields.boolean('Read', select=1),
+ 'starred': fields.boolean('Starred', select=1,
+ help='Starred message that goes into the todo mailbox'),
'message_id': fields.many2one('mail.message', string='Message',
ondelete='cascade', required=True, select=1),
}
_defaults = {
'read': False,
+ 'starred': False,
}
def init(self, cr):
- cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = %s', ('mail_notification_partner_id_read_message_id',))
+ cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = %s', ('mail_notification_partner_id_read_starred_message_id',))
if not cr.fetchone():
- cr.execute('CREATE INDEX mail_notification_partner_id_read_message_id ON mail_notification (partner_id, read, message_id)')
+ cr.execute('CREATE INDEX mail_notification_partner_id_read_starred_message_id ON mail_notification (partner_id, read, starred, message_id)')
def create(self, cr, uid, vals, context=None):
""" Override of create to check that we can not create a notification
@@ -83,31 +85,6 @@ class mail_notification(osv.Model):
return super(mail_notification, self).create(cr, uid, vals, context=context)
return False
- def set_message_read(self, cr, uid, msg_ids, read=None, context=None):
- """ Set messages as (un)read. Technically, the notifications related
- to uid are set to (un)read. If for some msg_ids there are missing
- notifications (i.e. due to load more or thread parent fetching),
- they are created.
-
- :param bool read: (un)read notification
- """
- user_pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
- notif_ids = self.search(cr, uid, [
- ('partner_id', '=', user_pid),
- ('message_id', 'in', msg_ids)
- ], context=context)
-
- # all message have notifications: already set them as (un)read
- if len(notif_ids) == len(msg_ids):
- return self.write(cr, uid, notif_ids, {'read': read}, context=context)
-
- # some messages do not have notifications: find which one, create notification, update read status
- notified_msg_ids = [notification.message_id.id for notification in self.browse(cr, uid, notif_ids, context=context)]
- to_create_msg_ids = list(set(msg_ids) - set(notified_msg_ids))
- for msg_id in to_create_msg_ids:
- self.create(cr, uid, {'partner_id': user_pid, 'read': read, 'message_id': msg_id}, context=context)
- return self.write(cr, uid, notif_ids, {'read': read}, context=context)
-
def get_partners_to_notify(self, cr, uid, message, context=None):
""" Return the list of partners to notify, based on their preferences.
diff --git a/addons/mail/mail_message.py b/addons/mail/mail_message.py
index aec106f766b..a5080e48373 100644
--- a/addons/mail/mail_message.py
+++ b/addons/mail/mail_message.py
@@ -55,7 +55,7 @@ class mail_message(osv.Model):
_message_read_limit = 30
_message_read_fields = ['id', 'parent_id', 'model', 'res_id', 'body', 'subject', 'date', 'to_read', 'email_from',
- 'type', 'vote_user_ids', 'attachment_ids', 'author_id', 'partner_ids', 'record_name', 'favorite_user_ids']
+ 'type', 'vote_user_ids', 'attachment_ids', 'author_id', 'partner_ids', 'record_name']
_message_record_name_length = 18
_message_read_more_limit = 1024
@@ -63,7 +63,7 @@ class mail_message(osv.Model):
# protection for `default_type` values leaking from menu action context (e.g. for invoices)
if context and context.get('default_type') and context.get('default_type') not in self._columns['type'].selection:
context = dict(context, default_type=None)
- return super(mail_message, self).default_get(cr, uid, fields, context=context)
+ return super(mail_message, self).default_get(cr, uid, fields, context=context)
def _shorten_name(self, name):
if len(name) <= (self._message_record_name_length + 3):
@@ -98,15 +98,26 @@ class mail_message(osv.Model):
def _search_to_read(self, cr, uid, obj, name, domain, context=None):
""" Search for messages to read 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)"
- else:
- read_cond = "read = True"
+ return ['&', ('notification_ids.partner_id.user_ids', 'in', [uid]), ('notification_ids.read', '=', not domain[0][2])]
+
+ def _get_starred(self, cr, uid, ids, name, arg, context=None):
+ """ Compute if the message is unread by the current user. """
+ res = dict((id, False) for id in ids)
partner_id = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
- cr.execute("SELECT message_id FROM mail_notification "\
- "WHERE partner_id = %%s AND %s" % read_cond,
- (partner_id,))
- return [('id', 'in', [r[0] for r in cr.fetchall()])]
+ notif_obj = self.pool.get('mail.notification')
+ notif_ids = notif_obj.search(cr, uid, [
+ ('partner_id', 'in', [partner_id]),
+ ('message_id', 'in', ids),
+ ('starred', '=', True),
+ ], context=context)
+ for notif in notif_obj.browse(cr, uid, notif_ids, context=context):
+ res[notif.message_id.id] = True
+ return res
+
+ def _search_starred(self, cr, uid, obj, name, domain, context=None):
+ """ Search for messages to read by the current user. Condition is
+ inversed because we search unread message on a read column. """
+ return ['&', ('notification_ids.partner_id.user_ids', 'in', [uid]), ('notification_ids.starred', '=', domain[0][2])]
def name_get(self, cr, uid, ids, context=None):
# name_get may receive int id instead of an id list
@@ -146,7 +157,7 @@ class mail_message(osv.Model):
store=True, string='Message Record Name',
help="Name get of the related document."),
'notification_ids': fields.one2many('mail.notification', 'message_id',
- string='Notifications',
+ string='Notifications', auto_join=True,
help='Technical field holding the message notifications. Use notified_partner_ids to access notified partners.'),
'subject': fields.char('Subject'),
'date': fields.datetime('Date'),
@@ -154,21 +165,19 @@ class mail_message(osv.Model):
'body': fields.html('Contents', help='Automatically sanitized HTML contents'),
'to_read': fields.function(_get_to_read, fnct_search=_search_to_read,
type='boolean', string='To read',
- help='Functional field to search for messages the current user has to read'),
+ help='Current user has an unread notification linked to this message'),
+ 'starred': fields.function(_get_starred, fnct_search=_search_starred,
+ type='boolean', string='Starred',
+ help='Current user has a starred notification linked to this message'),
'subtype_id': fields.many2one('mail.message.subtype', 'Subtype',
ondelete='set null', select=1,),
'vote_user_ids': fields.many2many('res.users', 'mail_vote',
'message_id', 'user_id', string='Votes',
help='Users that voted for this message'),
- 'favorite_user_ids': fields.many2many('res.users', 'mail_favorite',
- 'message_id', 'user_id', string='Favorite',
- help='Users that set this message in their favorites'),
}
def _needaction_domain_get(self, cr, uid, context=None):
- if self._needaction:
- return [('to_read', '=', True)]
- return []
+ return [('to_read', '=', True)]
def _get_default_author(self, cr, uid, context=None):
return self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
@@ -196,27 +205,62 @@ class mail_message(osv.Model):
return new_has_voted or False
#------------------------------------------------------
- # Favorite
+ # Notification API
#------------------------------------------------------
- def favorite_toggle(self, cr, uid, ids, context=None):
- ''' Toggles favorite. Performed using read to avoid access rights issues.
- Done as SUPERUSER_ID because uid may star a message he cannot modify. '''
- for message in self.read(cr, uid, ids, ['favorite_user_ids'], context=context):
- new_is_favorite = not (uid in message.get('favorite_user_ids'))
- if new_is_favorite:
- self.write(cr, SUPERUSER_ID, message.get('id'), {'favorite_user_ids': [(4, uid)]}, context=context)
- # when setting a favorite, set the related notification as unread, or create an unread notification if not existing
- notification_obj = self.pool.get('mail.notification')
- pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=None)['partner_id'][0]
- notif_id = notification_obj.search(cr, SUPERUSER_ID, [('message_id', '=', message.get('id')), ('partner_id', '=', pid)], context=context)
- if notif_id:
- notification_obj.write(cr, SUPERUSER_ID, notif_id, {'read': False}, context=context)
- else:
- notification_obj.create(cr, SUPERUSER_ID, {'message_id': message.get('id'), 'partner_id': pid, 'read': False}, context=context)
- else:
- self.write(cr, SUPERUSER_ID, message.get('id'), {'favorite_user_ids': [(3, uid)]}, context=context)
- return new_is_favorite or False
+ def set_message_read(self, cr, uid, msg_ids, read, context=None):
+ """ Set messages as (un)read. Technically, the notifications related
+ to uid are set to (un)read. If for some msg_ids there are missing
+ notifications (i.e. due to load more or thread parent fetching),
+ they are created.
+
+ :param bool read: set notification as (un)read
+ """
+ notification_obj = self.pool.get('mail.notification')
+ user_pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
+ notif_ids = notification_obj.search(cr, uid, [
+ ('partner_id', '=', user_pid),
+ ('message_id', 'in', msg_ids)
+ ], context=context)
+
+ # all message have notifications: already set them as (un)read
+ if len(notif_ids) == len(msg_ids):
+ return notification_obj.write(cr, uid, notif_ids, {'read': read}, context=context)
+
+ # some messages do not have notifications: find which one, create notification, update read status
+ notified_msg_ids = [notification.message_id.id for notification in notification_obj.browse(cr, uid, notif_ids, context=context)]
+ to_create_msg_ids = list(set(msg_ids) - set(notified_msg_ids))
+ for msg_id in to_create_msg_ids:
+ notification_obj.create(cr, uid, {'partner_id': user_pid, 'read': read, 'message_id': msg_id}, context=context)
+ return notification_obj.write(cr, uid, notif_ids, {'read': read}, context=context)
+
+ def set_message_starred(self, cr, uid, msg_ids, starred, context=None):
+ """ Set messages as (un)starred. Technically, the notifications related
+ to uid are set to (un)starred. If for some msg_ids there are missing
+ notifications (i.e. due to load more or thread parent fetching),
+ they are created.
+
+ :param bool starred: set notification as (un)starred
+ """
+ notification_obj = self.pool.get('mail.notification')
+ user_pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
+ notif_ids = notification_obj.search(cr, uid, [
+ ('partner_id', '=', user_pid),
+ ('message_id', 'in', msg_ids)
+ ], context=context)
+
+ # all message have notifications: already set them as (un)starred
+ if len(notif_ids) == len(msg_ids):
+ notification_obj.write(cr, uid, notif_ids, {'starred': starred}, context=context)
+ return starred
+
+ # some messages do not have notifications: find which one, create notification, update starred status
+ notified_msg_ids = [notification.message_id.id for notification in notification_obj.browse(cr, uid, notif_ids, context=context)]
+ to_create_msg_ids = list(set(msg_ids) - set(notified_msg_ids))
+ for msg_id in to_create_msg_ids:
+ notification_obj.create(cr, uid, {'partner_id': user_pid, 'starred': starred, 'message_id': msg_id}, context=context)
+ notification_obj.write(cr, uid, notif_ids, {'starred': starred}, context=context)
+ return starred
#------------------------------------------------------
# Message loading for web interface
@@ -291,7 +335,6 @@ class mail_message(osv.Model):
# votes and favorites: res.users ids, no prefetching should be done
vote_nb = len(message.vote_user_ids)
has_voted = uid in [user.id for user in message.vote_user_ids]
- is_favorite = uid in [user.id for user in message.favorite_user_ids]
return {'id': message.id,
'type': message.type,
@@ -309,7 +352,7 @@ class mail_message(osv.Model):
'partner_ids': [],
'vote_nb': vote_nb,
'has_voted': has_voted,
- 'is_favorite': is_favorite,
+ 'is_favorite': message.starred,
'attachment_ids': [],
}
@@ -482,15 +525,6 @@ class mail_message(osv.Model):
thread_level=thread_level, message_unload_ids=message_unload_ids, domain=domain, parent_id=parent_id, context=context)
return message_list
- # TDE Note: do we need this ?
- # def user_free_attachment(self, cr, uid, context=None):
- # attachment = self.pool.get('ir.attachment')
- # attachment_list = []
- # attachment_ids = attachment.search(cr, uid, [('res_model', '=', 'mail.message'), ('create_uid', '=', uid)])
- # if len(attachment_ids):
- # attachment_list = [{'id': attach.id, 'name': attach.name, 'date': attach.create_date} for attach in attachment.browse(cr, uid, attachment_ids, context=context)]
- # return attachment_list
-
#------------------------------------------------------
# mail_message internals
#------------------------------------------------------
@@ -648,12 +682,23 @@ class mail_message(osv.Model):
(self._description, operation))
def create(self, cr, uid, values, context=None):
+ if context is None:
+ context = {}
+ default_starred = context.pop('default_starred', False)
if not values.get('message_id') and values.get('res_id') and values.get('model'):
values['message_id'] = tools.generate_tracking_message_id('%(res_id)s-%(model)s' % values)
elif not values.get('message_id'):
values['message_id'] = tools.generate_tracking_message_id('private')
newid = super(mail_message, self).create(cr, uid, values, context)
self._notify(cr, SUPERUSER_ID, newid, context=context)
+ # TDE FIXME: handle default_starred. Why not setting an inv on starred ?
+ # Because starred will call set_message_starred, that looks for notifications.
+ # When creating a new mail_message, it will create a notification to a message
+ # that does not exist, leading to an error (key not existing). Also this
+ # this means unread notifications will be created, yet we can not assure
+ # this is what we want.
+ if default_starred:
+ self.set_message_starred(cr, uid, [newid], True, context=context)
return newid
def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'):
diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py
index 30954be4ed8..e706ad66a33 100644
--- a/addons/mail/mail_thread.py
+++ b/addons/mail/mail_thread.py
@@ -88,7 +88,7 @@ class mail_thread(osv.AbstractModel):
for thread in self.browse(cr, uid, ids, context=context):
cls = res[thread.id]['message_unread'] and ' class="oe_kanban_mail_new"' or ''
- res[thread.id]['message_summary'] = "9 %d + %d" % (cls, len(thread.message_comment_ids), len(thread.message_follower_ids))
+ res[thread.id]['message_summary'] = "9 %d + %d" % (cls, len(thread.message_ids), len(thread.message_follower_ids))
return res
@@ -121,18 +121,8 @@ class mail_thread(osv.AbstractModel):
return res
- def _search_unread(self, cr, uid, obj=None, name=None, domain=None, context=None):
- partner_id = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
- res = {}
- notif_obj = self.pool.get('mail.notification')
- notif_ids = notif_obj.search(cr, uid, [
- ('partner_id', '=', partner_id),
- ('message_id.model', '=', self._name),
- ('read', '=', False)
- ], 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())]
+ def _search_message_unread(self, cr, uid, obj=None, name=None, domain=None, context=None):
+ return [('message_ids.to_read', '=', True)]
def _get_followers(self, cr, uid, ids, name, arg, context=None):
fol_obj = self.pool.get('mail.followers')
@@ -201,16 +191,14 @@ class mail_thread(osv.AbstractModel):
'message_follower_ids': fields.function(_get_followers, fnct_inv=_set_followers,
fnct_search=_search_followers, type='many2many',
obj='res.partner', string='Followers', multi='_get_followers'),
- 'message_comment_ids': fields.one2many('mail.message', 'res_id',
- domain=lambda self: [('model', '=', self._name), ('type', 'in', ('comment', 'email'))],
- string='Comments and emails',
- help="Comments and emails"),
'message_ids': fields.one2many('mail.message', 'res_id',
domain=lambda self: [('model', '=', self._name)],
+ auto_join=True,
string='Messages',
help="Messages and communication history"),
- 'message_unread': fields.function(_get_message_data, fnct_search=_search_unread,
- type='boolean', string='Unread Messages', multi="_get_message_data",
+ 'message_unread': fields.function(_get_message_data,
+ fnct_search=_search_message_unread, multi="_get_message_data",
+ type='boolean', string='Unread Messages',
help="If checked new messages require your attention."),
'message_summary': fields.function(_get_message_data, method=True,
type='text', string='Summary', multi="_get_message_data",
@@ -245,7 +233,6 @@ class mail_thread(osv.AbstractModel):
def copy(self, cr, uid, id, default=None, context=None):
default = default or {}
default['message_ids'] = []
- default['message_comment_ids'] = []
default['message_follower_ids'] = []
return super(mail_thread, self).copy(cr, uid, id, default=default, context=context)
diff --git a/addons/mail/mail_thread_view.xml b/addons/mail/mail_thread_view.xml
index 4106eb2e024..d5c111573ea 100644
--- a/addons/mail/mail_thread_view.xml
+++ b/addons/mail/mail_thread_view.xml
@@ -11,9 +11,8 @@
}
@@ -63,7 +64,9 @@
'search_default_message_unread': True
}
@@ -85,7 +88,11 @@
'default_res_id': uid
}
diff --git a/addons/mail/static/src/js/mail.js b/addons/mail/static/src/js/mail.js
index 82534d713a3..7181367cf85 100644
--- a/addons/mail/static/src/js/mail.js
+++ b/addons/mail/static/src/js/mail.js
@@ -847,7 +847,7 @@ openerp.mail = function (session) {
}
var message_ids = _.map(messages, function (val) { return val.id; });
- this.ds_notification.call('set_message_read', [message_ids, read_value, this.context])
+ this.ds_message.call('set_message_read', [message_ids, read_value, this.context])
.then(function () {
// apply modification
_.each(messages, function (msg) {
@@ -896,7 +896,7 @@ openerp.mail = function (session) {
var self=this;
var button = self.$('.oe_star:first');
- this.ds_message.call('favorite_toggle', [[self.id]])
+ this.ds_message.call('set_message_starred', [[self.id], !self.is_favorite])
.then(function (star) {
self.is_favorite=star;
if (self.is_favorite) {
@@ -991,7 +991,7 @@ openerp.mail = function (session) {
// add message composition form view
if (!this.compose_message) {
this.compose_message = new mail.ThreadComposeMessage(this, this, {
- 'context': this.options.compose_as_todo && !this.thread_level ? _.extend(this.context, { 'default_favorite_user_ids': [this.session.uid] }) : this.context,
+ 'context': this.options.compose_as_todo && !this.thread_level ? _.extend(this.context, { 'default_starred': true }) : this.context,
'options': this.options,
});
if (!this.thread_level || this.thread_level > this.options.display_indented_thread) {
diff --git a/addons/mail/tests/test_mail.py b/addons/mail/tests/test_mail.py
index bc6df813ef7..c18dea46452 100644
--- a/addons/mail/tests/test_mail.py
+++ b/addons/mail/tests/test_mail.py
@@ -795,7 +795,7 @@ class test_mail(test_mail_mockup.TestMailMockups):
# Test: msg has Bert as voter
self.assertEqual(set(msg.vote_user_ids), set([user_raoul]), 'mail_message vote: after unvoting, Bert should be in the voter')
- def test_70_message_favorite(self):
+ def test_70_message_star(self):
""" Tests for favorites. """
cr, uid, user_admin, user_raoul, group_pigs = self.cr, self.uid, self.user_admin, self.user_raoul, self.group_pigs
# Data: post a message on Pigs
@@ -803,17 +803,18 @@ class test_mail(test_mail_mockup.TestMailMockups):
msg = self.mail_message.browse(cr, uid, msg_id)
# Do: Admin stars msg
- self.mail_message.favorite_toggle(cr, uid, [msg.id])
+ self.mail_message.set_message_starred(cr, uid, [msg.id], True)
msg.refresh()
# Test: msg starred by Admin
- self.assertEqual(set(msg.favorite_user_ids), set([user_admin]), 'mail_message favorite: after starring, Admin should be in favorite_user_ids')
+ self.assertTrue(msg.starred, 'mail_message starred failed')
# Do: Bert stars msg
- self.mail_message.favorite_toggle(cr, user_raoul.id, [msg.id])
+ self.mail_message.set_message_starred(cr, user_raoul.id, [msg.id], True)
msg.refresh()
# Test: msg starred by Admin and Raoul
- self.assertEqual(set(msg.favorite_user_ids), set([user_admin, user_raoul]), 'mail_message favorite: after starring, Admin and Raoul should be in favorite_user_ids')
+ # self.assertTrue(msg.starred, set([user_admin, user_raoul]), 'mail_message favorite: after starring, Admin and Raoul should be in favorite_user_ids')
# Do: Admin unvote for msg
- self.mail_message.favorite_toggle(cr, uid, [msg.id])
+ self.mail_message.set_message_starred(cr, uid, [msg.id], False)
msg.refresh()
# Test: msg starred by Raoul
- self.assertEqual(set(msg.favorite_user_ids), set([user_raoul]), 'mail_message favorite: after unstarring, Raoul should be in favorite_user_ids')
+ self.assertFalse(msg.starred, 'mail_message starred failed')
+ # self.assertEqual(set(msg.favorite_user_ids), set([user_raoul]), 'mail_message favorite: after unstarring, Raoul should be in favorite_user_ids')
diff --git a/addons/portal/portal_view.xml b/addons/portal/portal_view.xml
index b7765e63ffd..4aad43716b6 100644
--- a/addons/portal/portal_view.xml
+++ b/addons/portal/portal_view.xml
@@ -57,9 +57,8 @@
}