[MERGE] Mail/Portal fixes and tests.

Mail: fixes
- fixed employee user not allowed to invite people on documents (see bug lp:1075501)
- fixed a crash using email template in mass_mail mode without choosing a template (see bug lp:1078702
- fixed user not allowed to read mail_message displayed in their mailbox because of mission notification
- fixed user not allowed to read ir_attachment linked to displayed messages but without read access on the related document
- fixed design of mail.group that was inherits'ing from ir_ui_menu; removed the inheritance but added an explicit link and menu management inside mail_group
Portal:
- limited res_partner read access on their own partner for portal group members
- fixed access rights of group portal members for various models (based those defined for employees)
Misc:
- refactored a bit the various tests, now splitted in several files
- cleaned and updated tests about mail_message and Chatter flow
- more tests are using an employee instead of administrator to trigger access rights issues
- improved the tests related to mail in portal
- removed unused files of deprecated models mail_vote, mail_favorite

lp bug: https://launchpad.net/bugs/1075501 fixed
lp bug: https://launchpad.net/bugs/1078702 fixed

bzr revid: tde@openerp.com-20121214124410-jt18ai8ltw1h2xeo
This commit is contained in:
Thibault Delavallée 2012-12-14 13:44:10 +01:00
commit 1b800a75a4
37 changed files with 1073 additions and 919 deletions

View File

@ -20,22 +20,15 @@
##############################################################################
import base64
from openerp.addons.mail.tests import test_mail_mockup
from openerp.addons.mail.tests.test_mail_base import TestMailBase
class test_message_compose(test_mail_mockup.TestMailMockups):
class test_message_compose(TestMailBase):
def setUp(self):
super(test_message_compose, self).setUp()
self.mail_group = self.registry('mail.group')
self.mail_mail = self.registry('mail.mail')
self.mail_message = self.registry('mail.message')
self.res_users = self.registry('res.users')
self.res_partner = self.registry('res.partner')
# create a 'pigs' and 'bird' groups 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 !'})
self.group_bird_id = self.mail_group.create(self.cr, self.uid,
{'name': 'Bird', 'description': 'I am angry !'})

View File

@ -68,7 +68,7 @@ class mail_compose_message(osv.TransientModel):
values = self.pool.get('email.template').read(cr, uid, template_id, ['subject', 'body_html'], context)
values.pop('id')
elif template_id:
# FIXME odo: change the mail generation to avoid attachment duplication
# FIXME odo: change the mail generation to avoid attachment duplication
values = self.generate_email_for_composer(cr, uid, template_id, res_id, context=context)
# transform attachments into attachment_ids
values['attachment_ids'] = []
@ -142,12 +142,12 @@ class mail_compose_message(osv.TransientModel):
return values
def render_message(self, cr, uid, wizard, 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, ...
"""
""" Override to handle templates. """
# generate the composer email
values = self.generate_email_for_composer(cr, uid, wizard.template_id, res_id, context=context)
if wizard.template_id:
values = self.generate_email_for_composer(cr, uid, wizard.template_id, res_id, context=context)
else:
values = {}
# get values to return
email_dict = super(mail_compose_message, self).render_message(cr, uid, wizard, res_id, context)
email_dict.update(values)

View File

@ -22,8 +22,6 @@
import mail_message_subtype
import mail_alias
import mail_followers
import mail_vote
import mail_favorite
import mail_message
import mail_mail
import mail_thread

View File

@ -54,7 +54,6 @@ Main Features
'mail_message_view.xml',
'mail_mail_view.xml',
'mail_followers_view.xml',
'mail_favorite_view.xml',
'mail_thread_view.xml',
'mail_group_view.xml',
'res_partner_view.xml',

View File

@ -1,39 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2012-Today OpenERP SA (<http://www.openerp.com>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
#
##############################################################################
from osv import osv, fields
class mail_favorite(osv.Model):
''' Favorite model: relationship table between messages and users. A favorite
message is a message the user wants to see in a specific 'Favorite'
mailbox, like a starred mechanism. '''
_name = 'mail.favorite'
_description = 'Favorite messages'
_columns = {
'message_id': fields.many2one('mail.message', 'Message', select=1,
ondelete='cascade', required=True),
'user_id': fields.many2one('res.users', 'User', select=1,
ondelete='cascade', required=True),
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1,44 +0,0 @@
<?xml version="1.0"?>
<openerp>
<data>
<!-- FOLLOWERS !-->
<record model="ir.ui.view" id="view_mail_favorite_tree">
<field name="name">mail.favorite.tree</field>
<field name="model">mail.favorite</field>
<field name="arch" type="xml">
<tree string="Favorites">
<field name="user_id"/>
<field name="message_id"/>
</tree>
</field>
</record>
<record model="ir.ui.view" id="view_mail_favorite_form">
<field name="name">mail.favorite.form</field>
<field name="model">mail.favorite</field>
<field name="arch" type="xml">
<form string="Favorite Form" version="7.0">
<sheet>
<group>
<field name="user_id"/>
<field name="message_id"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="action_view_favorites" model="ir.actions.act_window">
<field name="name">Favorites</field>
<field name="res_model">mail.favorite</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
<!-- Add favorites related menu entries in Settings/Email -->
<menuitem name="Favorites" id="menu_email_favorites" parent="base.menu_email"
action="action_view_favorites" sequence="40" groups="base.group_no_one"/>
</data>
</openerp>

View File

@ -19,9 +19,9 @@
#
##############################################################################
from osv import osv
from osv import fields
import tools
from openerp import SUPERUSER_ID
from openerp.osv import osv, fields
from openerp import tools
class mail_followers(osv.Model):
@ -120,8 +120,8 @@ class mail_notification(osv.Model):
# mail_noemail (do not send email) or no partner_ids: do not send, return
if context.get('mail_noemail'):
return True
msg = self.pool.get('mail.message').browse(cr, uid, msg_id, context=context)
# browse as SUPERUSER_ID because of access to res_partner not necessarily allowed
msg = self.pool.get('mail.message').browse(cr, SUPERUSER_ID, msg_id, context=context)
notify_partner_ids = self.get_partners_to_notify(cr, uid, msg, context=context)
if not notify_partner_ids:
return True

View File

@ -33,7 +33,7 @@ class mail_group(osv.Model):
_name = 'mail.group'
_mail_flat_thread = False
_inherit = ['mail.thread']
_inherits = {'mail.alias': 'alias_id', 'ir.ui.menu': 'menu_id'}
_inherits = {'mail.alias': 'alias_id'}
def _get_image(self, cr, uid, ids, name, args, context=None):
result = dict.fromkeys(ids, False)
@ -45,6 +45,7 @@ class mail_group(osv.Model):
return self.write(cr, uid, [id], {'image': tools.image_resize_image_big(value)}, context=context)
_columns = {
'name': fields.char('Name', size=64, required=True, translate=True),
'description': fields.text('Description'),
'menu_id': fields.many2one('ir.ui.menu', string='Related Menu', required=True, ondelete="cascade"),
'public': fields.selection([('public', 'Public'), ('private', 'Private'), ('groups', 'Selected Group Only')], 'Privacy', required=True,
@ -88,16 +89,11 @@ class mail_group(osv.Model):
image_path = openerp.modules.get_module_resource('mail', 'static/src/img', 'groupdefault.png')
return tools.image_resize_image_big(open(image_path, 'rb').read().encode('base64'))
def _get_menu_parent(self, cr, uid, context=None):
ref = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'mail', 'mail_group_root')
return ref and ref[1] or False
_defaults = {
'public': 'groups',
'group_public_id': _get_default_employee_group,
'image': _get_default_image,
'parent_id': _get_menu_parent,
'alias_domain': False, # always hide alias during creation
'alias_domain': False, # always hide alias during creation
}
def _subscribe_users(self, cr, uid, ids, context=None):
@ -110,7 +106,7 @@ class mail_group(osv.Model):
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()
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.
@ -118,10 +114,18 @@ class mail_group(osv.Model):
model_name=self._name, context=context)
vals['alias_id'] = alias_id
#check access rights for the current user, then create as SUPERUSER because the object inherits
#ir.ui.menu (for which normal users do not have creation rights)
self.check_access_rights(cr, uid, 'create')
mail_group_id = super(mail_group, self).create(cr, SUPERUSER_ID, vals, context=context)
# get parent menu
menu_parent = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'mail', 'mail_group_root')
menu_parent = menu_parent and menu_parent[1] or False
# Create menu id
mobj = self.pool.get('ir.ui.menu')
menu_id = mobj.create(cr, SUPERUSER_ID, {'name': vals['name'], 'parent_id': menu_parent}, context=context)
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 client action for this group and link the menu to it
ref = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'mail', 'action_mail_group_feeds')
@ -137,9 +141,7 @@ class mail_group(osv.Model):
}
cobj = self.pool.get('ir.actions.client')
newref = cobj.copy(cr, SUPERUSER_ID, ref[1], default={'params': str(params), 'name': vals['name']}, context=context)
self.write(cr, SUPERUSER_ID, [mail_group_id], {'action': 'ir.actions.client,' + str(newref), 'mail_group_id': mail_group_id}, context=context)
mail_alias.write(cr, uid, [vals['alias_id']], {"alias_force_thread_id": mail_group_id}, context)
mobj.write(cr, SUPERUSER_ID, menu_id, { 'action': 'ir.actions.client,' + str(newref), 'mail_group_id': mail_group_id}, context=context)
if vals.get('group_ids'):
self._subscribe_users(cr, uid, [mail_group_id], context=context)
@ -150,11 +152,12 @@ class mail_group(osv.Model):
# Cascade-delete mail aliases as well, as they should not exist without the mail group.
mail_alias = self.pool.get('mail.alias')
alias_ids = [group.alias_id.id for group in groups if group.alias_id]
# Cascade-delete menu entries as well
self.pool.get('ir.ui.menu').unlink(cr, uid, [group.menu_id.id for group in groups if group.menu_id], context=context)
# Delete mail_group
res = super(mail_group, self).unlink(cr, uid, ids, context=context)
mail_alias.unlink(cr, uid, alias_ids, context=context)
# Delete alias
mail_alias.unlink(cr, SUPERUSER_ID, alias_ids, context=context)
# Cascade-delete menu entries as well
self.pool.get('ir.ui.menu').unlink(cr, SUPERUSER_ID, [group.menu_id.id for group in groups if group.menu_id], context=context)
return res
def write(self, cr, uid, ids, vals, context=None):
@ -164,10 +167,17 @@ class mail_group(osv.Model):
# if description is changed: update client action
if vals.get('description'):
cobj = self.pool.get('ir.actions.client')
for action in [group.action for group in self.browse(cr, SUPERUSER_ID, ids, context=context) if group.action]:
for action in [group.menu_id.action for group in self.browse(cr, uid, ids, context=context)]:
new_params = action.params
new_params['header_description'] = vals.get('description')
cobj.write(cr, SUPERUSER_ID, [action.id], {'params': str(new_params)}, context=context)
# if name is changed: update menu
if vals.get('name'):
mobj = self.pool.get('ir.ui.menu')
mobj.write(cr, SUPERUSER_ID,
[group.menu_id.id for group in self.browse(cr, uid, ids, context=context)],
{'name': vals.get('name')}, context=context)
return result
def action_follow(self, cr, uid, ids, context=None):

View File

@ -47,7 +47,8 @@ class ir_ui_menu(osv.osv):
for menu in self.browse(cr, uid, ids, context=context):
if menu.mail_group_id:
sub_ids = follower_obj.search(cr, SUPERUSER_ID, [
('partner_id', '=', partner_id), ('res_model', '=', 'mail.group'),
('partner_id', '=', partner_id),
('res_model', '=', 'mail.group'),
('res_id', '=', menu.mail_group_id.id)
], context=context)
if not sub_ids:

View File

@ -205,7 +205,7 @@ class mail_mail(osv.Model):
# specific behavior to customize the send email for notified partners
email_list = []
if recipient_ids:
for partner in self.pool.get('res.partner').browse(cr, uid, recipient_ids, context=context):
for partner in self.pool.get('res.partner').browse(cr, SUPERUSER_ID, recipient_ids, context=context):
email_list.append(self.send_get_email_dict(cr, uid, mail, partner=partner, context=context))
else:
email_list.append(self.send_get_email_dict(cr, uid, mail, context=context))

View File

@ -303,13 +303,11 @@ class mail_message(osv.Model):
partner_ids |= set([partner.id for partner in message.partner_ids])
if message.attachment_ids:
attachment_ids |= set([attachment.id for attachment in message.attachment_ids])
# Filter author_ids uid can see
# partner_ids = self.pool.get('res.partner').search(cr, uid, [('id', 'in', partner_ids)], context=context)
partners = res_partner_obj.name_get(cr, uid, list(partner_ids), context=context)
# Read partners as SUPERUSER -> display the names like classic m2o even if no access
partners = res_partner_obj.name_get(cr, SUPERUSER_ID, list(partner_ids), context=context)
partner_tree = dict((partner[0], partner) for partner in partners)
# 2. Attachments
# 2. Attachments as SUPERUSER, because could receive msg and attachments for doc uid cannot see
attachments = ir_attachment_obj.read(cr, SUPERUSER_ID, list(attachment_ids), ['id', 'datas_fname'], context=context)
attachments_tree = dict((attachment['id'], {'id': attachment['id'], 'filename': attachment['datas_fname']}) for attachment in attachments)
@ -617,54 +615,73 @@ class mail_message(osv.Model):
- uid have read access to the related document if model, res_id
- otherwise: raise
- create: if
- no model, no res_id, I create a private message
- no model, no res_id, I create a private message OR
- pid in message_follower_ids if model, res_id OR
- mail_notification (parent_id.id, pid) exists, uid has been notified of the parent, OR
- uid have write access on the related document if model, res_id, OR
- otherwise: raise
- write: if
- author_id == pid, uid is the author, OR
- uid has write access on the related document if model, res_id
- Otherwise: raise
- otherwise: raise
- unlink: if
- uid has write access on the related document if model, res_id
- Otherwise: raise
- otherwise: raise
"""
def _generate_model_record_ids(msg_val, msg_ids=[]):
""" :param model_record_ids: {'model': {'res_id': (msg_id, msg_id)}, ... }
:param message_values: {'msg_id': {'model': .., 'res_id': .., 'author_id': ..}}
"""
model_record_ids = {}
for id in msg_ids:
if msg_val[id]['model'] and msg_val[id]['res_id']:
model_record_ids.setdefault(msg_val[id]['model'], dict()).setdefault(msg_val[id]['res_id'], set()).add(msg_val[id]['res_id'])
return model_record_ids
if uid == SUPERUSER_ID:
return
if isinstance(ids, (int, long)):
ids = [ids]
not_obj = self.pool.get('mail.notification')
fol_obj = self.pool.get('mail.followers')
partner_id = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=None)['partner_id'][0]
# Read mail_message.ids to have their values
message_values = dict.fromkeys(ids)
model_record_ids = {}
cr.execute('SELECT DISTINCT id, model, res_id, author_id FROM "%s" WHERE id = ANY (%%s)' % self._table, (ids,))
for id, rmod, rid, author_id in cr.fetchall():
message_values[id] = {'res_model': rmod, 'res_id': rid, 'author_id': author_id}
if rmod:
model_record_ids.setdefault(rmod, dict()).setdefault(rid, set()).add(id)
cr.execute('SELECT DISTINCT id, model, res_id, author_id, parent_id FROM "%s" WHERE id = ANY (%%s)' % self._table, (ids,))
for id, rmod, rid, author_id, parent_id in cr.fetchall():
message_values[id] = {'model': rmod, 'res_id': rid, 'author_id': author_id, 'parent_id': parent_id}
# Author condition, for read and create (private message) -> could become an ir.rule, but not till we do not have a many2one variable field
if operation == 'read':
# Author condition (READ, WRITE, CREATE (private)) -> could become an ir.rule ?
author_ids = []
if operation == 'read' or operation == 'write':
author_ids = [mid for mid, message in message_values.iteritems()
if message.get('author_id') and message.get('author_id') == partner_id]
elif operation == 'create':
author_ids = [mid for mid, message in message_values.iteritems()
if not message.get('model') and not message.get('res_id')]
else:
author_ids = []
# Parent condition, for create (check for received notifications for the created message parent)
notified_ids = []
if operation == 'create':
parent_ids = [message.get('parent_id') for mid, message in message_values.iteritems()
if message.get('parent_id')]
not_ids = not_obj.search(cr, SUPERUSER_ID, [('message_id.id', 'in', parent_ids), ('partner_id', '=', partner_id)], context=context)
not_parent_ids = [notif.message_id.id for notif in not_obj.browse(cr, SUPERUSER_ID, not_ids, context=context)]
notified_ids += [mid for mid, message in message_values.iteritems()
if message.get('parent_id') in not_parent_ids]
# Notification condition, for read (check for received notifications and create (in message_follower_ids)) -> could become an ir.rule, but not till we do not have a many2one variable field
other_ids = set(ids).difference(set(author_ids), set(notified_ids))
model_record_ids = _generate_model_record_ids(message_values, other_ids)
if operation == 'read':
not_obj = self.pool.get('mail.notification')
not_ids = not_obj.search(cr, SUPERUSER_ID, [
('partner_id', '=', partner_id),
('message_id', 'in', ids),
], context=context)
notified_ids = [notification.message_id.id for notification in not_obj.browse(cr, SUPERUSER_ID, not_ids, context=context)]
elif operation == 'create':
notified_ids = []
for doc_model, doc_dict in model_record_ids.items():
fol_obj = self.pool.get('mail.followers')
fol_ids = fol_obj.search(cr, SUPERUSER_ID, [
('res_model', '=', doc_model),
('res_id', 'in', list(doc_dict.keys())),
@ -672,22 +689,15 @@ class mail_message(osv.Model):
], context=context)
fol_mids = [follower.res_id for follower in fol_obj.browse(cr, SUPERUSER_ID, fol_ids, context=context)]
notified_ids += [mid for mid, message in message_values.iteritems()
if message.get('res_model') == doc_model and message.get('res_id') in fol_mids]
else:
notified_ids = []
# Calculate remaining ids, and related model/res_ids
model_record_ids = {}
other_ids = set(ids).difference(set(author_ids), set(notified_ids))
for id in other_ids:
if message_values[id]['res_model']:
model_record_ids.setdefault(message_values[id]['res_model'], set()).add(message_values[id]['res_id'])
if message.get('model') == doc_model and message.get('res_id') in fol_mids]
# CRUD: Access rights related to the document
other_ids = other_ids.difference(set(notified_ids))
model_record_ids = _generate_model_record_ids(message_values, other_ids)
document_related_ids = []
for model, mids in model_record_ids.items():
for model, doc_dict in model_record_ids.items():
model_obj = self.pool.get(model)
mids = model_obj.exists(cr, uid, mids)
mids = model_obj.exists(cr, uid, doc_dict.keys())
if operation in ['create', 'write', 'unlink']:
model_obj.check_access_rights(cr, uid, 'write')
model_obj.check_access_rule(cr, uid, mids, 'write', context=context)
@ -695,10 +705,10 @@ class mail_message(osv.Model):
model_obj.check_access_rights(cr, uid, operation)
model_obj.check_access_rule(cr, uid, mids, operation, context=context)
document_related_ids += [mid for mid, message in message_values.iteritems()
if message.get('res_model') == model and message.get('res_id') in mids]
if message.get('model') == model and message.get('res_id') in mids]
# Calculate remaining ids: if not void, raise an error
other_ids = other_ids - set(document_related_ids)
other_ids = other_ids.difference(set(document_related_ids))
if not other_ids:
return
raise orm.except_orm(_('Access Denied'),
@ -714,7 +724,7 @@ class mail_message(osv.Model):
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)
self._notify(cr, uid, 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
@ -832,35 +842,49 @@ class mail_message(osv.Model):
""" Add the related record followers to the destination partner_ids if is not a private message.
Call mail_notification.notify to manage the email sending
"""
message = self.read(cr, uid, newid, ['model', 'res_id', 'author_id', 'subtype_id', 'partner_ids'], context=context)
notification_obj = self.pool.get('mail.notification')
message = self.browse(cr, uid, newid, context=context)
partners_to_notify = set([])
# message has no subtype_id: pure log message -> no partners, no one notified
if not message.get('subtype_id'):
if not message.subtype_id:
return True
# all partner_ids of the mail.message have to be notified
if message.get('partner_ids'):
partners_to_notify |= set(message.get('partner_ids'))
if message.partner_ids:
partners_to_notify |= set(message.partner_ids)
# all followers of the mail.message document have to be added as partners and notified
if message.get('model') and message.get('res_id'):
if message.model and message.res_id:
fol_obj = self.pool.get("mail.followers")
fol_ids = fol_obj.search(cr, uid, [
('res_model', '=', message.get('model')),
('res_id', '=', message.get('res_id')),
('subtype_ids', 'in', message.get('subtype_id')[0])
# browse as SUPERUSER because rules could restrict the search results
fol_ids = fol_obj.search(cr, SUPERUSER_ID, [
('res_model', '=', message.model),
('res_id', '=', message.res_id),
('subtype_ids', 'in', message.subtype_id.id)
], context=context)
fol_objs = fol_obj.read(cr, uid, fol_ids, ['partner_id'], context=context)
partners_to_notify |= set(fol['partner_id'][0] for fol in fol_objs)
partners_to_notify |= set(fo.partner_id for fo in fol_obj.browse(cr, SUPERUSER_ID, fol_ids, context=context))
# remove me from notified partners, unless the message is written on my own wall
if message.get('author_id') and message.get('model') == "res.partner" and message.get('res_id') == message.get('author_id')[0]:
partners_to_notify |= set([message.get('author_id')[0]])
elif message.get('author_id'):
partners_to_notify = partners_to_notify - set([message.get('author_id')[0]])
if message.author_id and message.model == "res.partner" and message.res_id == message.author_id.id:
partners_to_notify |= set([message.author_id])
elif message.author_id:
partners_to_notify = partners_to_notify - set([message.author_id])
# notify
if partners_to_notify:
self.write(cr, SUPERUSER_ID, [newid], {'notified_partner_ids': [(4, p_id) for p_id in partners_to_notify]}, context=context)
self.write(cr, SUPERUSER_ID, [newid], {'notified_partner_ids': [(4, p.id) for p in partners_to_notify]}, context=context)
notification_obj._notify(cr, uid, newid, context=context)
message.refresh()
self.pool.get('mail.notification')._notify(cr, uid, newid, context=context)
# An error appear when a user receive a notification without notifying
# the parent message -> add a read notification for the parent
if message.parent_id:
# all notified_partner_ids of the mail.message have to be notified for the parented messages
partners_to_parent_notify = set(message.notified_partner_ids).difference(message.parent_id.notified_partner_ids)
for partner in partners_to_parent_notify:
notification_obj.create(cr, uid, {
'message_id': message.parent_id.id,
'partner_id': partner.id,
'read': True,
}, context=context)
#------------------------------------------------------
# Tools

View File

@ -213,8 +213,11 @@ class mail_thread(osv.AbstractModel):
def create(self, cr, uid, vals, context=None):
""" Override to subscribe the current user. """
if context is None:
context = {}
thread_id = super(mail_thread, self).create(cr, uid, vals, context=context)
self.message_subscribe_users(cr, uid, [thread_id], [uid], context=context)
if not context.get('mail_nosubscribe'):
self.message_subscribe_users(cr, uid, [thread_id], [uid], context=context)
return thread_id
def unlink(self, cr, uid, ids, context=None):
@ -225,10 +228,12 @@ class mail_thread(osv.AbstractModel):
# delete messages and notifications
msg_ids = msg_obj.search(cr, uid, [('model', '=', self._name), ('res_id', 'in', ids)], context=context)
msg_obj.unlink(cr, uid, msg_ids, context=context)
# delete
res = super(mail_thread, self).unlink(cr, uid, ids, context=context)
# delete followers
fol_ids = fol_obj.search(cr, uid, [('res_model', '=', self._name), ('res_id', 'in', ids)], context=context)
fol_obj.unlink(cr, uid, fol_ids, context=context)
return super(mail_thread, self).unlink(cr, uid, ids, context=context)
fol_ids = fol_obj.search(cr, SUPERUSER_ID, [('res_model', '=', self._name), ('res_id', 'in', ids)], context=context)
fol_obj.unlink(cr, SUPERUSER_ID, fol_ids, context=context)
return res
def copy(self, cr, uid, id, default=None, context=None):
default = default or {}
@ -794,7 +799,7 @@ class mail_thread(osv.AbstractModel):
# if subtypes are not specified (and not set to a void list), fetch default ones
if subtype_ids is None:
subtype_obj = self.pool.get('mail.message.subtype')
subtype_ids = subtype_obj.search(cr, SUPERUSER_ID, [('default', '=', True), '|', ('res_model', '=', self._name), ('res_model', '=', False)], context=context)
subtype_ids = subtype_obj.search(cr, uid, [('default', '=', True), '|', ('res_model', '=', self._name), ('res_model', '=', False)], context=context)
# update the subscriptions
fol_obj = self.pool.get('mail.followers')
fol_ids = fol_obj.search(cr, SUPERUSER_ID, [('res_model', '=', self._name), ('res_id', 'in', ids), ('partner_id', 'in', partner_ids)], context=context)

View File

@ -1,39 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2012-Today OpenERP SA (<http://www.openerp.com>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
#
##############################################################################
from osv import osv, fields
class mail_vote(osv.Model):
''' Mail vote feature allow users to like and unlike messages attached
to a document. This allows for example to build a ranking-based
displaying of messages, for FAQ. '''
_name = 'mail.vote'
_description = 'Mail Vote'
_columns = {
'message_id': fields.many2one('mail.message', 'Message', select=1,
ondelete='cascade', required=True),
'user_id': fields.many2one('res.users', 'User', select=1,
ondelete='cascade', required=True),
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -34,24 +34,27 @@ class res_users(osv.Model):
_inherits = {'mail.alias': 'alias_id'}
_columns = {
'alias_id': fields.many2one('mail.alias', 'Alias', ondelete="cascade", required=True,
'alias_id': fields.many2one('mail.alias', 'Alias', ondelete="cascade", required=True,
help="Email address internally associated with this user. Incoming "\
"emails will appear in the user's notifications."),
}
_defaults = {
'alias_domain': False, # always hide alias during creation
'alias_domain': False, # always hide alias during creation
}
def __init__(self, pool, cr):
""" Override of __init__ to add access rights on notification_email_send
field. Access rights are disabled by default, but allowed on
fields defined in self.SELF_WRITEABLE_FIELDS.
and alias fields. Access rights are disabled by default, but allowed
on some specific fields defined in self.SELF_{READ/WRITE}ABLE_FIELDS.
"""
init_res = super(res_users, self).__init__(pool, cr)
# duplicate list to avoid modifying the original reference
self.SELF_WRITEABLE_FIELDS = list(self.SELF_WRITEABLE_FIELDS)
self.SELF_WRITEABLE_FIELDS.append('notification_email_send')
# duplicate list to avoid modifying the original reference
self.SELF_READABLE_FIELDS = list(self.SELF_READABLE_FIELDS)
self.SELF_READABLE_FIELDS.extend(['notification_email_send', 'alias_domain', 'alias_name'])
return init_res
def _auto_init(self, cr, context=None):
@ -124,7 +127,7 @@ class res_users(osv.Model):
context['thread_model'] = 'res.partner'
if isinstance(thread_id, (list, tuple)):
thread_id = thread_id[0]
partner_id = self.pool.get('res.users').read(cr, uid, thread_id, ['partner_id'], context=context)['partner_id'][0]
partner_id = self.browse(cr, uid, thread_id).partner_id.id
return self.pool.get('res.partner').message_post_user_api(cr, uid, partner_id, body=body, subject=subject,
parent_id=parent_id, attachment_ids=attachment_ids, context=context, content_subtype=content_subtype, **kwargs)
@ -138,11 +141,11 @@ class res_users(osv.Model):
context['thread_model'] = 'res.partner'
if isinstance(thread_id, (list, tuple)):
thread_id = thread_id[0]
partner_id = self.pool.get('res.users').read(cr, uid, thread_id, ['partner_id'], context=context)['partner_id'][0]
partner_id = self.browse(cr, uid, thread_id).partner_id.id
return self.pool.get('res.partner').message_post(cr, uid, partner_id, context=context, **kwargs)
def message_update(self, cr, uid, ids, msg_dict, update_vals=None, context=None):
partner_id = self.pool.get('res.users').browse(cr, uid, ids)[0].partner_id.id
partner_id = self.browse(cr, uid, ids)[0].partner_id.id
return self.pool.get('res.partner').message_update(cr, uid, [partner_id], msg_dict,
update_vals=update_vals, context=context)

View File

@ -1,13 +1,14 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_mail_message_all,mail.message.all,model_mail_message,,1,0,1,0
access_mail_message_group_user,mail.message.group.user,model_mail_message,base.group_user,1,1,1,1
access_mail_mail_all,mail.mail.all,model_mail_mail,,0,0,1,0
access_mail_mail_user,mail.mail,model_mail_mail,base.group_user,1,1,1,0
access_mail_message_all,mail.message.all,model_mail_message,,1,0,0,0
access_mail_message_user,mail.message.user,model_mail_message,base.group_user,1,1,1,1
access_mail_mail_all,mail.mail.all,model_mail_mail,,1,0,0,0
access_mail_mail_user,mail.mail.user,model_mail_mail,base.group_user,1,1,1,0
access_mail_mail_system,mail.mail.system,model_mail_mail,base.group_system,1,1,1,1
access_mail_followers_all,mail.followers.all,model_mail_followers,,1,0,0,0
access_mail_followers_user,mail.followers.user,model_mail_followers,base.group_user,1,1,0,0
access_mail_followers_system,mail.followers.system,model_mail_followers,base.group_system,1,1,1,1
access_mail_notification_all,mail.notification.all,model_mail_notification,,1,0,0,0
access_mail_notification_group_user,mail.notification.user,model_mail_notification,base.group_user,1,1,1,0
access_mail_notification_user,mail.notification.user,model_mail_notification,base.group_user,1,1,1,0
access_mail_notification_system,mail.notification.system,model_mail_notification,base.group_system,1,1,1,1
access_mail_group_all,mail.group.all,model_mail_group,,1,0,0,0
access_mail_group_user,mail.group.user,model_mail_group,base.group_user,1,1,1,1
@ -15,7 +16,6 @@ access_mail_alias_all,mail.alias.all,model_mail_alias,,1,0,0,0
access_mail_alias_user,mail.alias.user,model_mail_alias,base.group_user,1,1,1,0
access_mail_alias_system,mail.alias.system,model_mail_alias,base.group_system,1,1,1,1
access_mail_message_subtype_all,mail.message.subtype.all,model_mail_message_subtype,,1,0,0,0
access_mail_vote_all,mail.vote.all,model_mail_vote,,1,1,1,1
access_mail_favorite_all,mail.favorite.all,model_mail_favorite,,1,1,1,1
access_mail_message_subtype_system,mail.message.subtype.system,model_mail_message_subtype,base.group_system,1,1,1,1
access_mail_thread_all,mail.thread.all,model_mail_thread,,1,1,1,1
access_publisher_warranty_contract_all,publisher.warranty.contract.all,model_publisher_warranty_contract,,1,1,1,1

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_mail_message_all mail.message.all model_mail_message 1 0 1 0 0
3 access_mail_message_group_user access_mail_message_user mail.message.group.user mail.message.user model_mail_message base.group_user 1 1 1 1
4 access_mail_mail_all mail.mail.all model_mail_mail 0 1 0 1 0 0
5 access_mail_mail_user mail.mail mail.mail.user model_mail_mail base.group_user 1 1 1 0
6 access_mail_mail_system mail.mail.system model_mail_mail base.group_system 1 1 1 1
7 access_mail_followers_all mail.followers.all model_mail_followers 1 0 0 0
8 access_mail_followers_user mail.followers.user model_mail_followers base.group_user 1 1 0 0
9 access_mail_followers_system mail.followers.system model_mail_followers base.group_system 1 1 1 1
10 access_mail_notification_all mail.notification.all model_mail_notification 1 0 0 0
11 access_mail_notification_group_user access_mail_notification_user mail.notification.user model_mail_notification base.group_user 1 1 1 0
12 access_mail_notification_system mail.notification.system model_mail_notification base.group_system 1 1 1 1
13 access_mail_group_all mail.group.all model_mail_group 1 0 0 0
14 access_mail_group_user mail.group.user model_mail_group base.group_user 1 1 1 1
16 access_mail_alias_user mail.alias.user model_mail_alias base.group_user 1 1 1 0
17 access_mail_alias_system mail.alias.system model_mail_alias base.group_system 1 1 1 1
18 access_mail_message_subtype_all mail.message.subtype.all model_mail_message_subtype 1 0 0 0
19 access_mail_vote_all access_mail_message_subtype_system mail.vote.all mail.message.subtype.system model_mail_vote model_mail_message_subtype base.group_system 1 1 1 1
access_mail_favorite_all mail.favorite.all model_mail_favorite 1 1 1 1
20 access_mail_thread_all mail.thread.all model_mail_thread 1 1 1 1
21 access_publisher_warranty_contract_all publisher.warranty.contract.all model_publisher_warranty_contract 1 1 1 1

View File

@ -443,7 +443,7 @@
filter:none;
cursor: pointer;
}
.openerp .oe_mail .oe_mail_list_recipients{
.openerp .oe_mail .oe_msg_content .oe_mail_list_recipients{
font-size: 12px;
margin-top: 4px;
margin-bottom: 4px;

View File

@ -1411,10 +1411,9 @@ openerp.mail = function (session) {
* when the user clic on this compact mode, the composer is open
*... @param {Array} [message_ids] List of ids to fetch by the root thread.
* When you use this option, the domain is not used for the fetch root.
*... @param {String} [help] Message to display when there are no message.
*... @param {String} [compose_placeholder] Message to display on the textareaboxes.
*... @param {Boolean} [show_link_partner] Display partner (authors, followers...) on link or not
*... @param {Boolean} [compose_as_todo] The root composer mark automatically the message as todo
* @param {String} [no_message] Message to display when there are no message
* @param {Boolean} [show_link] Display partner (authors, followers...) on link or not
* @param {Boolean} [compose_as_todo] The root composer mark automatically the message as todo
*/
init: function (parent, action) {
this._super(parent, action);
@ -1432,7 +1431,7 @@ openerp.mail = function (session) {
'show_compose_message' : false,
'show_compact_message' : false,
'compose_placeholder': false,
'show_link_partner': true,
'show_link': true,
'view_inbox': false,
'message_ids': undefined,
'compose_as_todo' : false,

View File

@ -117,8 +117,8 @@
<t t-set="inc" t-value="0"/>
<t t-if="widget.partner_ids.length" t-foreach="widget.partner_ids" t-as="partner">
<span t-attf-class="oe_partner_follower #{inc>=3?'oe_hidden':''}"><t t-if="inc" t-raw="', '"/>
<a t-if="widget.options.show_link_partner" t-attf-href="#model=res.partner&amp;id=#{partner[0]}"><t t-raw="partner[1]"/></a>
<t t-if="!widget.options.show_link_partner" t-raw="partner[1]"/>
<a t-if="widget.options.show_link" t-attf-href="#model=res.partner&amp;id=#{partner[0]}"><t t-raw="partner[1]"/></a>
<t t-if="!widget.options.show_link" t-raw="partner[1]"/>
</span><t t-set="inc" t-value="inc+1"/>
</t>
<t t-if="widget.partner_ids.length>=3">
@ -204,10 +204,10 @@
<div t-attf-class="oe_msg #{widget.thread_level and widget.options.display_indented_thread > -1 ? 'oe_msg_indented' : ''} oe_msg_#{widget.type}">
<div class='oe_msg_left'>
<a t-if="widget.options.show_link_partner" t-attf-href="#model=res.partner&amp;id=#{widget.author_id[0]}" t-att-title="widget.author_id[1]">
<a t-if="widget.options.show_link" t-attf-href="#model=res.partner&amp;id=#{widget.author_id[0]}" t-att-title="widget.author_id[1]">
<img class="oe_msg_icon" t-att-src="widget.avatar"/>
</a>
<img t-if="!widget.options.show_link_partner" class="oe_msg_icon" t-att-src="widget.avatar"/>
<img t-if="!widget.options.show_link" class="oe_msg_icon" t-att-src="widget.avatar"/>
</div>
<div class="oe_msg_center">
@ -221,7 +221,7 @@
<!-- message itself -->
<div class="oe_msg_content">
<h1 t-if="(widget.show_record_name or widget.subject) and !widget.thread_level" class="oe_msg_title">
<a t-if="widget.show_record_name" class="oe_mail_action_model" t-attf-href="#model=#{widget.model}&amp;id=#{widget.res_id}"><t t-raw="widget.record_name"/></a>
<a t-if="widget.options.show_link and widget.show_record_name" class="oe_mail_action_model" t-attf-href="#model=#{widget.model}&amp;id=#{widget.res_id}"><t t-raw="widget.record_name"/></a><span t-if="!widget.options.show_link and widget.show_record_name"><t t-raw="widget.record_name"/></span><t t-if="widget.show_record_name">: </t>
<t t-if="widget.subject" t-raw="widget.subject"/>
</h1>
<div class="oe_msg_body">
@ -232,8 +232,8 @@
<t t-if="widget.attachment_ids.length > 0">
<div class="oe_msg_attachment_list"></div>
</t>
<a t-if="widget.author_id and widget.options.show_link_partner" t-attf-href="#model=res.partner&amp;id=#{widget.author_id[0]}"><t t-raw="widget.author_id[1]"/></a>
<span t-if="widget.author_id and !widget.options.show_link_partner"><t t-raw="widget.author_id[1]"/></span>
<a t-if="widget.author_id and widget.options.show_link" t-attf-href="#model=res.partner&amp;id=#{widget.author_id[0]}"><t t-raw="widget.author_id[1]"/></a>
<span t-if="widget.author_id and !widget.options.show_link"><t t-raw="widget.author_id[1]"/></span>
<span class='oe_subtle'></span>
<span t-att-title="widget.date"><t t-raw="widget.timerelative"/></span>
<span class='oe_subtle'></span>

View File

@ -19,11 +19,13 @@
#
##############################################################################
from . import test_mail, test_mail_access_rights
from . import test_mail_message, test_mail_features, test_message_read, test_invite
checks = [
test_mail,
test_mail_access_rights,
test_mail_message,
test_mail_features,
test_message_read,
test_invite,
]
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -0,0 +1,48 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Business Applications
# Copyright (c) 2012-TODAY OpenERP S.A. <http://openerp.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.addons.mail.tests.test_mail_base import TestMailBase
class test_invite(TestMailBase):
def test_00_basic_invite(self):
cr, uid = self.cr, self.uid
mail_invite = self.registry('mail.wizard.invite')
# Do: create a mail_wizard_invite, validate it
self._init_mock_build_email()
context = {'default_res_model': 'mail.group', 'default_res_id': self.group_pigs_id}
mail_invite_id = mail_invite.create(cr, self.user_raoul_id, {'partner_ids': [(4, self.partner_bert_id)]}, context)
mail_invite.add_followers(cr, self.user_raoul_id, [mail_invite_id], {'default_model': 'mail.group', 'default_res_id': 0})
# Test: Pigs followers should contain Admin, Bert
self.group_pigs.refresh()
follower_ids = [follower.id for follower in self.group_pigs.message_follower_ids]
self.assertEqual(set(follower_ids), set([self.partner_admin_id, self.partner_bert_id]), 'Pigs followers after invite is incorrect')
# Test: (pretend to) send email and check subject, body
self.assertEqual(len(self._build_email_kwargs_list), 1, 'sent email number incorrect, should be only for Bert')
for sent_email in self._build_email_kwargs_list:
self.assertEqual(sent_email.get('subject'), 'Invitation to follow Pigs',
'subject of invitation email is incorrect')
self.assertTrue('You have been invited to follow Pigs' in sent_email.get('body'),
'body of invitation email is incorrect')

View File

@ -1,213 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Business Applications
# Copyright (c) 2012-TODAY OpenERP S.A. <http://openerp.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.addons.mail.tests import test_mail_mockup
from openerp.osv.orm import except_orm
from openerp.tools import mute_logger
class test_mail_access_rights(test_mail_mockup.TestMailMockups):
def setUp(self):
super(test_mail_access_rights, self).setUp()
cr, uid = self.cr, self.uid
self.mail_group = self.registry('mail.group')
self.mail_message = self.registry('mail.message')
self.attachment = self.registry('ir.attachment')
self.mail_notification = self.registry('mail.notification')
self.res_users = self.registry('res.users')
self.res_groups = self.registry('res.groups')
self.res_partner = self.registry('res.partner')
# 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 !'})
# Find Employee group
group_employee_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'base', 'group_user')
self.group_employee_id = group_employee_ref and group_employee_ref[1] or False
# Create Bert (without groups) and Raoul( employee)
self.user_bert_id = self.res_users.create(cr, uid, {'name': 'Bert Tartopoils', 'login': 'bert', 'groups_id': [(6, 0, [])]})
self.user_raoul_id = self.res_users.create(cr, uid, {'name': 'Raoul Grosbedon', 'login': 'raoul', 'groups_id': [(6, 0, [self.group_employee_id])]})
self.user_bert = self.res_users.browse(cr, uid, self.user_bert_id)
self.partner_bert_id = self.user_bert.partner_id.id
self.user_raoul = self.res_users.browse(cr, uid, self.user_raoul_id)
self.partner_raoul_id = self.user_raoul.partner_id.id
@mute_logger('openerp.addons.base.ir.ir_model','openerp.osv.orm')
def test_00_mail_message_search_access_rights(self):
""" Test mail_message search override about access rights. """
cr, uid, group_pigs_id = self.cr, self.uid, self.group_pigs_id
partner_bert_id, partner_raoul_id = self.partner_bert_id, self.partner_raoul_id
user_bert_id, user_raoul_id = self.user_bert_id, self.user_raoul_id
# Data: comment subtype for mail.message creation
ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'mail', 'mt_comment')
subtype_id = ref and ref[1] or False
# Data: Birds group, private
group_birds_id = self.mail_group.create(self.cr, self.uid, {'name': 'Birds', 'public': 'private'})
# Data: raoul is member of Pigs
self.mail_group.message_subscribe(cr, uid, [group_pigs_id], [partner_raoul_id])
# Data: various author_ids, partner_ids, documents
msg_id1 = self.mail_message.create(cr, uid, {'subject': '_Test', 'body': 'A', 'subtype_id': subtype_id})
msg_id2 = self.mail_message.create(cr, uid, {'subject': '_Test', 'body': 'A+B', 'partner_ids': [(6, 0, [partner_bert_id])], 'subtype_id': subtype_id})
msg_id3 = self.mail_message.create(cr, uid, {'subject': '_Test', 'body': 'A Pigs', 'model': 'mail.group', 'res_id': group_pigs_id, 'subtype_id': subtype_id})
msg_id4 = self.mail_message.create(cr, uid, {'subject': '_Test', 'body': 'A+B Pigs', 'model': 'mail.group', 'res_id': group_pigs_id, 'partner_ids': [(6, 0, [partner_bert_id])], 'subtype_id': subtype_id})
msg_id5 = self.mail_message.create(cr, uid, {'subject': '_Test', 'body': 'A+R Pigs', 'model': 'mail.group', 'res_id': group_pigs_id, 'partner_ids': [(6, 0, [partner_raoul_id])], 'subtype_id': subtype_id})
msg_id6 = self.mail_message.create(cr, uid, {'subject': '_Test', 'body': 'A Birds', 'model': 'mail.group', 'res_id': group_birds_id, 'subtype_id': subtype_id})
msg_id7 = self.mail_message.create(cr, user_bert_id, {'subject': '_Test', 'body': 'B', 'subtype_id': subtype_id})
msg_id8 = self.mail_message.create(cr, user_bert_id, {'subject': '_Test', 'body': 'B+R', 'partner_ids': [(6, 0, [partner_raoul_id])], 'subtype_id': subtype_id})
# Test: Bert: 2 messages that have Bert in partner_ids + 2 messages as author
msg_ids = self.mail_message.search(cr, user_bert_id, [('subject', 'like', '_Test')])
self.assertEqual(set([msg_id2, msg_id4, msg_id7, msg_id8]), set(msg_ids), 'mail_message search failed')
# Test: Raoul: 3 messages on Pigs Raoul can read (employee can read group with default values), 0 on Birds (private group)
msg_ids = self.mail_message.search(cr, user_raoul_id, [('subject', 'like', '_Test'), ('body', 'like', 'A')])
self.assertEqual(set([msg_id3, msg_id4, msg_id5]), set(msg_ids), 'mail_message search failed')
# Test: Admin: all messages
msg_ids = self.mail_message.search(cr, uid, [('subject', 'like', '_Test')])
self.assertEqual(set([msg_id1, msg_id2, msg_id3, msg_id4, msg_id5, msg_id6, msg_id7, msg_id8]), set(msg_ids), 'mail_message search failed')
@mute_logger('openerp.addons.base.ir.ir_model','openerp.osv.orm')
def test_05_mail_message_read_access_rights(self):
""" Test basic mail_message read access rights. """
cr, uid = self.cr, self.uid
partner_bert_id, partner_raoul_id = self.partner_bert_id, self.partner_raoul_id
user_bert_id, user_raoul_id = self.user_bert_id, self.user_raoul_id
# Prepare groups: Pigs (employee), Jobs (public)
self.mail_group.message_post(cr, uid, self.group_pigs_id, body='Message')
self.group_jobs_id = self.mail_group.create(cr, uid, {'name': 'Jobs', 'public': 'public'})
# prepare an attachment
attachment_id = self.attachment.create(cr, uid, {'datas': 'My attachment'.encode('base64'), 'name': 'doc.txt', 'datas_fname': 'doc.txt' })
# ----------------------------------------
# CASE1: Bert, basic mail.message read access
# ----------------------------------------
# Do: create a new mail.message
message_id = self.mail_message.create(cr, uid, {'body': 'My Body', 'attachment_ids': [4, attachment_id] })
# Test: Bert reads the message, crash because not notification/not in doc followers/not read on doc
self.assertRaises(except_orm, self.mail_message.read,
cr, user_bert_id, message_id)
# Do: message is pushed to Bert
notif_id = self.mail_notification.create(cr, uid, {'message_id': message_id, 'partner_id': partner_bert_id})
# Test: Bert reads the message, ok because notification pushed
self.mail_message.read(cr, user_bert_id, message_id)
# Test: Bert download attachment, ok because he can read message
self.mail_message.download_attachment(cr, user_bert_id, message_id, attachment_id)
# Do: remove notification
self.mail_notification.unlink(cr, uid, notif_id)
# Test: Bert reads the message, crash because not notification/not in doc followers/not read on doc
self.assertRaises(except_orm, self.mail_message.read,
cr, self.user_bert_id, message_id)
# Test: Bert download attachment, crash because he can't read message
self.assertRaises(except_orm, self.mail_message.download_attachment,
cr, user_bert_id, message_id, attachment_id)
# Do: Bert is now the author
self.mail_message.write(cr, uid, [message_id], {'author_id': partner_bert_id})
# Test: Bert reads the message, ok because Bert is the author
self.mail_message.read(cr, user_bert_id, message_id)
# Do: Bert is not the author anymore
self.mail_message.write(cr, uid, [message_id], {'author_id': partner_raoul_id})
# Test: Bert reads the message, crash because not notification/not in doc followers/not read on doc
self.assertRaises(except_orm, self.mail_message.read,
cr, user_bert_id, message_id)
# Do: message is attached to a document Bert can read, Jobs
self.mail_message.write(cr, uid, [message_id], {'model': 'mail.group', 'res_id': self.group_jobs_id})
# Test: Bert reads the message, ok because linked to a doc he is allowed to read
self.mail_message.read(cr, user_bert_id, message_id)
# Do: message is attached to a document Bert cannot read, Pigs
self.mail_message.write(cr, uid, [message_id], {'model': 'mail.group', 'res_id': self.group_pigs_id})
# Test: Bert reads the message, crash because not notification/not in doc followers/not read on doc
self.assertRaises(except_orm, self.mail_message.read,
cr, user_bert_id, message_id)
@mute_logger('openerp.addons.base.ir.ir_model','openerp.osv.orm')
def test_10_mail_flow_access_rights(self):
""" Test a Chatter-looks alike flow. """
cr, uid = self.cr, self.uid
mail_compose = self.registry('mail.compose.message')
partner_bert_id, partner_raoul_id = self.partner_bert_id, self.partner_raoul_id
user_bert_id, user_raoul_id = self.user_bert_id, self.user_raoul_id
# Prepare groups: Pigs (employee), Jobs (public)
self.mail_group.message_post(cr, uid, self.group_pigs_id, body='Message')
self.group_jobs_id = self.mail_group.create(cr, uid, {'name': 'Jobs', 'public': 'public'})
# ----------------------------------------
# CASE1: Bert, without groups
# ----------------------------------------
# Do: Bert creates a group, should crash because perm_create only for employees
self.assertRaises(except_orm,
self.mail_group.create,
cr, user_bert_id, {'name': 'Bert\'s Group'})
# Do: Bert reads Jobs basic fields, ok because public = read access on the group
self.mail_group.read(cr, user_bert_id, self.group_jobs_id, ['name', 'description'])
# Do: Bert browse Pigs, ok (no direct browse of partners)
self.mail_group.browse(cr, user_bert_id, self.group_jobs_id)
# Do: Bert reads Jobs messages, ok because read access on the group => read access on its messages
jobs_message_ids = self.mail_group.read(cr, user_bert_id, self.group_jobs_id, ['message_ids'])['message_ids']
self.mail_message.read(cr, user_bert_id, jobs_message_ids)
# Do: Bert reads Jobs followers, ko because partner are accessible to employees or partner manager
jobs_followers_ids = self.mail_group.read(cr, user_bert_id, self.group_jobs_id, ['message_follower_ids'])['message_follower_ids']
self.assertRaises(except_orm,
self.res_partner.read,
cr, user_bert_id, jobs_followers_ids)
# Do: Bert comments Jobs, ko because no write access on the group and not in the followers
self.assertRaises(except_orm,
self.mail_group.message_post,
cr, user_bert_id, self.group_jobs_id, body='I love Pigs')
# Do: add Bert to jobs followers
self.mail_group.message_subscribe(cr, uid, [self.group_jobs_id], [partner_bert_id])
# Do: Bert comments Jobs, ok because he is now in the followers
self.mail_group.message_post(cr, user_bert_id, self.group_jobs_id, body='I love Pigs')
# Do: Bert reads Pigs, should crash because mail.group security=groups only for employee group
self.assertRaises(except_orm,
self.mail_group.read,
cr, user_bert_id, self.group_pigs_id)
# Do: Bert create a mail.compose.message record, because he uses the wizard
compose_id = mail_compose.create(cr, user_bert_id,
{'subject': 'Subject', 'body': 'Body text', 'partner_ids': []},
# {'subject': 'Subject', 'body_text': 'Body text', 'partner_ids': [(4, p_c_id), (4, p_d_id)]},
{'default_composition_mode': 'comment', 'default_model': 'mail.group', 'default_res_id': self.group_jobs_id})
mail_compose.send_mail(cr, user_bert_id, [compose_id])
self.user_demo_id = self.registry('ir.model.data').get_object_reference(self.cr, self.uid, 'base', 'user_demo')[1]
compose_id = mail_compose.create(cr, self.user_demo_id,
{'subject': 'Subject', 'body': 'Body text', 'partner_ids': []},
# {'subject': 'Subject', 'body_text': 'Body text', 'partner_ids': [(4, p_c_id), (4, p_d_id)]},
{'default_composition_mode': 'comment', 'default_model': 'mail.group', 'default_res_id': self.group_jobs_id})
mail_compose.send_mail(cr, self.user_demo_id, [compose_id])
# ----------------------------------------
# CASE2: Raoul, employee
# ----------------------------------------
# Do: Bert read Pigs, ok because public
self.mail_group.read(cr, user_raoul_id, self.group_pigs_id)
# Do: Bert read Jobs, ok because group_public_id = employee
self.mail_group.read(cr, user_raoul_id, self.group_jobs_id)

View File

@ -0,0 +1,91 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Business Applications
# Copyright (c) 2012-TODAY OpenERP S.A. <http://openerp.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.tests import common
class TestMailBase(common.TransactionCase):
def _mock_smtp_gateway(self, *args, **kwargs):
return True
def _init_mock_build_email(self):
self._build_email_args_list = []
self._build_email_kwargs_list = []
def _mock_build_email(self, *args, **kwargs):
""" Mock build_email to be able to test its values. Store them into
some internal variable for latter processing. """
self._build_email_args_list.append(args)
self._build_email_kwargs_list.append(kwargs)
return self._build_email(*args, **kwargs)
def setUp(self):
super(TestMailBase, self).setUp()
cr, uid = self.cr, self.uid
# Install mock SMTP gateway
self._init_mock_build_email()
self._build_email = self.registry('ir.mail_server').build_email
self.registry('ir.mail_server').build_email = self._mock_build_email
self._send_email = self.registry('ir.mail_server').send_email
self.registry('ir.mail_server').send_email = self._mock_smtp_gateway
# Usefull models
self.ir_model = self.registry('ir.model')
self.ir_attachment = self.registry('ir.attachment')
self.mail_alias = self.registry('mail.alias')
self.mail_thread = self.registry('mail.thread')
self.mail_group = self.registry('mail.group')
self.mail_mail = self.registry('mail.mail')
self.mail_message = self.registry('mail.message')
self.mail_notification = self.registry('mail.notification')
self.mail_followers = self.registry('mail.followers')
self.mail_message_subtype = self.registry('mail.message.subtype')
self.res_users = self.registry('res.users')
self.res_partner = self.registry('res.partner')
# Find Employee group
group_employee_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'base', 'group_user')
self.group_employee_id = group_employee_ref and group_employee_ref[1] or False
# Test users to use through the various tests
self.user_raoul_id = self.res_users.create(cr, uid,
{'name': 'Raoul Grosbedon', 'signature': 'Raoul', 'email': 'raoul@raoul.fr', 'login': 'raoul', 'groups_id': [(6, 0, [self.group_employee_id])]})
self.user_bert_id = self.res_users.create(cr, uid,
{'name': 'Bert Tartignole', 'signature': 'Bert', 'email': 'bert@bert.fr', 'login': '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)
self.partner_admin_id = self.user_admin.partner_id.id
self.partner_raoul_id = self.user_raoul.partner_id.id
self.partner_bert_id = self.user_bert.partner_id.id
# Test 'pigs' group to use through the various tests
self.group_pigs_id = self.mail_group.create(cr, uid,
{'name': 'Pigs', 'description': 'Fans of Pigs, unite !'})
self.group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
def tearDown(self):
# Remove mocks
self.registry('ir.mail_server').build_email = self._build_email
self.registry('ir.mail_server').send_email = self._send_email
super(TestMailBase, self).tearDown()

View File

@ -19,10 +19,8 @@
#
##############################################################################
import tools
from openerp.addons.mail.tests import test_mail_mockup
from openerp.tools.mail import html_sanitize
from openerp.addons.mail.tests.test_mail_base import TestMailBase
from openerp.tools.mail import html_sanitize, append_content_to_html
MAIL_TEMPLATE = """Return-Path: <whatever-2a840@postmaster.twitter.com>
To: {to}
@ -84,50 +82,20 @@ Sylvie
"""
class test_mail(test_mail_mockup.TestMailMockups):
class test_mail(TestMailBase):
def _mock_send_get_mail_body(self, *args, **kwargs):
# def _send_get_mail_body(self, cr, uid, mail, partner=None, context=None)
body = tools.append_content_to_html(args[2].body_html, kwargs.get('partner').name if kwargs.get('partner') else 'No specific partner', plaintext=False)
body = append_content_to_html(args[2].body_html, kwargs.get('partner').name if kwargs.get('partner') else 'No specific partner', plaintext=False)
return body
def setUp(self):
super(test_mail, self).setUp()
cr, uid = self.cr, self.uid
self.ir_model = self.registry('ir.model')
self.mail_alias = self.registry('mail.alias')
self.mail_thread = self.registry('mail.thread')
self.mail_group = self.registry('mail.group')
self.mail_mail = self.registry('mail.mail')
self.mail_message = self.registry('mail.message')
self.mail_notification = self.registry('mail.notification')
self.mail_followers = self.registry('mail.followers')
self.mail_message_subtype = self.registry('mail.message.subtype')
self.res_users = self.registry('res.users')
self.res_partner = self.registry('res.partner')
# Find Employee group
group_employee_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'base', 'group_user')
group_employee_id = group_employee_ref and group_employee_ref[1] or False
# Test users
self.user_raoul_id = self.res_users.create(cr, uid,
{'name': 'Raoul Grosbedon', 'email': 'raoul@raoul.fr', 'login': 'raoul', 'groups_id': [(6, 0, [group_employee_id])]})
self.user_raoul = self.res_users.browse(cr, uid, self.user_raoul_id)
self.user_admin = self.res_users.browse(cr, uid, uid)
# Mock send_get_mail_body to test its functionality without other addons override
self._send_get_mail_body = self.registry('mail.mail').send_get_mail_body
self.registry('mail.mail').send_get_mail_body = self._mock_send_get_mail_body
# groups@.. will cause the creation of new mail groups
self.mail_group_model_id = self.ir_model.search(cr, uid, [('model', '=', 'mail.group')])[0]
self.mail_alias.create(cr, 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(cr, uid,
{'name': 'Pigs', 'description': 'Fans of Pigs, unite !'})
self.group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
def tearDown(self):
# Remove mocks
self.registry('mail.mail').send_get_mail_body = self._send_get_mail_body
@ -136,6 +104,11 @@ class test_mail(test_mail_mockup.TestMailMockups):
def test_00_message_process(self):
""" Testing incoming emails processing. """
cr, uid, user_raoul = self.cr, self.uid, self.user_raoul
# groups@.. will cause the creation of new mail groups
self.mail_group_model_id = self.ir_model.search(cr, uid, [('model', '=', 'mail.group')])[0]
self.mail_alias.create(cr, uid, {'alias_name': 'groups', 'alias_model_id': self.mail_group_model_id})
# Incoming mail creates a new mail_group "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='')
@ -193,14 +166,42 @@ class test_mail(test_mail_mockup.TestMailMockups):
self.assertFalse(new_mail.author_id, 'message process shnould not have found a partner for _abcd_ email address')
self.assertIn('_abcd_', new_mail.email_from, 'message process should set en email_from when not finding a partner_id')
def test_05_thread_parent_resolution(self):
"""Verify parent/child relationships are correctly established when processing incoming mails"""
cr, uid = self.cr, self.uid
group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
msg1 = group_pigs.message_post(body='My Body', subject='1')
msg2 = group_pigs.message_post(body='My Body', subject='2')
msg1, msg2 = self.mail_message.browse(cr, uid, [msg1, msg2])
self.assertTrue(msg1.message_id, "New message should have a proper message_id")
# Reply to msg1, make sure the reply is properly attached using the various reply identification mechanisms
# 1. In-Reply-To header
reply_msg = MAIL_TEMPLATE.format(to='Pretty Pigs <group+pigs@example.com>, other@gmail.com', subject='Re: 1',
extra='In-Reply-To: %s' % msg1.message_id)
self.mail_group.message_process(cr, uid, None, reply_msg)
# 2. References header
reply_msg2 = MAIL_TEMPLATE.format(to='Pretty Pigs <group+pigs@example.com>, other@gmail.com', subject='Re: Re: 1',
extra='References: <2233@a.com>\r\n\t<3edss_dsa@b.com> %s' % msg1.message_id)
self.mail_group.message_process(cr, uid, None, reply_msg2)
# 3. Subject contains [<ID>] + model passed to message+process -> only attached to group, not to mail
reply_msg3 = MAIL_TEMPLATE.format(to='Pretty Pigs <group+pigs@example.com>, other@gmail.com',
extra='', subject='Re: [%s] 1' % self.group_pigs_id)
self.mail_group.message_process(cr, uid, 'mail.group', reply_msg3)
group_pigs.refresh()
msg1.refresh()
self.assertEqual(5, len(group_pigs.message_ids), 'group should contain 5 messages')
self.assertEqual(2, len(msg1.child_ids), 'msg1 should have 2 children now')
def test_10_followers_function_field(self):
""" Tests designed for the many2many function field 'follower_ids'.
We will test to perform writes using the many2many commands 0, 3, 4,
5 and 6. """
cr, uid, user_admin, group_pigs = self.cr, self.uid, self.user_admin, self.group_pigs
cr, uid, user_admin, partner_bert_id, group_pigs = self.cr, self.uid, self.user_admin, self.partner_bert_id, self.group_pigs
# Data: create partner Bert Poilu
partner_bert_id = self.res_partner.create(cr, uid, {'name': 'Bert Poilu'})
# Data: create 'disturbing' values in mail.followers: same res_id, other res_model; same res_model, other res_id
group_dummy_id = self.mail_group.create(cr, uid,
{'name': 'Dummy group'})
@ -254,10 +255,7 @@ class test_mail(test_mail_mockup.TestMailMockups):
def test_11_message_followers_and_subtypes(self):
""" Tests designed for the subscriber API as well as message subtypes """
cr, uid, user_admin, group_pigs = self.cr, self.uid, self.user_admin, self.group_pigs
# Data: user Raoul
user_raoul_id = self.user_raoul_id
user_raoul = self.res_users.browse(cr, uid, user_raoul_id)
cr, uid, user_admin, user_raoul, group_pigs = self.cr, self.uid, self.user_admin, self.user_raoul, self.group_pigs
# 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'})
@ -271,8 +269,8 @@ class test_mail(test_mail_mockup.TestMailMockups):
# ----------------------------------------
# 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.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]
@ -284,7 +282,7 @@ class test_mail(test_mail_mockup.TestMailMockups):
self.assertEqual(set(fol_subtype_ids), set(default_group_subtypes), 'subscription subtypes are incorrect')
# Do: Unsubscribe Raoul twice through message_unsubscribe_users
group_pigs.message_unsubscribe_users([user_raoul_id, user_raoul_id])
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]
@ -327,7 +325,7 @@ class test_mail(test_mail_mockup.TestMailMockups):
def test_21_message_post(self):
""" Tests designed for message_post. """
cr, uid, user_admin, group_pigs = self.cr, self.uid, self.user_admin, self.group_pigs
cr, uid, user_raoul, group_pigs = self.cr, self.uid, self.user_raoul, self.group_pigs
self.res_users.write(cr, uid, [uid], {'signature': 'Admin', 'email': 'a@a'})
# 1 - Bert Tartopoils, with email, should receive emails for comments and emails
p_b_id = self.res_partner.create(cr, uid, {'name': 'Bert Tartopoils', 'email': 'b@b'})
@ -336,18 +334,18 @@ class test_mail(test_mail_mockup.TestMailMockups):
# 3 - Dédé Grosbedon, without email, to test email verification; should receive emails for every message
p_d_id = self.res_partner.create(cr, uid, {'name': 'Dédé Grosbedon', 'notification_email_send': 'all'})
# Subscribe #1, #2
group_pigs.message_subscribe([p_b_id, p_c_id])
# Subscribe Raoul, #1, #2
group_pigs.message_subscribe([self.partner_raoul_id, p_b_id, p_c_id])
# Mail data
_subject = 'Pigs'
_mail_subject = '%s posted on %s' % (user_admin.name, group_pigs.name)
_mail_subject = '%s posted on %s' % (user_raoul.name, group_pigs.name)
_body1 = 'Pigs rules'
_mail_body1 = 'Pigs rules\n<div><p>Admin</p></div>\n'
_mail_bodyalt1 = 'Pigs rules\nAdmin\n'
_mail_body1 = 'Pigs rules\n<div><p>Raoul</p></div>\n'
_mail_bodyalt1 = 'Pigs rules\nRaoul\n'
_body2 = '<html>Pigs rules</html>'
_mail_body2 = html_sanitize('<html>Pigs rules\n<div><p>Admin</p></div>\n</html>')
_mail_bodyalt2 = 'Pigs rules\nAdmin'
_mail_body2 = html_sanitize('<html>Pigs rules\n<div><p>Raoul</p></div>\n</html>')
_mail_bodyalt2 = 'Pigs rules\nRaoul'
_attachments = [('First', 'My first attachment'), ('Second', 'My second attachment')]
# ----------------------------------------
@ -356,80 +354,89 @@ class test_mail(test_mail_mockup.TestMailMockups):
# 1. Post a new comment on Pigs
self._init_mock_build_email()
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)
msg1_id = self.mail_group.message_post(cr, user_raoul.id, self.group_pigs_id, body=_body1, subject=_subject, type='comment', subtype='mt_comment')
message1 = self.mail_message.browse(cr, uid, msg1_id)
sent_emails = self._build_email_kwargs_list
# Test: mail.mail notifications have been deleted
self.assertFalse(self.mail_mail.search(cr, uid, [('mail_message_id', '=', msg_id)]), 'mail.mail notifications should have been auto-deleted!')
self.assertFalse(self.mail_mail.search(cr, uid, [('mail_message_id', '=', msg1_id)]), 'mail.mail notifications should have been auto-deleted!')
# Test: mail_message: subject is _subject, body is _body1 (no formatting done)
self.assertEqual(message.subject, _subject, 'mail.message subject incorrect')
self.assertEqual(message.body, _body1, 'mail.message body incorrect')
self.assertEqual(message1.subject, _subject, 'mail.message subject incorrect')
self.assertEqual(message1.body, _body1, 'mail.message body incorrect')
# Test: sent_email: email send by server: correct subject, body, body_alternative
self.assertEqual(len(sent_emails), 2, 'sent_email number of sent emails incorrect')
for sent_email in sent_emails:
self.assertEqual(sent_email['subject'], _subject, 'sent_email subject incorrect')
self.assertEqual(sent_email['body'], _mail_body1 + '\nBert Tartopoils\n', 'sent_email body incorrect')
self.assertTrue(sent_email['body'] in [_mail_body1 + '\nBert Tartopoils\n', _mail_body1 + '\nAdministrator\n'],
'sent_email body incorrect')
# the html2plaintext uses etree or beautiful soup, so the result may be slighly different
# depending if you have installed beautiful soup.
self.assertIn(sent_email['body_alternative'], _mail_bodyalt1 + '\nBert Tartopoils\n', 'sent_email body_alternative is incorrect')
self.assertTrue(sent_email['body_alternative'] in [_mail_bodyalt1 + '\nBert Tartopoils\n', _mail_bodyalt1 + '\nAdministrator\n'],
'sent_email body_alternative is incorrect')
# Test: mail_message: notified_partner_ids = group followers
message_pids = set([partner.id for partner in message.notified_partner_ids])
test_pids = set([p_b_id, p_c_id])
self.assertEqual(test_pids, message_pids, 'mail.message partners incorrect')
message_pids = set([partner.id for partner in message1.notified_partner_ids])
test_pids = set([self.partner_admin_id, p_b_id, p_c_id])
self.assertEqual(test_pids, message_pids, 'mail.message notified partners incorrect')
# Test: notification linked to this message = group followers = notified_partner_ids
notif_ids = self.mail_notification.search(cr, uid, [('message_id', '=', message.id)])
notif_ids = self.mail_notification.search(cr, uid, [('message_id', '=', msg1_id)])
notif_pids = set([notif.partner_id.id for notif in self.mail_notification.browse(cr, uid, notif_ids)])
self.assertEqual(notif_pids, test_pids, 'mail.message notification partners incorrect')
# Test: sent_email: email_to should contain b@b, not c@c (pref email), not a@a (writer)
for sent_email in sent_emails:
self.assertEqual(sent_email['email_to'], ['b@b'], 'sent_email email_to is incorrect')
self.assertTrue(set(sent_email['email_to']).issubset(set(['a@a', 'b@b'])), 'sent_email email_to is incorrect')
# ----------------------------------------
# CASE2: post an email with attachments, parent_id, partner_ids
# CASE2: post an email with attachments, parent_id, partner_ids, parent notification
# ----------------------------------------
# 1. Post a new email comment on Pigs
self._init_mock_build_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)
msg2_id = self.mail_group.message_post(cr, user_raoul.id, self.group_pigs_id, body=_body2, type='email', subtype='mt_comment',
partner_ids=[(6, 0, [p_d_id])], parent_id=msg1_id, attachments=_attachments)
message2 = self.mail_message.browse(cr, uid, msg2_id)
sent_emails = self._build_email_kwargs_list
self.assertFalse(self.mail_mail.search(cr, uid, [('mail_message_id', '=', msg_id2)]), 'mail.mail notifications should have been auto-deleted!')
self.assertFalse(self.mail_mail.search(cr, uid, [('mail_message_id', '=', msg2_id)]), 'mail.mail notifications should have been auto-deleted!')
# Test: mail_message: subject is False, body is _body2 (no formatting done), parent_id is msg_id
self.assertEqual(message.subject, False, 'mail.message subject incorrect')
self.assertEqual(message.body, html_sanitize(_body2), 'mail.message body incorrect')
self.assertEqual(message.parent_id.id, msg_id, 'mail.message parent_id incorrect')
self.assertEqual(message2.subject, False, 'mail.message subject incorrect')
self.assertEqual(message2.body, html_sanitize(_body2), 'mail.message body incorrect')
self.assertEqual(message2.parent_id.id, msg1_id, 'mail.message parent_id incorrect')
# Test: sent_email: email send by server: correct automatic subject, body, body_alternative
self.assertEqual(len(sent_emails), 2, 'sent_email number of sent emails incorrect')
self.assertEqual(len(sent_emails), 3, 'sent_email number of sent emails incorrect')
for sent_email in sent_emails:
self.assertEqual(sent_email['subject'], _mail_subject, 'sent_email subject incorrect')
self.assertIn(_mail_body2, sent_email['body'], 'sent_email body incorrect')
self.assertIn(_mail_bodyalt2, sent_email['body_alternative'], 'sent_email body_alternative incorrect')
# Test: mail_message: notified_partner_ids = group followers
message_pids = set([partner.id for partner in message.notified_partner_ids])
test_pids = set([p_b_id, p_c_id, p_d_id])
message_pids = set([partner.id for partner in message2.notified_partner_ids])
test_pids = set([self.partner_admin_id, p_b_id, p_c_id, p_d_id])
self.assertEqual(message_pids, test_pids, 'mail.message partners incorrect')
# Test: notifications linked to this message = group followers = notified_partner_ids
notif_ids = self.mail_notification.search(cr, uid, [('message_id', '=', message.id)])
notif_ids = self.mail_notification.search(cr, uid, [('message_id', '=', msg2_id)])
notif_pids = set([notif.partner_id.id for notif in self.mail_notification.browse(cr, uid, notif_ids)])
self.assertEqual(notif_pids, test_pids, 'mail.message notification partners incorrect')
# Test: sent_email: email_to should contain b@b, c@c, not a@a (writer)
for sent_email in sent_emails:
self.assertTrue(set(sent_email['email_to']).issubset(set(['b@b', 'c@c'])), 'sent_email email_to incorrect')
self.assertTrue(set(sent_email['email_to']).issubset(set(['a@a', 'b@b', 'c@c'])), 'sent_email email_to incorrect')
# Test: attachments
for attach in message.attachment_ids:
for attach in message2.attachment_ids:
self.assertEqual(attach.res_model, 'mail.group', 'mail.message attachment res_model incorrect')
self.assertEqual(attach.res_id, self.group_pigs_id, 'mail.message attachment res_id incorrect')
self.assertIn((attach.name, attach.datas.decode('base64')), _attachments,
'mail.message attachment name / data incorrect')
# Test: download attachments
for attach in message.attachment_ids:
dl_attach = self.mail_message.download_attachment(cr, uid, id_message=message.id, attachment_id=attach.id)
self.assertIn(( dl_attach['filename'], dl_attach['base64'].decode('base64') ), _attachments, 'mail.message download_attachment is incorrect')
for attach in message2.attachment_ids:
dl_attach = self.mail_message.download_attachment(cr, user_raoul.id, id_message=message2.id, attachment_id=attach.id)
self.assertIn((dl_attach['filename'], dl_attach['base64'].decode('base64')), _attachments, 'mail.message download_attachment is incorrect')
# 2. Dédé has been notified -> should also have been notified of the parent message
message1.refresh()
message_pids = set([partner.id for partner in message1.notified_partner_ids])
test_pids = set([self.partner_admin_id, p_b_id, p_c_id, p_d_id])
self.assertEqual(test_pids, message_pids, 'mail.message parent notification not created')
# 3. Reply to the last message, check that its parent will be the first message
msg_id3 = self.mail_group.message_post(cr, uid, self.group_pigs_id, body='Test', parent_id=msg_id2)
message = self.mail_message.browse(cr, uid, msg_id3)
self.assertEqual(message.parent_id.id, msg_id, 'message_post did not flatten the thread structure')
msg3_id = self.mail_group.message_post(cr, user_raoul.id, self.group_pigs_id, body='Test', parent_id=msg2_id)
message = self.mail_message.browse(cr, uid, msg3_id)
self.assertEqual(message.parent_id.id, msg1_id, 'message_post did not flatten the thread structure')
def test_25_message_compose_wizard(self):
""" Tests designed for the mail.compose.message wizard. """
@ -509,7 +516,7 @@ class test_mail(test_mail_mockup.TestMailMockups):
# Test: mail.message: attachments
for attach in compose.attachment_ids:
self.assertIn((attach.datas_fname, attach.datas.decode('base64')), _attachments_test, 'mail.message attachment name / data incorrect')
# ----------------------------------------
# CASE3: mass_mail on Pigs and Bird
# ----------------------------------------
@ -538,169 +545,9 @@ class test_mail(test_mail_mockup.TestMailMockups):
self.assertEqual(message2.subject, _subject, 'mail.message subject incorrect')
self.assertEqual(message2.body, group_bird.description, 'mail.message body incorrect')
def test_30_message_read(self):
""" Tests for message_read and expandables. """
cr, uid, user_admin, group_pigs = self.cr, self.uid, self.user_admin, self.group_pigs
pigs_domain = [('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)]
# Data: create a discussion in Pigs (3 threads, with respectively 0, 4 and 4 answers)
msg_id0 = self.group_pigs.message_post(body='0', subtype='mt_comment')
msg_id1 = self.group_pigs.message_post(body='1', subtype='mt_comment')
msg_id2 = self.group_pigs.message_post(body='2', subtype='mt_comment')
msg_id3 = self.group_pigs.message_post(body='1-1', subtype='mt_comment', parent_id=msg_id1)
msg_id4 = self.group_pigs.message_post(body='2-1', subtype='mt_comment', parent_id=msg_id2)
msg_id5 = self.group_pigs.message_post(body='1-2', subtype='mt_comment', parent_id=msg_id1)
msg_id6 = self.group_pigs.message_post(body='2-2', subtype='mt_comment', parent_id=msg_id2)
msg_id7 = self.group_pigs.message_post(body='1-1-1', subtype='mt_comment', parent_id=msg_id3)
msg_id8 = self.group_pigs.message_post(body='2-1-1', subtype='mt_comment', parent_id=msg_id4)
msg_id9 = self.group_pigs.message_post(body='1-1-1', subtype='mt_comment', parent_id=msg_id3)
msg_id10 = self.group_pigs.message_post(body='2-1-1', subtype='mt_comment', parent_id=msg_id4)
msg_ids = [msg_id10, msg_id9, msg_id8, msg_id7, msg_id6, msg_id5, msg_id4, msg_id3, msg_id2, msg_id1, msg_id0]
ordered_msg_ids = [msg_id2, msg_id4, msg_id6, msg_id8, msg_id10, msg_id1, msg_id3, msg_id5, msg_id7, msg_id9, msg_id0]
# Test: read some specific ids
read_msg_list = self.mail_message.message_read(cr, uid, ids=msg_ids[2:4], domain=[('body', 'like', 'dummy')])
read_msg_ids = [msg.get('id') for msg in read_msg_list]
self.assertEqual(msg_ids[2:4], read_msg_ids, 'message_read with direct ids should read only the requested ids')
# Test: read messages of Pigs through a domain, being thread or not threaded
read_msg_list = self.mail_message.message_read(cr, uid, domain=pigs_domain, limit=200)
read_msg_ids = [msg.get('id') for msg in read_msg_list]
self.assertEqual(msg_ids, read_msg_ids, 'message_read flat with domain on Pigs should equal all messages of Pigs')
read_msg_list = self.mail_message.message_read(cr, uid, domain=pigs_domain, limit=200, thread_level=1)
read_msg_ids = [msg.get('id') for msg in read_msg_list]
self.assertEqual(ordered_msg_ids, read_msg_ids,
'message_read threaded with domain on Pigs should equal all messages of Pigs, and sort them with newer thread first, last message last in thread')
# ----------------------------------------
# CASE1: message_read with domain, threaded
# We simulate an entire flow, using the expandables to test them
# ----------------------------------------
# Do: read last message, threaded
read_msg_list = self.mail_message.message_read(cr, uid, domain=pigs_domain, limit=1, thread_level=1)
read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('type') != 'expandable']
# TDE TODO: test expandables order
type_list = map(lambda item: item.get('type'), read_msg_list)
# Test: structure content, ancestor is added to the read messages, ordered by id, ancestor is set, 2 expandables
self.assertEqual(len(read_msg_list), 4, 'message_read on last Pigs message should return 2 messages and 2 expandables')
self.assertEqual(set([msg_id2, msg_id10]), set(read_msg_ids), 'message_read on the last Pigs message should also get its parent')
self.assertEqual(read_msg_list[1].get('parent_id'), read_msg_list[0].get('id'), 'message_read should set the ancestor to the thread header')
# Data: get expandables
new_threads_exp, new_msg_exp = None, None
for msg in read_msg_list:
if msg.get('type') == 'expandable' and msg.get('nb_messages') == -1 and msg.get('max_limit'):
new_threads_exp = msg
elif msg.get('type') == 'expandable':
new_msg_exp = msg
# Do: fetch new messages in first thread, domain from expandable
self.assertIsNotNone(new_msg_exp, 'message_read on last Pigs message should have returned a new messages expandable')
domain = new_msg_exp.get('domain', [])
# Test: expandable, conditions in domain
self.assertIn(('id', 'child_of', msg_id2), domain, 'new messages expandable domain should contain a child_of condition')
self.assertIn(('id', '>=', msg_id4), domain, 'new messages expandable domain should contain an id greater than condition')
self.assertIn(('id', '<=', msg_id8), domain, 'new messages expandable domain should contain an id less than condition')
self.assertEqual(new_msg_exp.get('parent_id'), msg_id2, 'new messages expandable should have parent_id set to the thread header')
# Do: message_read with domain, thread_level=0, parent_id=msg_id2 (should be imposed by JS), 2 messages
read_msg_list = self.mail_message.message_read(cr, uid, domain=domain, limit=2, thread_level=0, parent_id=msg_id2)
read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('type') != 'expandable']
new_msg_exp = [msg for msg in read_msg_list if msg.get('type') == 'expandable'][0]
# Test: structure content, 2 messages and 1 thread expandable
self.assertEqual(len(read_msg_list), 3, 'message_read in Pigs thread should return 2 messages and 1 expandables')
self.assertEqual(set([msg_id6, msg_id8]), set(read_msg_ids), 'message_read in Pigs thread should return 2 more previous messages in thread')
# Do: read the last message
read_msg_list = self.mail_message.message_read(cr, uid, domain=new_msg_exp.get('domain'), limit=2, thread_level=0, parent_id=msg_id2)
read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('type') != 'expandable']
# Test: structure content, 1 message
self.assertEqual(len(read_msg_list), 1, 'message_read in Pigs thread should return 1 message')
self.assertEqual(set([msg_id4]), set(read_msg_ids), 'message_read in Pigs thread should return the last message in thread')
# Do: fetch a new thread, domain from expandable
self.assertIsNotNone(new_threads_exp, 'message_read on last Pigs message should have returned a new threads expandable')
domain = new_threads_exp.get('domain', [])
# Test: expandable, conditions in domain
for condition in pigs_domain:
self.assertIn(condition, domain, 'new threads expandable domain should contain the message_read domain parameter')
self.assertFalse(new_threads_exp.get('parent_id'), 'new threads expandable should not have an parent_id')
# Do: message_read with domain, thread_level=1 (should be imposed by JS)
read_msg_list = self.mail_message.message_read(cr, uid, domain=domain, limit=1, thread_level=1)
read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('type') != 'expandable']
# Test: structure content, ancestor is added to the read messages, ordered by id, ancestor is set, 2 expandables
self.assertEqual(len(read_msg_list), 4, 'message_read on Pigs should return 2 messages and 2 expandables')
self.assertEqual(set([msg_id1, msg_id9]), set(read_msg_ids), 'message_read on a Pigs message should also get its parent')
self.assertEqual(read_msg_list[1].get('parent_id'), read_msg_list[0].get('id'), 'message_read should set the ancestor to the thread header')
# Data: get expandables
new_threads_exp, new_msg_exp = None, None
for msg in read_msg_list:
if msg.get('type') == 'expandable' and msg.get('nb_messages') == -1 and msg.get('max_limit'):
new_threads_exp = msg
elif msg.get('type') == 'expandable':
new_msg_exp = msg
# Do: fetch new messages in second thread, domain from expandable
self.assertIsNotNone(new_msg_exp, 'message_read on Pigs message should have returned a new messages expandable')
domain = new_msg_exp.get('domain', [])
# Test: expandable, conditions in domain
self.assertIn(('id', 'child_of', msg_id1), domain, 'new messages expandable domain should contain a child_of condition')
self.assertIn(('id', '>=', msg_id3), domain, 'new messages expandable domain should contain an id greater than condition')
self.assertIn(('id', '<=', msg_id7), domain, 'new messages expandable domain should contain an id less than condition')
self.assertEqual(new_msg_exp.get('parent_id'), msg_id1, 'new messages expandable should have ancestor_id set to the thread header')
# Do: message_read with domain, thread_level=0, parent_id=msg_id1 (should be imposed by JS)
read_msg_list = self.mail_message.message_read(cr, uid, domain=domain, limit=200, thread_level=0, parent_id=msg_id1)
read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('type') != 'expandable']
# Test: other message in thread have been fetch
self.assertEqual(set([msg_id3, msg_id5, msg_id7]), set(read_msg_ids), 'message_read on the last Pigs message should also get its parent')
# Test: fetch a new thread, domain from expandable
self.assertIsNotNone(new_threads_exp, 'message_read should have returned a new threads expandable')
domain = new_threads_exp.get('domain', [])
# Test: expandable, conditions in domain
for condition in pigs_domain:
self.assertIn(condition, domain, 'general expandable domain should contain the message_read domain parameter')
# Do: message_read with domain, thread_level=1 (should be imposed by JS)
read_msg_list = self.mail_message.message_read(cr, uid, domain=domain, limit=1, thread_level=1)
read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('type') != 'expandable']
# Test: structure content, ancestor is added to the read messages, ordered by id, ancestor is set, 2 expandables
self.assertEqual(len(read_msg_list), 1, 'message_read on Pigs should return 1 message because everything else has been fetched')
self.assertEqual([msg_id0], read_msg_ids, 'message_read after 2 More should return only 1 last message')
# ----------------------------------------
# CASE2: message_read with domain, flat
# ----------------------------------------
# Do: read 2 lasts message, flat
read_msg_list = self.mail_message.message_read(cr, uid, domain=pigs_domain, limit=2, thread_level=0)
read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('type') != 'expandable']
# Test: structure content, ancestor is added to the read messages, ordered by id, ancestor is not set, 1 expandable
self.assertEqual(len(read_msg_list), 3, 'message_read on last Pigs message should return 2 messages and 1 expandable')
self.assertEqual(set([msg_id9, msg_id10]), set(read_msg_ids), 'message_read flat on Pigs last messages should only return those messages')
self.assertFalse(read_msg_list[0].get('parent_id'), 'message_read flat should set the ancestor as False')
self.assertFalse(read_msg_list[1].get('parent_id'), 'message_read flat should set the ancestor as False')
# Data: get expandables
new_threads_exp, new_msg_exp = None, None
for msg in read_msg_list:
if msg.get('type') == 'expandable' and msg.get('nb_messages') == -1 and msg.get('max_limit'):
new_threads_exp = msg
# Do: fetch new messages, domain from expandable
self.assertIsNotNone(new_threads_exp, 'message_read flat on the 2 last Pigs messages should have returns a new threads expandable')
domain = new_threads_exp.get('domain', [])
# Test: expandable, conditions in domain
for condition in pigs_domain:
self.assertIn(condition, domain, 'new threads expandable domain should contain the message_read domain parameter')
# Do: message_read with domain, thread_level=0 (should be imposed by JS)
read_msg_list = self.mail_message.message_read(cr, uid, domain=domain, limit=20, thread_level=0)
read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('type') != 'expandable']
# Test: structure content, ancestor is added to the read messages, ordered by id, ancestor is set, 2 expandables
self.assertEqual(len(read_msg_list), 9, 'message_read on Pigs should return 9 messages and 0 expandable')
self.assertEqual([msg_id8, msg_id7, msg_id6, msg_id5, msg_id4, msg_id3, msg_id2, msg_id1, msg_id0], read_msg_ids,
'message_read, More on flat, should return all remaning messages')
def test_40_needaction(self):
def test_30_needaction(self):
""" Tests for mail.message needaction. """
cr, uid, user_admin, group_pigs = self.cr, self.uid, self.user_admin, self.group_pigs
user_raoul = self.res_users.browse(cr, uid, self.user_raoul_id)
cr, uid, user_admin, user_raoul, group_pigs = self.cr, self.uid, self.user_admin, self.user_raoul, self.group_pigs
group_pigs_demo = self.mail_group.browse(cr, self.user_raoul_id, self.group_pigs_id)
na_admin_base = self.mail_message._needaction_count(cr, uid, domain=[])
na_demo_base = self.mail_message._needaction_count(cr, user_raoul.id, domain=[])
@ -739,86 +586,3 @@ class test_mail(test_mail_mockup.TestMailMockups):
na_demo_group = self.mail_message._needaction_count(cr, user_raoul.id, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)])
self.assertEqual(na_demo, na_demo_base + 0, 'Demo should have 0 new needaction')
self.assertEqual(na_demo_group, 0, 'Demo should have 0 needaction related to Pigs')
def test_50_thread_parent_resolution(self):
"""Verify parent/child relationships are correctly established when processing incoming mails"""
cr, uid = self.cr, self.uid
group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
msg1 = group_pigs.message_post(body='My Body', subject='1')
msg2 = group_pigs.message_post(body='My Body', subject='2')
msg1, msg2 = self.mail_message.browse(cr, uid, [msg1, msg2])
self.assertTrue(msg1.message_id, "New message should have a proper message_id")
# Reply to msg1, make sure the reply is properly attached using the various reply identification mechanisms
# 1. In-Reply-To header
reply_msg = MAIL_TEMPLATE.format(to='Pretty Pigs <group+pigs@example.com>, other@gmail.com', subject='Re: 1',
extra='In-Reply-To: %s' % msg1.message_id)
self.mail_group.message_process(cr, uid, None, reply_msg)
# 2. References header
reply_msg2 = MAIL_TEMPLATE.format(to='Pretty Pigs <group+pigs@example.com>, other@gmail.com', subject='Re: Re: 1',
extra='References: <2233@a.com>\r\n\t<3edss_dsa@b.com> %s' % msg1.message_id)
self.mail_group.message_process(cr, uid, None, reply_msg2)
# 3. Subject contains [<ID>] + model passed to message+process -> only attached to group, not to mail
reply_msg3 = MAIL_TEMPLATE.format(to='Pretty Pigs <group+pigs@example.com>, other@gmail.com',
extra='', subject='Re: [%s] 1' % self.group_pigs_id)
self.mail_group.message_process(cr, uid, 'mail.group', reply_msg3)
group_pigs.refresh()
msg1.refresh()
self.assertEqual(5, len(group_pigs.message_ids), 'group should contain 5 messages')
# TDE note: python test + debug because of the random error we see with the next assert
if len(msg1.child_ids) != 2:
msg_ids = self.mail_message.search(cr, uid, [('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)], limit=10)
for new_msg in self.mail_message.browse(cr, uid, msg_ids):
print new_msg.subject, '(id', new_msg.id, ')', 'parent_id:', new_msg.parent_id
print '\tchild_ids', [child.id for child in new_msg.child_ids]
self.assertEqual(2, len(msg1.child_ids), 'msg1 should have 2 children now')
def test_60_message_vote(self):
""" Test designed for the vote/unvote feature. """
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
msg_id = group_pigs.message_post(body='My Body', subject='1')
msg = self.mail_message.browse(cr, uid, msg_id)
# Do: Admin vote for msg
self.mail_message.vote_toggle(cr, uid, [msg.id])
msg.refresh()
# Test: msg has Admin as voter
self.assertEqual(set(msg.vote_user_ids), set([user_admin]), 'mail_message vote: after voting, Admin should be in the voter')
# Do: Bert vote for msg
self.mail_message.vote_toggle(cr, user_raoul.id, [msg.id])
msg.refresh()
# Test: msg has Admin and Bert as voters
self.assertEqual(set(msg.vote_user_ids), set([user_admin, user_raoul]), 'mail_message vote: after voting, Admin and Bert should be in the voters')
# Do: Admin unvote for msg
self.mail_message.vote_toggle(cr, uid, [msg.id])
msg.refresh()
# 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_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
msg_id = group_pigs.message_post(body='My Body', subject='1')
msg = self.mail_message.browse(cr, uid, msg_id)
# Do: Admin stars msg
self.mail_message.set_message_starred(cr, uid, [msg.id], True)
msg.refresh()
# Test: msg starred by Admin
self.assertTrue(msg.starred, 'mail_message starred failed')
# Do: Bert stars msg
self.mail_message.set_message_starred(cr, user_raoul.id, [msg.id], True)
msg.refresh()
# Test: msg starred by Admin and Raoul
# 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.set_message_starred(cr, uid, [msg.id], False)
msg.refresh()
# Test: msg starred by Raoul
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')

View File

@ -0,0 +1,375 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Business Applications
# Copyright (c) 2012-TODAY OpenERP S.A. <http://openerp.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.addons.mail.tests.test_mail_base import TestMailBase
from openerp.osv.orm import except_orm
from openerp.tools import mute_logger
class test_mail_access_rights(TestMailBase):
def setUp(self):
super(test_mail_access_rights, self).setUp()
cr, uid = self.cr, self.uid
# Test mail.group: public to provide access to everyone
self.group_jobs_id = self.mail_group.create(cr, uid, {'name': 'Jobs', 'public': 'public'})
# Test mail.group: private to restrict access
self.group_priv_id = self.mail_group.create(cr, uid, {'name': 'Private', 'public': 'private'})
@mute_logger('openerp.addons.base.ir.ir_model', 'openerp.osv.orm')
def test_00_mail_group_access_rights(self):
""" Testing mail_group access rights and basic mail_thread features """
cr, uid, user_bert_id, user_raoul_id = self.cr, self.uid, self.user_bert_id, self.user_raoul_id
# Do: Bert reads Jobs -> ok, public
self.mail_group.read(cr, user_bert_id, [self.group_jobs_id])
# Do: Bert read Pigs -> ko, restricted to employees
self.assertRaises(except_orm, self.mail_group.read,
cr, user_bert_id, [self.group_pigs_id])
# Do: Raoul read Pigs -> ok, belong to employees
self.mail_group.read(cr, user_raoul_id, [self.group_pigs_id])
# Do: Bert creates a group -> ko, no access rights
self.assertRaises(except_orm, self.mail_group.create,
cr, user_bert_id, {'name': 'Test'})
# Do: Raoul creates a restricted group -> ok
new_group_id = self.mail_group.create(cr, user_raoul_id, {'name': 'Test'})
# Do: Bert added in followers, read -> ok, in followers
self.mail_group.message_subscribe_users(cr, uid, [new_group_id], [user_bert_id])
self.mail_group.read(cr, user_bert_id, [new_group_id])
# Do: Raoul reads Priv -> ko, private
self.assertRaises(except_orm, self.mail_group.read,
cr, user_raoul_id, [self.group_priv_id])
# Do: Raoul added in follower, read -> ok, in followers
self.mail_group.message_subscribe_users(cr, uid, [self.group_priv_id], [user_raoul_id])
self.mail_group.read(cr, user_raoul_id, [self.group_priv_id])
# Do: Raoul write on Jobs -> ok
self.mail_group.write(cr, user_raoul_id, [self.group_priv_id], {'name': 'modified'})
# Do: Bert cannot write on Private -> ko (read but no write)
self.assertRaises(except_orm, self.mail_group.write,
cr, user_bert_id, [self.group_priv_id], {'name': 're-modified'})
# Test: Bert cannot unlink the group
self.assertRaises(except_orm,
self.mail_group.unlink,
cr, user_bert_id, [self.group_priv_id])
# Do: Raoul unlinks the group, there are no followers and messages left
self.mail_group.unlink(cr, user_raoul_id, [self.group_priv_id])
fol_ids = self.mail_followers.search(cr, uid, [('res_model', '=', 'mail.group'), ('res_id', '=', self.group_priv_id)])
self.assertFalse(fol_ids, 'unlinked document should not have any followers left')
msg_ids = self.mail_message.search(cr, uid, [('model', '=', 'mail.group'), ('res_id', '=', self.group_priv_id)])
self.assertFalse(msg_ids, 'unlinked document should not have any followers left')
@mute_logger('openerp.addons.base.ir.ir_model', 'openerp.osv.orm')
def test_10_mail_message_search_access_rights(self):
""" Testing mail_message.search() using specific _search implementation """
cr, uid, group_pigs_id = self.cr, self.uid, self.group_pigs_id
# Data: comment subtype for mail.message creation
ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'mail', 'mt_comment')
subtype_id = ref and ref[1] or False
# Data: Birds group, private
group_birds_id = self.mail_group.create(self.cr, self.uid, {'name': 'Birds', 'public': 'private'})
# Data: Raoul is member of Pigs
self.mail_group.message_subscribe(cr, uid, [group_pigs_id], [self.partner_raoul_id])
# Data: various author_ids, partner_ids, documents
msg_id1 = self.mail_message.create(cr, uid, {'subject': '_Test', 'body': 'A', 'subtype_id': subtype_id})
msg_id2 = self.mail_message.create(cr, uid, {'subject': '_Test', 'body': 'A+B', 'partner_ids': [(6, 0, [self.partner_bert_id])], 'subtype_id': subtype_id})
msg_id3 = self.mail_message.create(cr, uid, {'subject': '_Test', 'body': 'A Pigs', 'model': 'mail.group', 'res_id': group_pigs_id, 'subtype_id': subtype_id})
msg_id4 = self.mail_message.create(cr, uid, {'subject': '_Test', 'body': 'A+B Pigs', 'model': 'mail.group', 'res_id': group_pigs_id, 'partner_ids': [(6, 0, [self.partner_bert_id])], 'subtype_id': subtype_id})
msg_id5 = self.mail_message.create(cr, uid, {'subject': '_Test', 'body': 'A+R Pigs', 'model': 'mail.group', 'res_id': group_pigs_id, 'partner_ids': [(6, 0, [self.partner_raoul_id])], 'subtype_id': subtype_id})
msg_id6 = self.mail_message.create(cr, uid, {'subject': '_Test', 'body': 'A Birds', 'model': 'mail.group', 'res_id': group_birds_id, 'subtype_id': subtype_id})
msg_id7 = self.mail_message.create(cr, self.user_raoul_id, {'subject': '_Test', 'body': 'B', 'subtype_id': subtype_id})
msg_id8 = self.mail_message.create(cr, self.user_raoul_id, {'subject': '_Test', 'body': 'B+R', 'partner_ids': [(6, 0, [self.partner_raoul_id])], 'subtype_id': subtype_id})
# Test: Bert: 2 messages that have Bert in partner_ids
msg_ids = self.mail_message.search(cr, self.user_bert_id, [('subject', 'like', '_Test')])
self.assertEqual(set([msg_id2, msg_id4]), set(msg_ids), 'mail_message search failed')
# Test: Raoul: 3 messages on Pigs Raoul can read (employee can read group with default values), 0 on Birds (private group)
msg_ids = self.mail_message.search(cr, self.user_raoul_id, [('subject', 'like', '_Test'), ('body', 'like', 'A')])
self.assertEqual(set([msg_id3, msg_id4, msg_id5]), set(msg_ids), 'mail_message search failed')
# Test: Raoul: 3 messages on Pigs Raoul can read (employee can read group with default values), 0 on Birds (private group) + 2 messages as author
msg_ids = self.mail_message.search(cr, self.user_raoul_id, [('subject', 'like', '_Test')])
self.assertEqual(set([msg_id3, msg_id4, msg_id5, msg_id7, msg_id8]), set(msg_ids), 'mail_message search failed')
# Test: Admin: all messages
msg_ids = self.mail_message.search(cr, uid, [('subject', 'like', '_Test')])
self.assertEqual(set([msg_id1, msg_id2, msg_id3, msg_id4, msg_id5, msg_id6, msg_id7, msg_id8]), set(msg_ids), 'mail_message search failed')
@mute_logger('openerp.addons.base.ir.ir_model', 'openerp.osv.orm')
def test_15_mail_message_check_access_rule(self):
""" Testing mail_message.check_access_rule() """
cr, uid = self.cr, self.uid
partner_bert_id, partner_raoul_id = self.partner_bert_id, self.partner_raoul_id
user_bert_id, user_raoul_id = self.user_bert_id, self.user_raoul_id
# Prepare groups: Pigs (employee), Jobs (public)
pigs_msg_id = self.mail_group.message_post(cr, uid, self.group_pigs_id, body='Message')
priv_msg_id = self.mail_group.message_post(cr, uid, self.group_priv_id, body='Message')
# prepare an attachment
attachment_id = self.ir_attachment.create(cr, uid, {'datas': 'My attachment'.encode('base64'), 'name': 'doc.txt', 'datas_fname': 'doc.txt'})
# ----------------------------------------
# CASE1: read
# ----------------------------------------
# Do: create a new mail.message
message_id = self.mail_message.create(cr, uid, {'body': 'My Body', 'attachment_ids': [(4, attachment_id)]})
# Test: Bert reads the message, crash because not notification/not in doc followers/not read on doc
self.assertRaises(except_orm, self.mail_message.read,
cr, user_bert_id, message_id)
# Do: message is pushed to Bert
notif_id = self.mail_notification.create(cr, uid, {'message_id': message_id, 'partner_id': partner_bert_id})
# Test: Bert reads the message, ok because notification pushed
self.mail_message.read(cr, user_bert_id, message_id)
# Test: Bert downloads attachment, ok because he can read message
self.mail_message.download_attachment(cr, user_bert_id, message_id, attachment_id)
# Do: remove notification
self.mail_notification.unlink(cr, uid, notif_id)
# Test: Bert reads the message, crash because not notification/not in doc followers/not read on doc
self.assertRaises(except_orm, self.mail_message.read,
cr, self.user_bert_id, message_id)
# Test: Bert downloads attachment, crash because he can't read message
self.assertRaises(except_orm, self.mail_message.download_attachment,
cr, user_bert_id, message_id, attachment_id)
# Do: Bert is now the author
self.mail_message.write(cr, uid, [message_id], {'author_id': partner_bert_id})
# Test: Bert reads the message, ok because Bert is the author
self.mail_message.read(cr, user_bert_id, message_id)
# Do: Bert is not the author anymore
self.mail_message.write(cr, uid, [message_id], {'author_id': partner_raoul_id})
# Test: Bert reads the message, crash because not notification/not in doc followers/not read on doc
self.assertRaises(except_orm, self.mail_message.read,
cr, user_bert_id, message_id)
# Do: message is attached to a document Bert can read, Jobs
self.mail_message.write(cr, uid, [message_id], {'model': 'mail.group', 'res_id': self.group_jobs_id})
# Test: Bert reads the message, ok because linked to a doc he is allowed to read
self.mail_message.read(cr, user_bert_id, message_id)
# Do: message is attached to a document Bert cannot read, Pigs
self.mail_message.write(cr, uid, [message_id], {'model': 'mail.group', 'res_id': self.group_pigs_id})
# Test: Bert reads the message, crash because not notification/not in doc followers/not read on doc
self.assertRaises(except_orm, self.mail_message.read,
cr, user_bert_id, message_id)
# ----------------------------------------
# CASE2: create
# ----------------------------------------
# Do: Bert creates a message on Pigs -> ko, no creation rights
self.assertRaises(except_orm, self.mail_message.create,
cr, user_bert_id, {'model': 'mail.group', 'res_id': self.group_pigs_id, 'body': 'Test'})
# Do: Bert create a message on Jobs -> ko, no creation rights
self.assertRaises(except_orm, self.mail_message.create,
cr, user_bert_id, {'model': 'mail.group', 'res_id': self.group_jobs_id, 'body': 'Test'})
# Do: Bert create a private message -> ko, no creation rights
self.assertRaises(except_orm, self.mail_message.create,
cr, user_bert_id, {'body': 'Test'})
# Do: Raoul creates a message on Jobs -> ok, write access to the related document
self.mail_message.create(cr, user_raoul_id, {'model': 'mail.group', 'res_id': self.group_jobs_id, 'body': 'Test'})
# Do: Raoul creates a message on Priv -> ko, no write access to the related document
self.assertRaises(except_orm, self.mail_message.create,
cr, user_raoul_id, {'model': 'mail.group', 'res_id': self.group_priv_id, 'body': 'Test'})
# Do: Raoul creates a private message -> ok
self.mail_message.create(cr, user_raoul_id, {'body': 'Test'})
# Do: Raoul creates a reply to a message on Priv -> ko
self.assertRaises(except_orm, self.mail_message.create,
cr, user_raoul_id, {'model': 'mail.group', 'res_id': self.group_priv_id, 'body': 'Test', 'parent_id': priv_msg_id})
# Do: Raoul creates a reply to a message on Priv-> ok if has received parent
self.mail_notification.create(cr, uid, {'message_id': priv_msg_id, 'partner_id': self.partner_raoul_id})
self.mail_message.create(cr, user_raoul_id, {'model': 'mail.group', 'res_id': self.group_priv_id, 'body': 'Test', 'parent_id': priv_msg_id})
def test_20_message_set_star(self):
""" Tests for starring messages and its related access rights """
cr, uid = self.cr, self.uid
# Data: post a message on Pigs
msg_id = self.group_pigs.message_post(body='My Body', subject='1')
msg = self.mail_message.browse(cr, uid, msg_id)
msg_raoul = self.mail_message.browse(cr, self.user_raoul_id, msg_id)
# Do: Admin stars msg
self.mail_message.set_message_starred(cr, uid, [msg.id], True)
msg.refresh()
# Test: notification exists
notif_ids = self.mail_notification.search(cr, uid, [('partner_id', '=', self.partner_admin_id), ('message_id', '=', msg.id)])
self.assertEqual(len(notif_ids), 1, 'mail_message set_message_starred: more than one notification created')
# Test: notification starred
notif = self.mail_notification.browse(cr, uid, notif_ids[0])
self.assertTrue(notif.starred, 'mail_notification starred failed')
self.assertTrue(msg.starred, 'mail_message starred failed')
# Do: Raoul stars msg
self.mail_message.set_message_starred(cr, self.user_raoul_id, [msg.id], True)
msg_raoul.refresh()
# Test: notification exists
notif_ids = self.mail_notification.search(cr, uid, [('partner_id', '=', self.partner_raoul_id), ('message_id', '=', msg.id)])
self.assertEqual(len(notif_ids), 1, 'mail_message set_message_starred: more than one notification created')
# Test: notification starred
notif = self.mail_notification.browse(cr, uid, notif_ids[0])
self.assertTrue(notif.starred, 'mail_notification starred failed')
self.assertTrue(msg_raoul.starred, 'mail_message starred failed')
# Do: Admin unstars msg
self.mail_message.set_message_starred(cr, uid, [msg.id], False)
msg.refresh()
msg_raoul.refresh()
# Test: msg unstarred for Admin, starred for Raoul
self.assertFalse(msg.starred, 'mail_message starred failed')
self.assertTrue(msg_raoul.starred, 'mail_message starred failed')
def test_30_message_set_read(self):
""" Tests for reading messages and its related access rights """
cr, uid = self.cr, self.uid
# Data: post a message on Pigs
msg_id = self.group_pigs.message_post(body='My Body', subject='1')
msg = self.mail_message.browse(cr, uid, msg_id)
msg_raoul = self.mail_message.browse(cr, self.user_raoul_id, msg_id)
# Do: Admin reads msg
self.mail_message.set_message_read(cr, uid, [msg.id], True)
msg.refresh()
# Test: notification exists
notif_ids = self.mail_notification.search(cr, uid, [('partner_id', '=', self.partner_admin_id), ('message_id', '=', msg.id)])
self.assertEqual(len(notif_ids), 1, 'mail_message set_message_read: more than one notification created')
# Test: notification read
notif = self.mail_notification.browse(cr, uid, notif_ids[0])
self.assertTrue(notif.read, 'mail_notification read failed')
self.assertFalse(msg.to_read, 'mail_message read failed')
# Do: Raoul reads msg
self.mail_message.set_message_read(cr, self.user_raoul_id, [msg.id], True)
msg_raoul.refresh()
# Test: notification exists
notif_ids = self.mail_notification.search(cr, uid, [('partner_id', '=', self.partner_raoul_id), ('message_id', '=', msg.id)])
self.assertEqual(len(notif_ids), 1, 'mail_message set_message_read: more than one notification created')
# Test: notification read
notif = self.mail_notification.browse(cr, uid, notif_ids[0])
self.assertTrue(notif.read, 'mail_notification starred failed')
self.assertFalse(msg_raoul.to_read, 'mail_message starred failed')
# Do: Admin unreads msg
self.mail_message.set_message_read(cr, uid, [msg.id], False)
msg.refresh()
msg_raoul.refresh()
# Test: msg unread for Admin, read for Raoul
self.assertTrue(msg.to_read, 'mail_message read failed')
self.assertFalse(msg_raoul.to_read, 'mail_message read failed')
def test_40_message_vote(self):
""" Test designed for the vote/unvote feature. """
cr, uid = self.cr, self.uid
# Data: post a message on Pigs
msg_id = self.group_pigs.message_post(body='My Body', subject='1')
msg = self.mail_message.browse(cr, uid, msg_id)
msg_raoul = self.mail_message.browse(cr, self.user_raoul_id, msg_id)
# Do: Admin vote for msg
self.mail_message.vote_toggle(cr, uid, [msg.id])
msg.refresh()
# Test: msg has Admin as voter
self.assertEqual(set(msg.vote_user_ids), set([self.user_admin]), 'mail_message vote: after voting, Admin should be in the voter')
# Do: Bert vote for msg
self.mail_message.vote_toggle(cr, self.user_raoul_id, [msg.id])
msg_raoul.refresh()
# Test: msg has Admin and Bert as voters
self.assertEqual(set(msg_raoul.vote_user_ids), set([self.user_admin, self.user_raoul]), 'mail_message vote: after voting, Admin and Bert should be in the voters')
# Do: Admin unvote for msg
self.mail_message.vote_toggle(cr, uid, [msg.id])
msg.refresh()
msg_raoul.refresh()
# Test: msg has Bert as voter
self.assertEqual(set(msg.vote_user_ids), set([self.user_raoul]), 'mail_message vote: after unvoting, Bert should be in the voter')
self.assertEqual(set(msg_raoul.vote_user_ids), set([self.user_raoul]), 'mail_message vote: after unvoting, Bert should be in the voter')
@mute_logger('openerp.addons.base.ir.ir_model', 'openerp.osv.orm')
def test_50_mail_flow_access_rights(self):
""" Test a Chatter-looks alike flow to test access rights """
cr, uid = self.cr, self.uid
mail_compose = self.registry('mail.compose.message')
partner_bert_id, partner_raoul_id = self.partner_bert_id, self.partner_raoul_id
user_bert_id, user_raoul_id = self.user_bert_id, self.user_raoul_id
# Prepare groups: Pigs (employee), Jobs (public)
pigs_msg_id = self.mail_group.message_post(cr, uid, self.group_pigs_id, body='Message', partner_ids=[(4, self.partner_admin_id)])
jobs_msg_id = self.mail_group.message_post(cr, uid, self.group_jobs_id, body='Message', partner_ids=[(4, self.partner_admin_id)])
# ----------------------------------------
# CASE1: Bert, without groups
# ----------------------------------------
# Do: Bert reads Jobs basic fields, ok because public = read access on the group
self.mail_group.read(cr, user_bert_id, self.group_jobs_id, ['name', 'description'])
# Do: Bert reads Jobs messages, ok because read access on the group => read access on its messages
jobs_message_ids = self.mail_group.read(cr, user_bert_id, self.group_jobs_id, ['message_ids'])['message_ids']
self.mail_message.read(cr, user_bert_id, jobs_message_ids)
# Do: Bert browses Jobs, ok (no direct browse of partners), ok for messages, ko for followers (accessible to employees or partner manager)
bert_jobs = self.mail_group.browse(cr, user_bert_id, self.group_jobs_id)
trigger_read = bert_jobs.name
for message in bert_jobs.message_ids:
trigger_read = message.subject
for partner in bert_jobs.message_follower_ids:
with self.assertRaises(except_orm):
trigger_read = partner.name
# Do: Bert comments Jobs, ko because no creation right
self.assertRaises(except_orm,
self.mail_group.message_post,
cr, user_bert_id, self.group_jobs_id, body='I love Pigs')
# Do: Bert writes on its own profile, ko because no message create access
with self.assertRaises(except_orm):
self.res_users.message_post(cr, user_bert_id, user_bert_id, body='I love Bert')
self.res_partner.message_post(cr, user_bert_id, partner_bert_id, body='I love Bert')
# ----------------------------------------
# CASE2: Raoul, employee
# ----------------------------------------
# Do: Raoul browses Jobs -> ok, ok for message_ids, of for message_follower_ids
raoul_jobs = self.mail_group.browse(cr, user_raoul_id, self.group_jobs_id)
trigger_read = raoul_jobs.name
for message in raoul_jobs.message_ids:
trigger_read = message.subject
for partner in raoul_jobs.message_follower_ids:
trigger_read = partner.name
# Do: Raoul comments Jobs, ok
self.mail_group.message_post(cr, user_raoul_id, self.group_jobs_id, body='I love Pigs')
# Do: Raoul create a mail.compose.message record on Jobs, because he uses the wizard
compose_id = mail_compose.create(cr, user_raoul_id,
{'subject': 'Subject', 'body': 'Body text', 'partner_ids': []},
{'default_composition_mode': 'comment', 'default_model': 'mail.group', 'default_res_id': self.group_jobs_id})
mail_compose.send_mail(cr, user_raoul_id, [compose_id])
# Do: Raoul replies to a Jobs message using the composer
compose_id = mail_compose.create(cr, user_raoul_id,
{'subject': 'Subject', 'body': 'Body text'},
{'default_composition_mode': 'reply', 'default_parent_id': pigs_msg_id})
mail_compose.send_mail(cr, user_raoul_id, [compose_id])
# Do: Raoul writes on its own profile, ok because follower of its partner
self.res_users.message_post(cr, user_raoul_id, user_raoul_id, body='I love Raoul')
self.res_partner.message_post(cr, user_raoul_id, partner_raoul_id, body='I love Raoul')
compose_id = mail_compose.create(cr, user_raoul_id,
{'subject': 'Subject', 'body': 'Body text', 'partner_ids': []},
{'default_composition_mode': 'comment', 'default_model': 'res.users', 'default_res_id': self.user_raoul_id})
mail_compose.send_mail(cr, user_raoul_id, [compose_id])

View File

@ -1,54 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Business Applications
# Copyright (c) 2012-TODAY OpenERP S.A. <http://openerp.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.tests import common
class TestMailMockups(common.TransactionCase):
def _mock_smtp_gateway(self, *args, **kwargs):
return True
def _init_mock_build_email(self):
self._build_email_args_list = []
self._build_email_kwargs_list = []
def _mock_build_email(self, *args, **kwargs):
""" Mock build_email to be able to test its values. Store them into
some internal variable for latter processing. """
self._build_email_args_list.append(args)
self._build_email_kwargs_list.append(kwargs)
return self._build_email(*args, **kwargs)
def setUp(self):
super(TestMailMockups, self).setUp()
# Install mock SMTP gateway
self._init_mock_build_email()
self._build_email = self.registry('ir.mail_server').build_email
self.registry('ir.mail_server').build_email = self._mock_build_email
self._send_email = self.registry('ir.mail_server').send_email
self.registry('ir.mail_server').send_email = self._mock_smtp_gateway
def tearDown(self):
# Remove mocks
self.registry('ir.mail_server').build_email = self._build_email
self.registry('ir.mail_server').send_email = self._send_email
super(TestMailMockups, self).tearDown()

View File

@ -0,0 +1,184 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Business Applications
# Copyright (c) 2012-TODAY OpenERP S.A. <http://openerp.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.addons.mail.tests.test_mail_base import TestMailBase
class test_mail_access_rights(TestMailBase):
def test_00_message_read(self):
""" Tests for message_read and expandables. """
cr, uid, user_admin, group_pigs = self.cr, self.uid, self.user_admin, self.group_pigs
pigs_domain = [('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)]
# Data: create a discussion in Pigs (3 threads, with respectively 0, 4 and 4 answers)
msg_id0 = self.group_pigs.message_post(body='0', subtype='mt_comment')
msg_id1 = self.group_pigs.message_post(body='1', subtype='mt_comment')
msg_id2 = self.group_pigs.message_post(body='2', subtype='mt_comment')
msg_id3 = self.group_pigs.message_post(body='1-1', subtype='mt_comment', parent_id=msg_id1)
msg_id4 = self.group_pigs.message_post(body='2-1', subtype='mt_comment', parent_id=msg_id2)
msg_id5 = self.group_pigs.message_post(body='1-2', subtype='mt_comment', parent_id=msg_id1)
msg_id6 = self.group_pigs.message_post(body='2-2', subtype='mt_comment', parent_id=msg_id2)
msg_id7 = self.group_pigs.message_post(body='1-1-1', subtype='mt_comment', parent_id=msg_id3)
msg_id8 = self.group_pigs.message_post(body='2-1-1', subtype='mt_comment', parent_id=msg_id4)
msg_id9 = self.group_pigs.message_post(body='1-1-1', subtype='mt_comment', parent_id=msg_id3)
msg_id10 = self.group_pigs.message_post(body='2-1-1', subtype='mt_comment', parent_id=msg_id4)
msg_ids = [msg_id10, msg_id9, msg_id8, msg_id7, msg_id6, msg_id5, msg_id4, msg_id3, msg_id2, msg_id1, msg_id0]
ordered_msg_ids = [msg_id2, msg_id4, msg_id6, msg_id8, msg_id10, msg_id1, msg_id3, msg_id5, msg_id7, msg_id9, msg_id0]
# Test: read some specific ids
read_msg_list = self.mail_message.message_read(cr, uid, ids=msg_ids[2:4], domain=[('body', 'like', 'dummy')])
read_msg_ids = [msg.get('id') for msg in read_msg_list]
self.assertEqual(msg_ids[2:4], read_msg_ids, 'message_read with direct ids should read only the requested ids')
# Test: read messages of Pigs through a domain, being thread or not threaded
read_msg_list = self.mail_message.message_read(cr, uid, domain=pigs_domain, limit=200)
read_msg_ids = [msg.get('id') for msg in read_msg_list]
self.assertEqual(msg_ids, read_msg_ids, 'message_read flat with domain on Pigs should equal all messages of Pigs')
read_msg_list = self.mail_message.message_read(cr, uid, domain=pigs_domain, limit=200, thread_level=1)
read_msg_ids = [msg.get('id') for msg in read_msg_list]
self.assertEqual(ordered_msg_ids, read_msg_ids,
'message_read threaded with domain on Pigs should equal all messages of Pigs, and sort them with newer thread first, last message last in thread')
# ----------------------------------------
# CASE1: message_read with domain, threaded
# We simulate an entire flow, using the expandables to test them
# ----------------------------------------
# Do: read last message, threaded
read_msg_list = self.mail_message.message_read(cr, uid, domain=pigs_domain, limit=1, thread_level=1)
read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('type') != 'expandable']
# TDE TODO: test expandables order
type_list = map(lambda item: item.get('type'), read_msg_list)
# Test: structure content, ancestor is added to the read messages, ordered by id, ancestor is set, 2 expandables
self.assertEqual(len(read_msg_list), 4, 'message_read on last Pigs message should return 2 messages and 2 expandables')
self.assertEqual(set([msg_id2, msg_id10]), set(read_msg_ids), 'message_read on the last Pigs message should also get its parent')
self.assertEqual(read_msg_list[1].get('parent_id'), read_msg_list[0].get('id'), 'message_read should set the ancestor to the thread header')
# Data: get expandables
new_threads_exp, new_msg_exp = None, None
for msg in read_msg_list:
if msg.get('type') == 'expandable' and msg.get('nb_messages') == -1 and msg.get('max_limit'):
new_threads_exp = msg
elif msg.get('type') == 'expandable':
new_msg_exp = msg
# Do: fetch new messages in first thread, domain from expandable
self.assertIsNotNone(new_msg_exp, 'message_read on last Pigs message should have returned a new messages expandable')
domain = new_msg_exp.get('domain', [])
# Test: expandable, conditions in domain
self.assertIn(('id', 'child_of', msg_id2), domain, 'new messages expandable domain should contain a child_of condition')
self.assertIn(('id', '>=', msg_id4), domain, 'new messages expandable domain should contain an id greater than condition')
self.assertIn(('id', '<=', msg_id8), domain, 'new messages expandable domain should contain an id less than condition')
self.assertEqual(new_msg_exp.get('parent_id'), msg_id2, 'new messages expandable should have parent_id set to the thread header')
# Do: message_read with domain, thread_level=0, parent_id=msg_id2 (should be imposed by JS), 2 messages
read_msg_list = self.mail_message.message_read(cr, uid, domain=domain, limit=2, thread_level=0, parent_id=msg_id2)
read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('type') != 'expandable']
new_msg_exp = [msg for msg in read_msg_list if msg.get('type') == 'expandable'][0]
# Test: structure content, 2 messages and 1 thread expandable
self.assertEqual(len(read_msg_list), 3, 'message_read in Pigs thread should return 2 messages and 1 expandables')
self.assertEqual(set([msg_id6, msg_id8]), set(read_msg_ids), 'message_read in Pigs thread should return 2 more previous messages in thread')
# Do: read the last message
read_msg_list = self.mail_message.message_read(cr, uid, domain=new_msg_exp.get('domain'), limit=2, thread_level=0, parent_id=msg_id2)
read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('type') != 'expandable']
# Test: structure content, 1 message
self.assertEqual(len(read_msg_list), 1, 'message_read in Pigs thread should return 1 message')
self.assertEqual(set([msg_id4]), set(read_msg_ids), 'message_read in Pigs thread should return the last message in thread')
# Do: fetch a new thread, domain from expandable
self.assertIsNotNone(new_threads_exp, 'message_read on last Pigs message should have returned a new threads expandable')
domain = new_threads_exp.get('domain', [])
# Test: expandable, conditions in domain
for condition in pigs_domain:
self.assertIn(condition, domain, 'new threads expandable domain should contain the message_read domain parameter')
self.assertFalse(new_threads_exp.get('parent_id'), 'new threads expandable should not have an parent_id')
# Do: message_read with domain, thread_level=1 (should be imposed by JS)
read_msg_list = self.mail_message.message_read(cr, uid, domain=domain, limit=1, thread_level=1)
read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('type') != 'expandable']
# Test: structure content, ancestor is added to the read messages, ordered by id, ancestor is set, 2 expandables
self.assertEqual(len(read_msg_list), 4, 'message_read on Pigs should return 2 messages and 2 expandables')
self.assertEqual(set([msg_id1, msg_id9]), set(read_msg_ids), 'message_read on a Pigs message should also get its parent')
self.assertEqual(read_msg_list[1].get('parent_id'), read_msg_list[0].get('id'), 'message_read should set the ancestor to the thread header')
# Data: get expandables
new_threads_exp, new_msg_exp = None, None
for msg in read_msg_list:
if msg.get('type') == 'expandable' and msg.get('nb_messages') == -1 and msg.get('max_limit'):
new_threads_exp = msg
elif msg.get('type') == 'expandable':
new_msg_exp = msg
# Do: fetch new messages in second thread, domain from expandable
self.assertIsNotNone(new_msg_exp, 'message_read on Pigs message should have returned a new messages expandable')
domain = new_msg_exp.get('domain', [])
# Test: expandable, conditions in domain
self.assertIn(('id', 'child_of', msg_id1), domain, 'new messages expandable domain should contain a child_of condition')
self.assertIn(('id', '>=', msg_id3), domain, 'new messages expandable domain should contain an id greater than condition')
self.assertIn(('id', '<=', msg_id7), domain, 'new messages expandable domain should contain an id less than condition')
self.assertEqual(new_msg_exp.get('parent_id'), msg_id1, 'new messages expandable should have ancestor_id set to the thread header')
# Do: message_read with domain, thread_level=0, parent_id=msg_id1 (should be imposed by JS)
read_msg_list = self.mail_message.message_read(cr, uid, domain=domain, limit=200, thread_level=0, parent_id=msg_id1)
read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('type') != 'expandable']
# Test: other message in thread have been fetch
self.assertEqual(set([msg_id3, msg_id5, msg_id7]), set(read_msg_ids), 'message_read on the last Pigs message should also get its parent')
# Test: fetch a new thread, domain from expandable
self.assertIsNotNone(new_threads_exp, 'message_read should have returned a new threads expandable')
domain = new_threads_exp.get('domain', [])
# Test: expandable, conditions in domain
for condition in pigs_domain:
self.assertIn(condition, domain, 'general expandable domain should contain the message_read domain parameter')
# Do: message_read with domain, thread_level=1 (should be imposed by JS)
read_msg_list = self.mail_message.message_read(cr, uid, domain=domain, limit=1, thread_level=1)
read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('type') != 'expandable']
# Test: structure content, ancestor is added to the read messages, ordered by id, ancestor is set, 2 expandables
self.assertEqual(len(read_msg_list), 1, 'message_read on Pigs should return 1 message because everything else has been fetched')
self.assertEqual([msg_id0], read_msg_ids, 'message_read after 2 More should return only 1 last message')
# ----------------------------------------
# CASE2: message_read with domain, flat
# ----------------------------------------
# Do: read 2 lasts message, flat
read_msg_list = self.mail_message.message_read(cr, uid, domain=pigs_domain, limit=2, thread_level=0)
read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('type') != 'expandable']
# Test: structure content, ancestor is added to the read messages, ordered by id, ancestor is not set, 1 expandable
self.assertEqual(len(read_msg_list), 3, 'message_read on last Pigs message should return 2 messages and 1 expandable')
self.assertEqual(set([msg_id9, msg_id10]), set(read_msg_ids), 'message_read flat on Pigs last messages should only return those messages')
self.assertFalse(read_msg_list[0].get('parent_id'), 'message_read flat should set the ancestor as False')
self.assertFalse(read_msg_list[1].get('parent_id'), 'message_read flat should set the ancestor as False')
# Data: get expandables
new_threads_exp, new_msg_exp = None, None
for msg in read_msg_list:
if msg.get('type') == 'expandable' and msg.get('nb_messages') == -1 and msg.get('max_limit'):
new_threads_exp = msg
# Do: fetch new messages, domain from expandable
self.assertIsNotNone(new_threads_exp, 'message_read flat on the 2 last Pigs messages should have returns a new threads expandable')
domain = new_threads_exp.get('domain', [])
# Test: expandable, conditions in domain
for condition in pigs_domain:
self.assertIn(condition, domain, 'new threads expandable domain should contain the message_read domain parameter')
# Do: message_read with domain, thread_level=0 (should be imposed by JS)
read_msg_list = self.mail_message.message_read(cr, uid, domain=domain, limit=20, thread_level=0)
read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('type') != 'expandable']
# Test: structure content, ancestor is added to the read messages, ordered by id, ancestor is set, 2 expandables
self.assertEqual(len(read_msg_list), 9, 'message_read on Pigs should return 9 messages and 0 expandable')
self.assertEqual([msg_id8, msg_id7, msg_id6, msg_id5, msg_id4, msg_id3, msg_id2, msg_id1, msg_id0], read_msg_ids,
'message_read, More on flat, should return all remaning messages')

