Thibault Delavallée (OpenERP) has proposed merging
lp:~openerp-dev/openobject-addons/trunk-mail-invite-tde into
lp:openobject-addons.
Requested reviews:
OpenERP Core Team (openerp)
For more details, see:
https://code.launchpad.net/~openerp-dev/openobject-addons/trunk-mail-invite-tde/+merge/123973
OpenChatter "Invite Yur Mummy" Invite branch
Featuring :
- Invite wizard, to add followers to a document, and to send them an email
- Invite button in Chatter, that calls the Invite wizard
- each send notification is a distinct email, one for each partner to have to
receive a notification. Changes in mail_followers and mail_mail to handle this.
If send receives a notifier_ids parameter, it uses it instead of the
mail_mail.email_to field, and send an email to each partner. Overriding some of
the methods allow to somewhat personnalize the email
- Portal: add the signin url in notifications send to portal users
--
https://code.launchpad.net/~openerp-dev/openobject-addons/trunk-mail-invite-tde/+merge/123973
Your team OpenERP R&D Team is subscribed to branch
lp:~openerp-dev/openobject-addons/trunk-mail-invite-tde.
=== modified file 'mail/__openerp__.py'
--- mail/__openerp__.py 2012-09-06 13:42:01 +0000
+++ mail/__openerp__.py 2012-09-12 14:13:35 +0000
@@ -61,6 +61,7 @@
'website': 'http://www.openerp.com',
'depends': ['base', 'base_tools', 'base_setup'],
'data': [
+ 'wizard/invite_view.xml',
'wizard/mail_compose_message_view.xml',
'res_config_view.xml',
'mail_message_view.xml',
=== modified file 'mail/mail_followers.py'
--- mail/mail_followers.py 2012-09-05 15:19:50 +0000
+++ mail/mail_followers.py 2012-09-12 14:13:35 +0000
@@ -84,16 +84,44 @@
notif_ids = self.search(cr, uid, [('partner_id', '=', partner_id), ('message_id', '=', msg_id)], context=context)
return self.write(cr, uid, notif_ids, {'read': True}, context=context)
+ def get_partners_to_notify(self, cr, uid, partner_ids, message, context=None):
+ """ Return the list of partners to notify, based on their preferences.
+
+ :param message: browse_record of a mail.message
+ """
+ notify_pids = []
+ for partner in self.pool.get('res.partner').browse(cr, uid, partner_ids, context=context):
+ # Do not send an email to the writer
+ if partner.user_ids and partner.user_ids[0].id == uid:
+ continue
+ # Do not send to partners without email address defined
+ if not partner.email:
+ continue
+ # Partner does not want to receive any emails
+ if partner.notification_email_send == 'none':
+ continue
+ # Partner wants to receive only emails and comments
+ if partner.notification_email_send == 'comment' and message.type not in ('email', 'comment'):
+ continue
+ # Partner wants to receive only emails
+ if partner.notification_email_send == 'email' and message.type != 'email':
+ continue
+ notify_pids.append(partner.id)
+ return notify_pids
+
def notify(self, cr, uid, partner_ids, msg_id, context=None):
""" Send by email the notification depending on the user preferences """
context = context or {}
# mail_noemail (do not send email) or no partner_ids: do not send, return
if context.get('mail_noemail') or not partner_ids:
return True
+ msg = self.pool.get('mail.message').browse(cr, uid, msg_id, context=context)
+
+ notify_partner_ids = self.get_partners_to_notify(cr, uid, partner_ids, msg, context=context)
+ if not notify_partner_ids:
+ return True
mail_mail = self.pool.get('mail.mail')
- msg = self.pool.get('mail.message').browse(cr, uid, msg_id, context=context)
-
# add signature
body_html = msg.body
signature = msg.author_id and msg.author_id.user_ids[0].signature or ''
@@ -107,27 +135,6 @@
'body_html': body_html,
'state': 'outgoing',
}
-
- for partner in self.pool.get('res.partner').browse(cr, uid, partner_ids, context=context):
- # Do not send an email to the writer
- if partner.user_ids and partner.user_ids[0].id == uid:
- continue
- # Do not send to partners without email address defined
- if not partner.email:
- continue
- # Partner does not want to receive any emails
- if partner.notification_email_send == 'none':
- continue
- # Partner wants to receive only emails and comments
- if partner.notification_email_send == 'comment' and msg.type not in ('email', 'comment'):
- continue
- # Partner wants to receive only emails
- if partner.notification_email_send == 'email' and msg.type != 'email':
- continue
- if partner.email not in mail_values['email_to']:
- mail_values['email_to'].append(partner.email)
- if mail_values['email_to']:
- mail_values['email_to'] = ', '.join(mail_values['email_to'])
- email_notif_id = mail_mail.create(cr, uid, mail_values, context=context)
- mail_mail.send(cr, uid, [email_notif_id], context=context)
- return True
+ mail_values['email_to'] = ', '.join(mail_values['email_to'])
+ email_notif_id = mail_mail.create(cr, uid, mail_values, context=context)
+ return mail_mail.send(cr, uid, [email_notif_id], notifier_ids=notify_partner_ids, context=context)
=== modified file 'mail/mail_mail.py'
--- mail/mail_mail.py 2012-09-05 15:19:50 +0000
+++ mail/mail_mail.py 2012-09-12 14:13:35 +0000
@@ -30,6 +30,7 @@
_logger = logging.getLogger(__name__)
+
class mail_mail(osv.Model):
""" Model holding RFC2822 email messages to send. This model also provides
facilities to queue and send new email messages. """
@@ -58,8 +59,8 @@
'body_html': fields.text('Rich-text Contents', help="Rich-text/HTML message"),
# Auto-detected based on create() - if 'mail_message_id' was passed then this mail is a notification
- # and during unlink() we will cascade delete the parent and its attachments
- 'notification': fields.boolean('Is Notification')
+ # and during unlink() we will not cascade delete the parent and its attachments
+ 'notification': fields.boolean('Is Notification')
}
def _get_default_from(self, cr, uid, context=None):
@@ -76,21 +77,21 @@
def create(self, cr, uid, values, context=None):
if 'notification' not in values and values.get('mail_message_id'):
values['notification'] = True
- return super(mail_mail,self).create(cr, uid, values, context=context)
+ return super(mail_mail, self).create(cr, uid, values, context=context)
def unlink(self, cr, uid, ids, context=None):
# cascade-delete the parent message for all mails that are not created for a notification
- ids_to_cascade = self.search(cr, uid, [('notification','=',False),('id','in',ids)])
+ ids_to_cascade = self.search(cr, uid, [('notification', '=', False), ('id', 'in', ids)])
parent_msg_ids = [m.mail_message_id.id for m in self.browse(cr, uid, ids_to_cascade, context=context)]
- res = super(mail_mail,self).unlink(cr, uid, ids, context=context)
+ res = super(mail_mail, self).unlink(cr, uid, ids, context=context)
self.pool.get('mail.message').unlink(cr, uid, parent_msg_ids, context=context)
return res
def mark_outgoing(self, cr, uid, ids, context=None):
- return self.write(cr, uid, ids, {'state':'outgoing'}, context=context)
+ return self.write(cr, uid, ids, {'state': 'outgoing'}, context=context)
def cancel(self, cr, uid, ids, context=None):
- return self.write(cr, uid, ids, {'state':'cancel'}, context=context)
+ return self.write(cr, uid, ids, {'state': 'cancel'}, context=context)
def process_email_queue(self, cr, uid, ids=None, context=None):
"""Send immediately queued messages, committing after each
@@ -127,7 +128,7 @@
"""Perform any post-processing necessary after sending ``mail``
successfully, including deleting it completely along with its
attachment if the ``auto_delete`` flag of the mail was set.
- Overridden by subclasses for extra post-processing behaviors.
+ Overridden by subclasses for extra post-processing behaviors.
:param browse_record mail: the mail that was just sent
:return: True
@@ -136,14 +137,46 @@
mail.unlink()
return True
- def _send_get_mail_subject(self, cr, uid, mail, force=False, context=None):
+ def _send_get_mail_subject(self, cr, uid, mail, force=False, partner=None, context=None):
""" if void and related document: '<Author> posted on <Resource>'
- :param mail: mail.mail browse_record """
+
+ :param force: force the 'Author posted'... subject
+ :param mail: mail.mail browse_record
+ :param partner: browse_record of the specific recipient partner
+ """
if force or (not mail.subject and mail.model and mail.res_id):
return '%s posted on %s' % (mail.author_id.name, mail.record_name)
return mail.subject
- def send(self, cr, uid, ids, auto_commit=False, context=None):
+ def _send_get_mail_body(self, cr, uid, mail, partner=None, context=None):
+ """ Return a specific ir_email body. The main purpose of this method
+ is to be inherited by Portal, to add a link for signing in, in
+ each notification email a partner receives.
+
+ :param mail: mail.mail browse_record
+ :param partner: browse_record of the specific recipient partner
+ """
+ return mail.body_html
+
+ def _send_get_ir_email_dict(self, cr, uid, mail, partner=None, context=None):
+ """ Return a dictionary for specific ir_email values, depending on a
+ partner, or generic to the whole recipients given by mail.email_to.
+
+ :param mail: mail.mail browse_record
+ :param partner: browse_record of the specific recipient partner
+ """
+ body = self._send_get_mail_body(cr, uid, mail, partner=partner, context=context)
+ subject = self._send_get_mail_subject(cr, uid, mail, partner=partner, context=context)
+ body_alternative = tools.html2plaintext(body)
+ email_to = [partner.email] if partner else tools.email_split(mail.email_to)
+ return {
+ 'body': body,
+ 'body_alternative': body_alternative,
+ 'subject': subject,
+ 'email_to': email_to,
+ }
+
+ def send(self, cr, uid, ids, auto_commit=False, notifier_ids=None, context=None):
""" Sends the selected emails immediately, ignoring their current
state (mails that have already been sent should not be passed
unless they should actually be re-sent).
@@ -159,45 +192,46 @@
ir_mail_server = self.pool.get('ir.mail_server')
for mail in self.browse(cr, uid, ids, context=context):
try:
- body = mail.body_html
- subject = self._send_get_mail_subject(cr, uid, mail, context=context)
-
# handle attachments
attachments = []
for attach in mail.attachment_ids:
attachments.append((attach.datas_fname, base64.b64decode(attach.datas)))
-
- # use only sanitized html and set its plaintexted version as alternative
- body_alternative = tools.html2plaintext(body)
- content_subtype_alternative = 'plain'
+ # specific behavior to customize the send email for notified partners
+ ir_email_list = []
+ if notifier_ids:
+ for partner in self.pool.get('res.partner').browse(cr, uid, notifier_ids, context=context):
+ ir_email_list.append(self._send_get_ir_email_dict(cr, uid, mail, partner=partner, context=context))
+ else:
+ ir_email_list.append(self._send_get_ir_email_dict(cr, uid, mail, context=context))
# build an RFC2822 email.message.Message object and send it without queuing
- msg = ir_mail_server.build_email(
- email_from = mail.email_from,
- email_to = tools.email_split(mail.email_to),
- subject = subject,
- body = body,
- body_alternative = body_alternative,
- email_cc = tools.email_split(mail.email_cc),
- reply_to = mail.reply_to,
- attachments = attachments,
- message_id = mail.message_id,
- references = mail.references,
- object_id = mail.res_id and ('%s-%s' % (mail.res_id, mail.model)),
- subtype = 'html',
- subtype_alternative = content_subtype_alternative)
- res = ir_mail_server.send_email(cr, uid, msg,
- mail_server_id=mail.mail_server_id.id, context=context)
+ for ir_email in ir_email_list:
+ msg = ir_mail_server.build_email(
+ email_from = mail.email_from,
+ email_to = ir_email.get('email_to'),
+ subject = ir_email.get('subject'),
+ body = ir_email.get('body'),
+ body_alternative = ir_email.get('body_alternative'),
+ email_cc = tools.email_split(mail.email_cc),
+ reply_to = mail.reply_to,
+ attachments = attachments,
+ message_id = mail.message_id,
+ references = mail.references,
+ object_id = mail.res_id and ('%s-%s' % (mail.res_id, mail.model)),
+ subtype = 'html',
+ subtype_alternative = 'plain')
+ res = ir_mail_server.send_email(cr, uid, msg,
+ mail_server_id=mail.mail_server_id.id, context=context)
if res:
- mail.write({'state':'sent', 'message_id': res})
+ mail.write({'state': 'sent', 'message_id': res})
else:
- mail.write({'state':'exception'})
+ mail.write({'state': 'exception'})
mail.refresh()
if mail.state == 'sent':
self._postprocess_sent_message(cr, uid, mail, context=context)
except Exception:
_logger.exception('failed sending mail.mail %s', mail.id)
- mail.write({'state':'exception'})
+ mail.write({'state': 'exception'})
if auto_commit == True:
cr.commit()
=== modified file 'mail/mail_message.py'
--- mail/mail_message.py 2012-09-07 11:54:22 +0000
+++ mail/mail_message.py 2012-09-12 14:13:35 +0000
@@ -25,6 +25,7 @@
from email.header import decode_header
from operator import itemgetter
from osv import osv, fields
+from tools.translate import _
_logger = logging.getLogger(__name__)
@@ -341,3 +342,26 @@
default = {}
default.update(message_id=False, headers=False)
return super(mail_message, self).copy(cr, uid, id, default=default, context=context)
+
+ #------------------------------------------------------
+ # Tools
+ #------------------------------------------------------
+
+ def verify_partner_email(self, cr, uid, partner_ids, context=None):
+ """ Verify that selected partner_ids have an email_address defined.
+ Otherwise throw a warning. """
+ partner_wo_email_lst = []
+ for partner in self.pool.get('res.partner').browse(cr, uid, partner_ids, context=context):
+ if not partner.email:
+ partner_wo_email_lst.append(partner)
+ if not partner_wo_email_lst:
+ return {}
+ warning_msg = _('The following partners chosen as recipients for the email have no email address linked :')
+ for partner in partner_wo_email_lst:
+ warning_msg += '\n- %s' % (partner.name)
+ return {'warning': {
+ 'title': _('Partners email addresses not found'),
+ 'message': warning_msg,
+ }
+ }
+
=== modified file 'mail/mail_thread.py'
--- mail/mail_thread.py 2012-09-05 16:01:45 +0000
+++ mail/mail_thread.py 2012-09-12 14:13:35 +0000
@@ -623,14 +623,8 @@
return self.message_subscribe(cr, uid, ids, partner_ids, context=context)
def message_subscribe(self, cr, uid, ids, partner_ids, context=None):
- """ Add partners to the records followers.
- :param partner_ids: a list of partner_ids to subscribe
- :param return: new value of followers if read_back key in context
- """
- self.write(cr, uid, ids, {'message_follower_ids': [(4, pid) for pid in partner_ids]}, context=context)
- if context and context.get('read_back'):
- return [follower.id for thread in self.browse(cr, uid, ids, context=context) for follower in thread.message_follower_ids]
- return []
+ """ Add partners to the records followers. """
+ return self.write(cr, uid, ids, {'message_follower_ids': [(4, pid) for pid in partner_ids]}, context=context)
def message_unsubscribe_users(self, cr, uid, ids, user_ids=None, context=None):
""" Wrapper on message_subscribe, using users. If user_ids is not
@@ -640,14 +634,8 @@
return self.message_unsubscribe(cr, uid, ids, partner_ids, context=context)
def message_unsubscribe(self, cr, uid, ids, partner_ids, context=None):
- """ Remove partners from the records followers.
- :param partner_ids: a list of partner_ids to unsubscribe
- :param return: new value of followers if read_back key in context
- """
- self.write(cr, uid, ids, {'message_follower_ids': [(3, pid) for pid in partner_ids]}, context=context)
- if context and context.get('read_back'):
- return [follower.id for thread in self.browse(cr, uid, ids, context=context) for follower in thread.message_follower_ids]
- return []
+ """ Remove partners from the records followers. """
+ return self.write(cr, uid, ids, {'message_follower_ids': [(3, pid) for pid in partner_ids]}, context=context)
#------------------------------------------------------
# Thread state
=== modified file 'mail/static/src/css/mail.css'
--- mail/static/src/css/mail.css 2012-09-12 14:02:24 +0000
+++ mail/static/src/css/mail.css 2012-09-12 14:13:35 +0000
@@ -96,10 +96,6 @@
width: 120px;
}
-.openerp button.oe_mail_button_followers {
- display: inline;
-}
-
.openerp button.oe_mail_button_mouseout {
color: white;
background-color: #8a89ba;
=== modified file 'mail/static/src/js/mail_followers.js'
--- mail/static/src/js/mail_followers.js 2012-09-06 11:14:44 +0000
+++ mail/static/src/js/mail_followers.js 2012-09-12 14:13:35 +0000
@@ -31,17 +31,9 @@
},
start: function() {
- var self = this;
- // NB: all the widget should be modified to check the actual_mode property on view, not use
- // any other method to know if the view is in create mode anymore
+ // use actual_mode property on view to know if the view is in create mode anymore
this.view.on("change:actual_mode", this, this._check_visibility);
this._check_visibility();
- this.$el.find('button.oe_mail_button_follow').click(function () { self.do_follow(); })
- .mouseover(function () { $(this).html('Follow').removeClass('oe_mail_button_mouseout').addClass('oe_mail_button_mouseover'); })
- .mouseleave(function () { $(this).html('Not following').removeClass('oe_mail_button_mouseover').addClass('oe_mail_button_mouseout'); });
- this.$el.find('button.oe_mail_button_unfollow').click(function () { self.do_unfollow(); })
- .mouseover(function () { $(this).html('Unfollow').removeClass('oe_mail_button_mouseout').addClass('oe_mail_button_mouseover'); })
- .mouseleave(function () { $(this).html('Following').removeClass('oe_mail_button_mouseover').addClass('oe_mail_button_mouseout'); });
this.reinit();
},
@@ -49,15 +41,41 @@
this.$el.toggle(this.view.get("actual_mode") !== "create");
},
- destroy: function () {
- this._super.apply(this, arguments);
- },
-
reinit: function() {
this.$el.find('button.oe_mail_button_follow').hide();
this.$el.find('button.oe_mail_button_unfollow').hide();
},
+ bind_events: function() {
+ var self = this;
+ this.$el.find('button.oe_mail_button_follow').on('click', function () { self.do_follow(); })
+ this.$el.find('button.oe_mail_button_unfollow').on('click', function () { self.do_unfollow(); })
+ .mouseover(function () { $(this).html('Unfollow').removeClass('oe_mail_button_mouseout').addClass('oe_mail_button_mouseover'); })
+ .mouseleave(function () { $(this).html('Following').removeClass('oe_mail_button_mouseover').addClass('oe_mail_button_mouseout'); });
+ this.$el.find('button.oe_mail_button_invite').on('click', function(event) {
+ action = {
+ type: 'ir.actions.act_window',
+ res_model: 'mail.wizard.invite',
+ view_mode: 'form',
+ view_type: 'form',
+ views: [[false, 'form']],
+ target: 'new',
+ context: {
+ 'default_res_model': self.view.dataset.model,
+ 'default_res_id': self.view.datarecord.id
+ },
+ }
+ self.do_action(action, function() { self.read_value(); });
+ });
+ },
+
+ read_value: function() {
+ var self = this;
+ return this.ds_model.read_ids([this.view.datarecord.id], ['message_follower_ids']).pipe(function (results) {
+ return results[0].message_follower_ids;
+ }).pipe(this.proxy('set_value'));
+ },
+
set_value: function(value_) {
this.reinit();
if (! this.view.datarecord.id ||
@@ -65,11 +83,12 @@
this.$el.find('div.oe_mail_recthread_aside').hide();
return;
}
- return this.fetch_followers(value_);
+ this.bind_events();
+ return this.fetch_followers(value_ || this.get_value());
},
fetch_followers: function (value_) {
- return this.ds_follow.call('read', [value_ || this.get_value(), ['name', 'user_ids']]).then(this.proxy('display_followers'));
+ return this.ds_follow.call('read', [value_, ['name', 'user_ids']]).pipe(this.proxy('display_followers'));
},
/** Display the followers, evaluate is_follower directly */
@@ -91,13 +110,13 @@
},
do_follow: function () {
- var context = new session.web.CompoundContext(this.build_context(), {'read_back': true});
- return this.ds_model.call('message_subscribe_users', [[this.view.datarecord.id], undefined, context]).pipe(this.proxy('set_value'));
+ var context = new session.web.CompoundContext(this.build_context(), {});
+ return this.ds_model.call('message_subscribe_users', [[this.view.datarecord.id], undefined, context]).pipe(this.proxy('read_value'));
},
do_unfollow: function () {
- var context = new session.web.CompoundContext(this.build_context(), {'read_back': true});
- return this.ds_model.call('message_unsubscribe_users', [[this.view.datarecord.id], undefined, context]).pipe(this.proxy('set_value'));
+ var context = new session.web.CompoundContext(this.build_context(), {});
+ return this.ds_model.call('message_unsubscribe_users', [[this.view.datarecord.id], undefined, context]).pipe(this.proxy('read_value'));
},
});
};
=== modified file 'mail/static/src/xml/mail_followers.xml'
--- mail/static/src/xml/mail_followers.xml 2012-09-05 16:31:35 +0000
+++ mail/static/src/xml/mail_followers.xml 2012-09-12 14:13:35 +0000
@@ -7,7 +7,8 @@
-->
<div t-name="mail.followers" class="oe_mail_recthread_aside oe_semantic_html_override">
<div class="oe_mail_recthread_actions">
- <button type="button" class="oe_mail_button_follow oe_mail_button_mouseout">Not following</button>
+ <button type="button" class="oe_mail_button_invite">Invite</button>
+ <button type="button" class="oe_mail_button_follow">Follow</button>
<button type="button" class="oe_mail_button_unfollow oe_mail_button_mouseout">Following</button>
</div>
<div class="oe_mail_recthread_followers">
=== modified file 'mail/tests/test_mail.py'
--- mail/tests/test_mail.py 2012-09-12 10:21:11 +0000
+++ mail/tests/test_mail.py 2012-09-12 14:13:35 +0000
@@ -19,6 +19,8 @@
#
##############################################################################
+import tools
+
from openerp.tests import common
from openerp.tools.html_sanitize import html_sanitize
@@ -88,10 +90,19 @@
return True
def _mock_build_email(self, *args, **kwargs):
- self._build_email_args = args
- self._build_email_kwargs = kwargs
+ self._build_email_args_list.append(args)
+ self._build_email_kwargs_list.append(kwargs)
return self.build_email_real(*args, **kwargs)
+ def _init_mock_build_email(self):
+ self._build_email_args_list = []
+ self._build_email_kwargs_list = []
+
+ 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')
+ return body
+
def setUp(self):
super(test_mail, self).setUp()
self.ir_model = self.registry('ir.model')
@@ -106,10 +117,14 @@
self.res_partner = self.registry('res.partner')
# Install mock SMTP gateway
+ self._init_mock_build_email()
self.build_email_real = self.registry('ir.mail_server').build_email
self.registry('ir.mail_server').build_email = self._mock_build_email
self.registry('ir.mail_server').send_email = self._mock_smtp_gateway
+ # Mock _send_get_mail_body to test its functionality without other addons override
+ 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(self.cr, self.uid, [('model', '=', 'mail.group')])[0]
self.mail_alias.create(self.cr, self.uid, {'alias_name': 'groups',
@@ -274,18 +289,20 @@
_attachments = [('First', 'My first attachment'), ('Second', 'My second attachment')]
# CASE1: post comment, body and subject specified
+ self._init_mock_build_email()
msg_id = self.mail_group.message_post(cr, uid, self.group_pigs_id, body=_body1, subject=_subject, type='comment')
message = self.mail_message.browse(cr, uid, msg_id)
- sent_email = self._build_email_kwargs
+ sent_emails = self._build_email_kwargs_list
# Test: 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!')
# 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')
- # Test: sent_email: email send by server: correct subject, body; body_alternative
- self.assertEqual(sent_email['subject'], _subject, 'sent_email subject incorrect')
- self.assertEqual(sent_email['body'], _mail_body1, 'sent_email body incorrect')
- self.assertEqual(sent_email['body_alternative'], _mail_bodyalt1, 'sent_email body_alternative is incorrect')
+ # Test: sent_email: email send by server: correct subject, body, body_alternative
+ for sent_email in sent_emails:
+ self.assertEqual(sent_email['subject'], _subject, 'sent_email subject incorrect')
+ self.assertEqual(sent_email['body'], _mail_body1 + '\n<pre>Bert Tartopoils</pre>\n', 'sent_email body incorrect')
+ self.assertEqual(sent_email['body_alternative'], _mail_bodyalt1 + '\nBert Tartopoils', 'sent_email body_alternative is incorrect')
# Test: mail_message: partner_ids = group followers
message_pids = set([partner.id for partner in message.partner_ids])
test_pids = set([p_a_id, p_b_id, p_c_id])
@@ -295,14 +312,16 @@
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)
- self.assertEqual(sent_email['email_to'], ['b@b'], 'sent_email email_to is incorrect')
+ for sent_email in sent_emails:
+ self.assertEqual(sent_email['email_to'], ['b@b'], 'sent_email email_to is incorrect')
# CASE2: post an email with attachments, parent_id, partner_ids
# TESTS: automatic subject, signature in body_html, attachments propagation
+ self._init_mock_build_email()
msg_id2 = self.mail_group.message_post(cr, uid, self.group_pigs_id, body=_body2, type='email',
partner_ids=[(6, 0, [p_d_id])], parent_id=msg_id, attachments=_attachments)
message = self.mail_message.browse(cr, uid, msg_id2)
- sent_email = self._build_email_kwargs
+ 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!')
# Test: mail_message: subject is False, body is _body2 (no formatting done), parent_id is msg_id
@@ -310,9 +329,11 @@
self.assertEqual(message.body, html_sanitize(_body2), 'mail.message body incorrect')
self.assertEqual(message.parent_id.id, msg_id, 'mail.message parent_id incorrect')
# Test: sent_email: email send by server: correct subject, body, body_alternative
- self.assertEqual(sent_email['subject'], _mail_subject, 'sent_email subject incorrect')
- self.assertEqual(sent_email['body'], _mail_body2, 'sent_email body incorrect')
- self.assertEqual(sent_email['body_alternative'], _mail_bodyalt2, 'sent_email body_alternative incorrect')
+ self.assertEqual(len(sent_emails), 2, '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: partner_ids = group followers
message_pids = set([partner.id for partner in message.partner_ids])
test_pids = set([p_a_id, p_b_id, p_c_id, p_d_id])
@@ -322,7 +343,8 @@
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)
- self.assertEqual(set(sent_email['email_to']), set(['b@b', 'c@c']), 'sent_email email_to incorrect')
+ for sent_email in sent_emails:
+ self.assertTrue(set(sent_email['email_to']).issubset(set(['b@b', 'c@c'])), 'sent_email email_to incorrect')
# Test: attachments
for attach in message.attachment_ids:
self.assertEqual(attach.res_model, 'mail.group', 'mail.message attachment res_model incorrect')
=== modified file 'mail/wizard/__init__.py'
--- mail/wizard/__init__.py 2012-07-03 17:11:30 +0000
+++ mail/wizard/__init__.py 2012-09-12 14:13:35 +0000
@@ -19,6 +19,7 @@
#
##############################################################################
+import invite
import mail_compose_message
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
=== added file 'mail/wizard/invite.py'
--- mail/wizard/invite.py 1970-01-01 00:00:00 +0000
+++ mail/wizard/invite.py 2012-09-12 14:13:35 +0000
@@ -0,0 +1,56 @@
+# -*- 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 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>
+#
+##############################################################################
+
+from osv import osv
+from osv import fields
+
+
+class invite_wizard(osv.osv_memory):
+ """ Wizard to invite partners and make them followers. """
+ _name = 'mail.wizard.invite'
+ _description = 'Invite wizard'
+
+ _columns = {
+ 'res_model': fields.char('Related Document Model', size=128,
+ required=True, select=1,
+ help='Model of the followed resource'),
+ 'res_id': fields.integer('Related Document ID', select=1,
+ help='Id of the followed resource'),
+ 'partner_ids': fields.many2many('res.partner', string='Partners'),
+ 'message': fields.text('Message'),
+ }
+
+ def onchange_partner_ids(self, cr, uid, ids, value, context=None):
+ """ onchange_partner_ids (value format: [[6, 0, [3, 4]]]). The
+ basic purpose of this method is to check that destination partners
+ effectively have email addresses. Otherwise a warning is thrown.
+ """
+ res = {'value': {}}
+ if not value or not value[0] or not value[0][0] == 6:
+ return
+ res.update(self.pool.get('mail.message').verify_partner_email(cr, uid, value[0][2], context=context))
+ return res
+
+ def add_followers(self, cr, uid, ids, context=None):
+ for wizard in self.browse(cr, uid, ids, context=context):
+ model_obj = self.pool.get(wizard.res_model)
+ model_obj.message_subscribe(cr, uid, [wizard.res_id], [p.id for p in wizard.partner_ids], context=context)
+ return {'type': 'ir.actions.act_window_close'}
=== added file 'mail/wizard/invite_view.xml'
--- mail/wizard/invite_view.xml 1970-01-01 00:00:00 +0000
+++ mail/wizard/invite_view.xml 2012-09-12 14:13:35 +0000
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data>
+
+ <!-- wizard view -->
+ <record model="ir.ui.view" id="mail_wizard_invite_form">
+ <field name="name">Add Followers</field>
+ <field name="model">mail.wizard.invite</field>
+ <field name="arch" type="xml">
+ <form string="Add Followers" version="7.0">
+ <group>
+ <field name="res_model" invisible="1"/>
+ <field name="res_id" invisible="1"/>
+ <field name="partner_ids" widget="many2many_tags"
+ on_change="onchange_partner_ids(partner_ids)" />
+ <field name="message"/>
+ </group>
+ <footer>
+ <button string="Add Followers"
+ name="add_followers" type="object" class="oe_highlight" />
+ or
+ <button string="Cancel" class="oe_link" special="cancel" />
+ </footer>
+ </form>
+ </field>
+ </record>
+
+ </data>
+</openerp>
=== modified file 'mail/wizard/mail_compose_message.py'
--- mail/wizard/mail_compose_message.py 2012-09-12 10:21:11 +0000
+++ mail/wizard/mail_compose_message.py 2012-09-12 14:13:35 +0000
@@ -193,24 +193,6 @@
"""
return {'value': {'content_subtype': value}}
- def _verify_partner_email(self, cr, uid, partner_ids, context=None):
- """ Verify that selected partner_ids have an email_address defined.
- Otherwise throw a warning. """
- partner_wo_email_lst = []
- for partner in self.pool.get('res.partner').browse(cr, uid, partner_ids, context=context):
- if not partner.email:
- partner_wo_email_lst.append(partner)
- if not partner_wo_email_lst:
- return {}
- warning_msg = _('The following partners chosen as recipients for the email have no email address linked :')
- for partner in partner_wo_email_lst:
- warning_msg += '\n- %s' % (partner.name)
- return {'warning': {
- 'title': _('Partners email addresses not found'),
- 'message': warning_msg,
- }
- }
-
def onchange_partner_ids(self, cr, uid, ids, value, context=None):
""" The basic purpose of this method is to check that destination partners
effectively have email addresses. Otherwise a warning is thrown.
@@ -219,7 +201,7 @@
res = {'value': {}}
if not value or not value[0] or not value[0][0] == 6:
return
- res.update(self._verify_partner_email(cr, uid, value[0][2], context=context))
+ res.update(self.verify_partner_email(cr, uid, value[0][2], context=context))
return res
def dummy(self, cr, uid, ids, context=None):
_______________________________________________
Mailing list: https://launchpad.net/~openerp-dev-gtk
Post to : [email protected]
Unsubscribe : https://launchpad.net/~openerp-dev-gtk
More help : https://help.launchpad.net/ListHelp