[CLEAN] Cleaned subtype model, implementation and tests. Added a message_subtype_data field, holding data about subtypes. This will allow to avoid doing too much calls from the front-end.

bzr revid: tde@openerp.com-20120920101704-cri6zq7sqi7y660b
This commit is contained in:
Thibault Delavallée
2012-09-20 12:17:04 +02:00
parent 7df8619cbd
commit ed3518d858
5 changed files with 135 additions and 88 deletions
+2 -4
View File
@@ -45,10 +45,8 @@ class mail_followers(osv.Model):
help='Id of the followed resource'),
'partner_id': fields.many2one('res.partner', string='Related Partner',
ondelete='cascade', required=True, select=1),
'subtype_ids': fields.many2many('mail.message.subtype',
'mail_message_subtyp_rel',
'subscription_id', 'subtype_id', 'Subtype',
help = "linking some subscription to several subtype for projet/task"),
'subtype_ids': fields.many2many('mail.message.subtype', string='Subtype',
help="Message subtypes followed, meaning subtypes that will be pushed onto the user's Wall."),
}
+12 -15
View File
@@ -354,27 +354,24 @@ class mail_message(osv.Model):
""" Add the related record followers to the destination partner_ids.
Call mail_notification.notify to manage the email sending
"""
followers_obj = self.pool.get("mail.followers")
message = self.browse(cr, uid, newid, context=context)
partners_to_notify = set([])
# add all partner_ids of the message
# message has no subtype_id: pure log message -> no partners, no one notified
if not message.subtype_id:
message.write({'partner_ids': [5]})
return True
# all partner_ids of the mail.message have to be notified
if message.partner_ids:
partners_to_notify |= set(partner.id for partner in message.partner_ids)
# add all followers and set add them in partner_ids
# all followers of the mail.message document have to be added as partners and notified
if message.model and message.res_id:
record = self.pool.get(message.model).browse(cr, uid, message.res_id, context=context)
extra_notified = set(partner.id for partner in record.message_follower_ids)
fol_obj = self.pool.get("mail.followers")
fol_ids = fol_obj.search(cr, uid, [('res_model', '=', message.model), ('res_id', '=', message.res_id), ('subtype_ids', 'in', message.subtype_id.id)], context=context)
fol_objs = fol_obj.browse(cr, uid, fol_ids, context=context)
extra_notified = set(fol.partner_id.id for fol in fol_objs)
missing_notified = extra_notified - partners_to_notify
missing_follow_ids = []
if message.subtype_id:
for p_id in missing_notified:
follow_ids = followers_obj.search(cr, uid, [('partner_id','=',p_id),('subtype_ids','in',[message.subtype_id.id]),('res_model','=',message.model),('res_id','=',message.res_id)], context=context)
if follow_ids and len(follow_ids):
missing_follow_ids.append(p_id)
subtype_record = self.pool.get('mail.message.subtype').browse(cr, uid, message.subtype_id.id,context=context)
if not subtype_record.res_model:
missing_follow_ids.append(p_id)
message.write({'partner_ids': [(4, p_id) for p_id in missing_follow_ids]})
if missing_notified:
message.write({'partner_ids': [(4, p_id) for p_id in missing_notified]})
partners_to_notify |= extra_notified
self.pool.get('mail.notification').notify(cr, uid, list(partners_to_notify), newid, context=context)
+69 -30
View File
@@ -95,6 +95,7 @@ class many2many_reference(fields.many2many):
else:
return super(many2many_reference, self).set(cr, model, id, name, values, user, context)
class mail_thread(osv.AbstractModel):
''' mail_thread model is meant to be inherited by any model that needs to
act as a discussion topic on which messages can be attached. Public
@@ -117,8 +118,10 @@ class mail_thread(osv.AbstractModel):
_description = 'Email Thread'
def _get_message_data(self, cr, uid, ids, name, args, context=None):
""" Computes:
- message_unread: has uid unread message for the document
- message_summary: html snippet summarizing the Chatter for kanban views """
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')
notif_ids = notif_obj.search(cr, uid, [
@@ -133,7 +136,35 @@ 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'] = "<span%s><span class='oe_e'>9</span> %d</span> <span><span class='oe_e'>+</span> %d</span>" % (cls, len(thread.message_comment_ids), len(thread.message_follower_ids))
res[thread.id]['message_is_follower'] = user.partner_id.id in [follower.id for follower in thread.message_follower_ids]
return res
def _get_subscription_data(self, cr, uid, ids, name, args, context=None):
""" Computes:
- message_is_follower: is uid in the document followers
- message_subtype_data: data about document subtypes: which are
available, which are followed if any """
res = dict((id, dict(message_subtype_data='')) for id in ids)
user_pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
# find current model subtypes, add them to a dictionary
subtype_obj = self.pool.get('mail.message.subtype')
subtype_ids = subtype_obj.search(cr, uid, ['|', ('res_model', '=', self._name), ('res_model', '=', False)], context=context)
subtype_dict = dict((subtype.name, dict(default=subtype.default, followed=False)) for subtype in subtype_obj.browse(cr, uid, subtype_ids, context=context))
# find the document followers, update the data
fol_obj = self.pool.get('mail.followers')
fol_ids = fol_obj.search(cr, uid, [
('partner_id', '=', user_pid),
('res_id', 'in', ids),
('res_model', '=', self._name),
], context=context)
for fol in fol_obj.browse(cr, uid, fol_ids, context=context):
thread_subtype_dict = subtype_dict.copy()
res[fol.res_id]['message_is_follower'] = True
for subtype in fol.subtype_ids:
thread_subtype_dict[subtype.name]['followed'] = True
res[fol.res_id]['message_subtype_data'] = '%s' % thread_subtype_dict
return res
def _search_unread(self, cr, uid, obj=None, name=None, domain=None, context=None):
@@ -150,8 +181,13 @@ class mail_thread(osv.AbstractModel):
return [('id', 'in', res.keys())]
_columns = {
'message_is_follower': fields.function(_get_message_data,
type='boolean', string='Is a Follower', multi='_get_message_data'),
'message_is_follower': fields.function(_get_subscription_data,
type='boolean', string='Is a Follower', multi='_get_subscription_data,'),
'message_subtype_data': fields.function(_get_subscription_data,
type='text', string='Subscription data', multi="_get_subscription_data",
help="Holds data about the subtypes. The content of this field "\
"is a structure holding the current model subtypes, and the "\
"current document followed subtypes."),
'message_follower_ids': many2many_reference('res.partner',
'mail_followers', 'res_id', 'partner_id',
reference_column='res_model', string='Followers'),
@@ -563,7 +599,7 @@ class mail_thread(osv.AbstractModel):
self.message_post(cr, uid, [id], message, context=context)
def message_post(self, cr, uid, thread_id, body='', subject=False, type='notification',
subtype_xml_id=None, parent_id=False, attachments=None, context=None, **kwargs):
subtype=None, parent_id=False, attachments=None, context=None, **kwargs):
""" Post a new message in an existing thread, returning the new
mail.message ID. Extra keyword arguments will be used as default
column values for the new mail.message record.
@@ -579,8 +615,8 @@ class mail_thread(osv.AbstractModel):
"""
context = context or {}
attachments = attachments or []
assert (not thread_id) or isinstance(thread_id, (int,long)) or \
(isinstance(thread_id, (list, tuple)) and len(thread_id) == 1), "Invalid thread_id"
assert (not thread_id) or isinstance(thread_id, (int, long)) or \
(isinstance(thread_id, (list, tuple)) and len(thread_id) == 1), "Invalid thread_id"
if isinstance(thread_id, (list, tuple)):
thread_id = thread_id and thread_id[0]
@@ -598,15 +634,13 @@ class mail_thread(osv.AbstractModel):
}
attachment_ids.append((0, 0, data_attach))
if subtype:
ref = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'mail', subtype)
subtype_id = ref and ref[1] or False
else:
subtype_id = False
values = kwargs
subtype_obj = self.pool.get('mail.message.subtype')
if subtype_xml_id:
ref = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'mail', subtype_xml_id)
subtype_browse = subtype_obj.browse(cr, uid, ref[1],context=context)
if self._name == subtype_browse.res_model:
values['subtype_id']=subtype_browse.id
if not subtype_browse.res_model:
values['subtype_id']=subtype_browse.id
values.update({
'model': context.get('thread_model', self._name) if thread_id else False,
'res_id': thread_id or False,
@@ -615,34 +649,44 @@ class mail_thread(osv.AbstractModel):
'type': type,
'parent_id': parent_id,
'attachment_ids': attachment_ids,
'subtype_id': subtype_id,
})
for x in ('from', 'to', 'cc'): values.pop(x, None) # Avoid warnings
# Avoid warnings about non-existing fields
for x in ('from', 'to', 'cc'):
values.pop(x, None)
return self.pool.get('mail.message').create(cr, uid, values, context=context)
#------------------------------------------------------
# Followers API
#------------------------------------------------------
def message_subscribe_users(self, cr, uid, ids, user_ids=None, context=None):
def message_subscribe_users(self, cr, uid, ids, user_ids=None, subtype_ids=None, context=None):
""" Wrapper on message_subscribe, using users. If user_ids is not
provided, subscribe uid instead. """
if not user_ids: user_ids = [uid]
if not user_ids:
user_ids = [uid]
partner_ids = [user.partner_id.id for user in self.pool.get('res.users').browse(cr, uid, user_ids, context=context)]
return self.message_subscribe(cr, uid, ids, partner_ids, context=context)
return self.message_subscribe(cr, uid, ids, partner_ids, subtype_ids=subtype_ids, context=context)
def message_subscribe(self, cr, uid, ids, partner_ids,subtype_ids=None, context=None):
def message_subscribe(self, cr, uid, ids, partner_ids, subtype_ids=None, context=None):
""" Add partners to the records followers. """
self.write(cr, uid, ids, {'message_follower_ids': [(4, pid) for pid in partner_ids]}, context=context)
if not subtype_ids:
subtype_obj = self.pool.get('mail.message.subtype')
subtype_ids = subtype_obj.search(cr, uid, [('default', '=', 'true'),('res_model', '=', self._name)],context=context)
if subtype_ids:
self.message_subscribe_udpate_subtypes(cr, uid, ids, partner_ids, subtype_ids, context=context)
return self.write(cr, uid, ids, {'message_follower_ids': [(4, pid) for pid in partner_ids]}, context=context)
subtype_ids = subtype_obj.search(cr, uid, [('default', '=', True), '|', ('res_model', '=', self._name), ('res_model', '=', False)], context=context)
fol_obj = self.pool.get('mail.followers')
fol_ids = fol_obj.search(cr, uid, [('res_model', '=', self._name), ('res_id', 'in', ids), ('partner_id', 'in', partner_ids)], context=context)
fol_obj.write(cr, uid, fol_ids, {'subtype_ids': [(6, 0, subtype_ids)]}, context=context)
return True
def message_unsubscribe_users(self, cr, uid, ids, user_ids=None, context=None):
""" Wrapper on message_subscribe, using users. If user_ids is not
provided, unsubscribe uid instead. """
if not user_ids: user_ids = [uid]
if not user_ids:
user_ids = [uid]
partner_ids = [user.partner_id.id for user in self.pool.get('res.users').browse(cr, uid, user_ids, context=context)]
return self.message_unsubscribe(cr, uid, ids, partner_ids, context=context)
@@ -678,9 +722,4 @@ class mail_thread(osv.AbstractModel):
''', (ids, self._name, partner_id))
return True
def message_subscribe_udpate_subtypes(self, cr, uid, ids, user_id, subtype_ids,context=None):
followers_obj = self.pool.get('mail.followers')
followers_ids = followers_obj.search(cr, uid, [('res_model', '=', self._name), ('res_id', 'in', ids)], context=context)
return followers_obj.write(cr, uid, followers_ids, {'subtype_ids': [(6, 0 , subtype_ids)]}, context = context)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+51 -38
View File
@@ -253,30 +253,65 @@ class test_mail(TestMailMockups):
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')
def test_11_message_followers(self):
""" Tests designed for the subscriber API. """
def test_11_message_followers_and_subtypes(self):
""" Tests designed for the subscriber API as well as message subtypes """
cr, uid = self.cr, self.uid
user_admin = self.res_users.browse(cr, uid, uid)
group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
# Create user Raoul
# Data: user Raoul
user_raoul_id = self.res_users.create(cr, uid, {'name': 'Raoul Grosbedon', 'login': 'raoul'})
user_raoul = self.res_users.browse(cr, uid, user_raoul_id)
# Data: message subtypes
self.mail_message_subtype.create(cr, uid, {'name': 'mt_mg_def', 'default': True, 'res_model': 'mail.group'})
self.mail_message_subtype.create(cr, uid, {'name': 'mt_other_def', 'default': True, 'res_model': 'crm.lead'})
self.mail_message_subtype.create(cr, uid, {'name': 'mt_all_def', 'default': True, 'res_model': False})
mt_mg_nodef = self.mail_message_subtype.create(cr, uid, {'name': 'mt_mg_nodef', 'default': False, 'res_model': 'mail.group'})
mt_all_nodef = self.mail_message_subtype.create(cr, uid, {'name': 'mt_all_nodef', 'default': False, 'res_model': False})
default_group_subtypes = self.mail_message_subtype.search(cr, uid, [('default', '=', True), '|', ('res_model', '=', 'mail.group'), ('res_model', '=', False)])
# Subscribe Raoul three times (niak niak) through message_subscribe_users
# ----------------------------------------
# CASE1: test subscriptions with subtypes
# ----------------------------------------
# Do: Subscribe Raoul three times (niak niak) through message_subscribe_users
group_pigs.message_subscribe_users([user_raoul_id, user_raoul_id])
group_pigs.message_subscribe_users([user_raoul_id])
group_pigs.refresh()
# Test: 2 followers (Admin and Raoul)
follower_ids = [follower.id for follower in group_pigs.message_follower_ids]
self.assertEqual(len(follower_ids), 2, 'There should be 2 Pigs fans')
self.assertEqual(set(follower_ids), set([user_raoul.partner_id.id, user_admin.partner_id.id]), 'Admin and Raoul should be the only 2 Pigs fans')
# Test: Raoul follows default subtypes
fol_ids = self.mail_followers.search(cr, uid, [('res_model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id), ('partner_id', '=', user_raoul.partner_id.id)])
fol_obj = self.mail_followers.browse(cr, uid, fol_ids)[0]
fol_subtype_ids = set([subtype.id for subtype in fol_obj.subtype_ids])
self.assertEqual(set(fol_subtype_ids), set(default_group_subtypes), 'subscription subtypes are incorrect')
# Unsubscribe Raoul twice through message_unsubscribe_users
# Do: Unsubscribe Raoul twice through message_unsubscribe_users
group_pigs.message_unsubscribe_users([user_raoul_id, user_raoul_id])
group_pigs.refresh()
# Test: 1 follower (Admin)
follower_ids = [follower.id for follower in group_pigs.message_follower_ids]
self.assertEqual(follower_ids, [user_admin.partner_id.id], 'Admin must be the only Pigs fan')
# Do: subscribe Admin with subtype_ids
group_pigs.message_subscribe_users([uid], [mt_mg_nodef, mt_all_nodef])
fol_ids = self.mail_followers.search(cr, uid, [('res_model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id), ('partner_id', '=', user_admin.partner_id.id)])
fol_obj = self.mail_followers.browse(cr, uid, fol_ids)[0]
fol_subtype_ids = set([subtype.id for subtype in fol_obj.subtype_ids])
self.assertEqual(set(fol_subtype_ids), set([mt_mg_nodef, mt_all_nodef]), 'subscription subtypes are incorrect')
# ----------------------------------------
# CASE2: test mail_thread fields
# ----------------------------------------
group_pigs.refresh()
subtype_data = eval(group_pigs.message_subtype_data)
self.assertEqual(set(subtype_data.keys()), set(['comment', 'mt_mg_def', 'mt_all_def', 'mt_mg_nodef', 'mt_all_nodef']), 'mail.group available subtypes incorrect')
self.assertFalse(subtype_data['comment']['followed'], 'Admin should not follow comments in pigs')
self.assertTrue(subtype_data['mt_mg_nodef']['followed'], 'Admin should follow mt_mg_nodef in pigs')
self.assertTrue(subtype_data['mt_all_nodef']['followed'], 'Admin should follow mt_all_nodef in pigs')
def test_20_message_post(self):
""" Tests designed for message_post. """
cr, uid = self.cr, self.uid
@@ -307,9 +342,12 @@ class test_mail(TestMailMockups):
_mail_bodyalt2 = 'Pigs rules\nAdmin'
_attachments = [('First', 'My first attachment'), ('Second', 'My second attachment')]
# ----------------------------------------
# CASE1: post comment, body and subject specified
# ----------------------------------------
self._init_mock_build_email()
msg_id = self.mail_group.message_post(cr, uid, self.group_pigs_id, body=_body1, subject=_subject, type='comment')
msg_id = self.mail_group.message_post(cr, uid, self.group_pigs_id, body=_body1, subject=_subject, type='comment', subtype='mt_comment')
message = self.mail_message.browse(cr, uid, msg_id)
sent_emails = self._build_email_kwargs_list
# Test: notifications have been deleted
@@ -334,10 +372,13 @@ class test_mail(TestMailMockups):
for sent_email in sent_emails:
self.assertEqual(sent_email['email_to'], ['b@b'], 'sent_email email_to is incorrect')
# ----------------------------------------
# CASE2: post an email with attachments, parent_id, partner_ids
# ----------------------------------------
# TESTS: automatic subject, signature in body_html, attachments propagation
self._init_mock_build_email()
msg_id2 = self.mail_group.message_post(cr, uid, self.group_pigs_id, body=_body2, type='email',
msg_id2 = self.mail_group.message_post(cr, uid, self.group_pigs_id, body=_body2, type='email', subtype='mt_comment',
partner_ids=[(6, 0, [p_d_id])], parent_id=msg_id, attachments=_attachments)
message = self.mail_message.browse(cr, uid, msg_id2)
sent_emails = self._build_email_kwargs_list
@@ -608,7 +649,7 @@ class test_mail(TestMailMockups):
# Post 4 message on group_pigs
for dummy in range(4):
group_pigs.message_post(body='My Body')
group_pigs.message_post(body='My Body', subtype='mt_comment')
# Check there are 4 new needaction on mail.message
notif_ids = self.mail_notification.search(cr, uid, [
@@ -652,6 +693,7 @@ class test_mail(TestMailMockups):
def test_60_vote(self):
""" Test designed for the vote/unvote feature. """
cr, uid = self.cr, self.uid
user_admin = self.res_users.browse(cr, uid, uid)
group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
msg1 = group_pigs.message_post(body='My Body', subject='1')
msg1 = self.mail_message.browse(cr, uid, msg1)
@@ -677,32 +719,3 @@ class test_mail(TestMailMockups):
msg1.refresh()
# Test: msg1 has Bert as voter
self.assertEqual(set(msg1.vote_user_ids), set([user_bert]), 'after unvoting for Admin, Bert is not the voter')
def test_70_message_subtype(self):
""" Tests designed for message_subtype. """
cr, uid = self.cr, self.uid
self.res_users.write(cr, uid, [uid], {'signature': 'Admin', 'email': 'a@a'})
user_admin = self.res_users.browse(cr, uid, uid)
group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
# 0 - Admin
p_a_id = user_admin.partner_id.id
# Subscribe #1,
group_pigs.message_subscribe_users([uid])
# Mail data
_subject = 'Pigs'
_mail_subject = '%s posted on %s' % (user_admin.name, group_pigs.name)
_body1 = 'Pigs rules'
_mail_body1 = 'Pigs rules\n<pre>Admin</pre>\n'
_mail_bodyalt1 = 'Pigs rules\nAdmin'
_body2 = '<html>Pigs rules</html>'
_mail_body2 = '<html>Pigs rules\n<pre>Admin</pre>\n</html>'
_mail_bodyalt2 = 'Pigs rules\nAdmin\n'
filter_subtype_id = self.mail_message_subtype.search(cr, uid, [('default','=',True)])
# Post comment with body and subject, comment preference
msg_id = self.mail_group.message_post(cr, uid, [self.group_pigs_id], body=_body1, subject=_subject, type='comment',subtype_xml_id='mt_comment')
notif_ids = self.mail_notification.search(cr, uid, [('message_id', '=', msg_id)])
self.assertTrue(len(notif_ids) >= 1,"subtype is email and show notification on wall")
+1 -1
View File
@@ -245,7 +245,7 @@ class mail_compose_message(osv.TransientModel):
post_values['attachments'] += new_attachments
post_values.update(email_dict)
# post the message
active_model_pool.message_post(cr, uid, [res_id], type='comment', context=context, **post_values)
active_model_pool.message_post(cr, uid, [res_id], type='comment', subtype='mt_comment', context=context, **post_values)
# post process: update attachments, because id is not necessarily known when adding attachments in Chatter
self.pool.get('ir.attachment').write(cr, uid, [attach.id for attach in wizard.attachment_ids], {'res_id': wizard.id}, context=context)