View File

@ -73,12 +73,15 @@ class invite_wizard(osv.osv_memory):
if wizard.message:
for follower_id in new_follower_ids:
mail_mail = self.pool.get('mail.mail')
# the invite wizard should create a private message not related to any object -> no model, no res_id
mail_id = mail_mail.create(cr, uid, {
'model': wizard.res_model,
'res_id': wizard.res_id,
'subject': 'Invitation to follow %s' % document.name_get()[0][1],
'body_html': '%s' % wizard.message,
'auto_delete': True,
'res_id': False,
'model': False,
}, context=context)
mail_mail.send(cr, uid, [mail_id], recipient_ids=[follower_id], context=context)
return {'type': 'ir.actions.act_window_close'}

View File

@ -90,6 +90,17 @@ class mail_compose_message(osv.TransientModel):
for field in vals:
if field in fields:
result[field] = vals[field]
# TDE HACK: as mailboxes used default_model='res.users' and default_res_id=uid
# (because of lack of an accessible pid), creating a message on its own
# profile may crash (res_users does not allow writing on it)
# Posting on its own profile works (res_users redirect to res_partner)
# but when creating the mail.message to create the mail.compose.message
# access rights issues may rise
# We therefore directly change the model and res_id
if result.get('model') == 'res.users' and result.get('res_id') == uid:
result['model'] = 'res.partner'
result['res_id'] = self.pool.get('res.users').browse(cr, uid, uid).partner_id.id
return result
def _get_composition_mode_selection(self, cr, uid, context=None):

View File

@ -14,15 +14,18 @@
<field name="parent_id" invisible="1"/>
<!-- visible wizard -->
<label for="partner_ids" string="Recipients"/>
<div>
<span attrs="{'invisible':[('model', '=', False)]}">
<div groups="base.group_user">
<span attrs="{'invisible':['|', ('model', '=', False), ('composition_mode', '!=', 'mass_mail')]}">
Followers of selected items and
</span>
<span attrs="{'invisible':['|', ('model', '=', False), ('composition_mode', '=', 'mass_mail')]}">
Followers of
<field name="record_name" readonly="1" class="oe_inline"
attrs="{'invisible':[('model', '=', False)]}"/>
and
</span>
<field name="partner_ids" widget="many2many_tags_email" placeholder="Add contacts to notify..."
context="{'force_email':True}" required="1"/>
context="{'force_email':True}"/>
</div>
<field name="subject" placeholder="Subject..."/>
</group>

View File

@ -49,6 +49,7 @@ very handy when used in combination with the module 'share'.
'wizard/share_wizard_view.xml',
'acquirer_view.xml',
'security/ir.model.access.csv',
'security/portal_security.xml',
],
'demo': ['portal_demo.xml'],
'css': ['static/src/css/portal.css'],

View File

@ -19,10 +19,12 @@
#
##############################################################################
from openerp import SUPERUSER_ID
from openerp.osv import osv
from openerp.tools import append_content_to_html
from openerp.tools.translate import _
class mail_mail(osv.Model):
""" Update of mail_mail class, to add the signin URL to notifications. """
_inherit = 'mail.mail'
@ -36,7 +38,7 @@ class mail_mail(osv.Model):
body = super(mail_mail, self).send_get_mail_body(cr, uid, mail, partner, context=context)
if partner:
context = dict(context or {}, signup_valid=True)
partner = self.pool.get('res.partner').browse(cr, uid, partner.id, context)
partner = self.pool.get('res.partner').browse(cr, SUPERUSER_ID, partner.id, context=context)
text = _("""Access your personal documents through <a href="%s">our Customer Portal</a>""") % partner.signup_url
body = append_content_to_html(body, ("<div><p>%s</p></div>" % text), plaintext=False)
return body

View File

@ -32,7 +32,7 @@
'default_model': 'mail.group',
'default_res_id': ref('company_news_feed'),
},
'show_link_partner': False,
'show_link': False,
'res_model': 'mail.message',
'thread_level': 1,
}"/>
@ -64,7 +64,7 @@
'default_model': 'mail.group',
'default_res_id': ref('company_jobs'),
},
'show_link_partner': False,
'show_link': False,
'res_model': 'mail.message',
'thread_level': 1,
}"/>

View File

@ -23,7 +23,7 @@
('starred', '=', False),
],
'show_compose_message': False,
'show_link_partner': False,
'show_link': False,
'view_mailbox': True,
'view_inbox': True,
'read_action': 'read'

View File

@ -1,10 +1,9 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_mail_message_portal,mail.message.portal,mail.model_mail_message,portal.group_portal,1,0,1,1
access_mail_notification_portal,mail.notification.portal,mail.model_mail_notification,portal.group_portal,1,1,1,0
access_mail_message_portal,mail.message.portal,mail.model_mail_message,group_portal,1,1,1,1
access_mail_mail_portal,mail.mail.portal,mail.model_mail_mail,group_portal,1,1,1,0
access_mail_notification_portal,mail.notification.portal,mail.model_mail_notification,group_portal,1,1,1,0
access_mail_followers_portal,mail.followers.portal,mail.model_mail_followers,group_portal,1,1,0,0
access_res_partner,res.partner,base.model_res_partner,portal.group_portal,1,0,0,0
access_res_partner_address,res.partner_address,base.model_res_partner_address,portal.group_portal,1,0,0,0
access_res_partner_category,res.partner_category,base.model_res_partner_category,portal.group_portal,1,0,0,0
access_res_partner_title,res.partner_title,base.model_res_partner_title,portal.group_portal,1,0,0,0
access_acquirer,portal.payment.acquirer,portal.model_portal_payment_acquirer,,1,0,0,0
access_acquirer_all,portal.payment.acquirer,portal.model_portal_payment_acquirer,base.group_system,1,1,1,1
access_ir_attachment_group_portal,ir.attachment group_portal,base.model_ir_attachment,portal.group_portal,1,0,1,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_mail_message_portal mail.message.portal mail.model_mail_message portal.group_portal group_portal 1 0 1 1 1
3 access_mail_notification_portal access_mail_mail_portal mail.notification.portal mail.mail.portal mail.model_mail_notification mail.model_mail_mail portal.group_portal group_portal 1 1 1 0
4 access_mail_notification_portal mail.notification.portal mail.model_mail_notification group_portal 1 1 1 0
5 access_mail_followers_portal mail.followers.portal mail.model_mail_followers group_portal 1 1 0 0
6 access_res_partner res.partner base.model_res_partner portal.group_portal 1 0 0 0
access_res_partner_address res.partner_address base.model_res_partner_address portal.group_portal 1 0 0 0
access_res_partner_category res.partner_category base.model_res_partner_category portal.group_portal 1 0 0 0
access_res_partner_title res.partner_title base.model_res_partner_title portal.group_portal 1 0 0 0
7 access_acquirer portal.payment.acquirer portal.model_portal_payment_acquirer 1 0 0 0
8 access_acquirer_all portal.payment.acquirer portal.model_portal_payment_acquirer base.group_system 1 1 1 1
9 access_ir_attachment_group_portal ir.attachment group_portal base.model_ir_attachment portal.group_portal 1 0 1 0

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="1">
<record id="portal_read_own_res_partner" model="ir.rule">
<field name="name">res_partner: read access on my partner</field>
<field name="model_id" ref="base.model_res_partner"/>
<field name="domain_force">[('user_ids', 'in', user.id)]</field>
<field name="groups" eval="[(4, ref('group_portal'))]"/>
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="False"/>
<field name="perm_write" eval="False"/>
</record>
</data>
</openerp>

View File

@ -19,97 +19,107 @@
#
##############################################################################
from openerp.addons.mail.tests import test_mail_mockup
from openerp.addons.mail.tests.test_mail_base import TestMailBase
from openerp.osv.orm import except_orm
from openerp.tools.misc import mute_logger
class test_portal(test_mail_mockup.TestMailMockups):
class test_portal(TestMailBase):
def setUp(self):
super(test_portal, self).setUp()
cr, uid = self.cr, self.uid
self.ir_model = self.registry('ir.model')
self.mail_group = self.registry('mail.group')
self.mail_mail = self.registry('mail.mail')
self.mail_message = self.registry('mail.message')
self.mail_invite = self.registry('mail.wizard.invite')
self.res_users = self.registry('res.users')
self.res_partner = self.registry('res.partner')
# 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 !'})
# Find Portal group
group_portal = self.registry('ir.model.data').get_object(cr, uid, 'portal', 'group_portal')
self.group_portal_id = group_portal.id
# Create Chell (portal user)
self.user_chell_id = self.res_users.create(cr, uid, {'name': 'Chell Gladys', 'login': 'chell', 'groups_id': [(6, 0, [self.group_portal_id])]})
user_chell = self.res_users.browse(cr, uid, self.user_chell_id)
self.partner_chell_id = user_chell.partner_id.id
self.user_chell_id = self.res_users.create(cr, uid, {'name': 'Chell Gladys', 'login': 'chell', 'email': 'chell@gladys.portal', 'groups_id': [(6, 0, [self.group_portal_id])]})
self.user_chell = self.res_users.browse(cr, uid, self.user_chell_id)
self.partner_chell_id = self.user_chell.partner_id.id
# Create a PigsPortal group
self.group_port_id = self.mail_group.create(cr, uid, {'name': 'PigsPortal', 'public': 'groups', 'group_public_id': self.group_portal_id})
# Set an email address for the user running the tests, used as Sender for outgoing mails
self.res_users.write(cr, uid, uid, {'email': 'test@localhost'})
@mute_logger('openerp.addons.base.ir.ir_model')
def test_00_access_rights(self):
@mute_logger('openerp.addons.base.ir.ir_model', 'openerp.osv.orm')
def test_00_mail_access_rights(self):
""" Test basic mail_message and mail_group access rights for portal users. """
cr, uid = self.cr, self.uid
mail_compose = self.registry('mail.compose.message')
# Prepare group: Pigs (portal)
self.mail_group.message_post(cr, uid, self.group_pigs_id, body='Message')
self.mail_group.write(cr, uid, [self.group_pigs_id], {'name': 'Jobs', 'public': 'groups', 'group_public_id': self.group_portal_id})
# Prepare group: Pigs and PigsPortal
pigs_msg_id = self.mail_group.message_post(cr, uid, self.group_pigs_id, body='Message')
port_msg_id = self.mail_group.message_post(cr, uid, self.group_port_id, body='Message')
# ----------------------------------------
# CASE1: Chell will use the Chatter
# ----------------------------------------
# Do: Chell reads Pigs messages, ok because restricted to portal group
message_ids = self.mail_group.read(cr, self.user_chell_id, self.group_pigs_id, ['message_ids'])['message_ids']
self.mail_message.read(cr, self.user_chell_id, message_ids)
# Do: Chell browses Pigs -> ko, employee group
chell_pigs = self.mail_group.browse(cr, self.user_chell_id, self.group_pigs_id)
with self.assertRaises(except_orm):
trigger_read = chell_pigs.name
# Do: Chell posts a message on Pigs, crash because can not write on group or is not in the followers
with self.assertRaises(except_orm):
self.mail_group.message_post(cr, self.user_chell_id, self.group_pigs_id, body='Message')
# Do: Chell is added to Pigs followers
self.mail_group.message_subscribe(cr, uid, [self.group_pigs_id], [self.partner_chell_id])
# Do: Chell is added into Pigs followers and browse it -> ok for messages, ko for partners (no read permission)
self.mail_group.message_subscribe_users(cr, uid, [self.group_pigs_id], [self.user_chell_id])
chell_pigs = self.mail_group.browse(cr, self.user_chell_id, self.group_pigs_id)
trigger_read = chell_pigs.name
for message in chell_pigs.message_ids:
trigger_read = message.subject
for partner in chell_pigs.message_follower_ids:
with self.assertRaises(except_orm):
trigger_read = partner.name
# Test: Chell posts a message on Pigs, ok because in the followers
self.mail_group.message_post(cr, self.user_chell_id, self.group_pigs_id, body='Message')
# Do: Chell comments Pigs, ok because he is now in the followers
self.mail_group.message_post(cr, self.user_chell_id, self.group_pigs_id, body='I love Pigs')
# Do: Chell creates a mail.compose.message record on Pigs, because he uses the wizard
compose_id = mail_compose.create(cr, self.user_chell_id,
{'subject': 'Subject', 'body': 'Body text', 'partner_ids': []},
{'default_composition_mode': 'comment', 'default_model': 'mail.group', 'default_res_id': self.group_pigs_id})
mail_compose.send_mail(cr, self.user_chell_id, [compose_id])
# Do: Chell replies to a Pigs message using the composer
compose_id = mail_compose.create(cr, self.user_chell_id,
{'subject': 'Subject', 'body': 'Body text'},
{'default_composition_mode': 'reply', 'default_parent_id': pigs_msg_id})
mail_compose.send_mail(cr, self.user_chell_id, [compose_id])
def test_50_mail_invite(self):
# Do: Chell browses PigsPortal -> ok because groups security, ko for partners (no read permission)
chell_port = self.mail_group.browse(cr, self.user_chell_id, self.group_port_id)
trigger_read = chell_port.name
for message in chell_port.message_ids:
trigger_read = message.subject
for partner in chell_port.message_follower_ids:
with self.assertRaises(except_orm):
trigger_read = partner.name
def test_10_mail_invite(self):
cr, uid = self.cr, self.uid
user_admin = self.res_users.browse(cr, uid, uid)
mail_invite = self.registry('mail.wizard.invite')
base_url = self.registry('ir.config_parameter').get_param(cr, uid, 'web.base.url', default='')
# 0 - Admin
partner_admin_id = user_admin.partner_id.id
# 1 - Bert Tartopoils, with email, should receive emails for comments and emails
partner_bert_id = self.res_partner.create(cr, uid, {'name': 'Bert Tartopoils', 'email': 'b@b'})
# ----------------------------------------
# CASE: invite Bert to follow Pigs
# ----------------------------------------
# Carine Poilvache, with email, should receive emails for comments and emails
partner_carine_id = self.res_partner.create(cr, uid, {'name': 'Carine Poilvache', 'email': 'c@c'})
# Do: create a mail_wizard_invite, validate it
self._init_mock_build_email()
context = {'default_res_model': 'mail.group', 'default_res_id': self.group_pigs_id}
mail_invite_id = self.mail_invite.create(cr, uid, {'partner_ids': [(4, partner_bert_id)]}, context)
self.mail_invite.add_followers(cr, uid, [mail_invite_id])
mail_invite_id = mail_invite.create(cr, uid, {'partner_ids': [(4, partner_carine_id)]}, context)
mail_invite.add_followers(cr, uid, [mail_invite_id])
# Test: Pigs followers should contain Admin and Bert
group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
follower_ids = [follower.id for follower in group_pigs.message_follower_ids]
self.assertEqual(set(follower_ids), set([partner_admin_id, partner_bert_id]), 'Pigs followers after invite is incorrect')
self.assertEqual(set(follower_ids), set([self.partner_admin_id, partner_carine_id]), 'Pigs followers after invite is incorrect')
# Test: partner must have been prepared for signup
partner_bert = self.res_partner.browse(cr, uid, partner_bert_id)
self.assertTrue(partner_bert.signup_valid, 'partner has not been prepared for signup')
self.assertTrue(base_url in partner_bert.signup_url, 'signup url is incorrect')
self.assertTrue(cr.dbname in partner_bert.signup_url, 'signup url is incorrect')
self.assertTrue(partner_bert.signup_token in partner_bert.signup_url, 'signup url is incorrect')
partner_carine = self.res_partner.browse(cr, uid, partner_carine_id)
self.assertTrue(partner_carine.signup_valid, 'partner has not been prepared for signup')
self.assertTrue(base_url in partner_carine.signup_url, 'signup url is incorrect')
self.assertTrue(cr.dbname in partner_carine.signup_url, 'signup url is incorrect')
self.assertTrue(partner_carine.signup_token in partner_carine.signup_url, 'signup url is incorrect')
# Test: (pretend to) send email and check subject, body
self.assertEqual(len(self._build_email_kwargs_list), 1, 'sent email number incorrect, should be only for Bert')
@ -118,5 +128,5 @@ class test_portal(test_mail_mockup.TestMailMockups):
'subject of invitation email is incorrect')
self.assertTrue('You have been invited to follow Pigs' in sent_email.get('body'),
'body of invitation email is incorrect')
self.assertTrue(partner_bert.signup_url in sent_email.get('body'),
self.assertTrue(partner_carine.signup_url in sent_email.get('body'),
'body of invitation email does not contain signup url')

View File

@ -96,14 +96,16 @@ class crm_contact_us(osv.TransientModel):
Therefore, user SUPERUSER_ID will perform the creation.
"""
values['contact_name'] = values['partner_name']
crm_lead.create(cr, SUPERUSER_ID, dict(values,user_id=False), context)
crm_lead.create(cr, SUPERUSER_ID, dict(values, user_id=False), context)
"""
Create an empty record in the contact table.
Since the 'name' field is mandatory, give an empty string to avoid an integrity error.
Pass mail_nosubscribe key in context because otherwise the inheritance
leads to a message_subscribe_user, that triggers access right issues.
"""
empty_values = dict((k, False) if k != 'name' else (k, '') for k, v in values.iteritems())
return super(crm_contact_us, self).create(cr, uid, empty_values)
return super(crm_contact_us, self).create(cr, uid, empty_values, {'mail_nosubscribe': True})
def submit(self, cr, uid, ids, context=None):
""" When the form is submitted, redirect the user to a "Thanks" message """