[MERGE]merge with parent branch

bzr revid: sgo@tinyerp.com-20120914053347-u28fihi7mcetup4j
This commit is contained in:
Sanjay Gohel (Open ERP)
2012-09-14 11:03:47 +05:30
10 changed files with 147 additions and 86 deletions
+9 -1
View File
@@ -27,13 +27,15 @@ import time
import tools
from tools.translate import _
from base.res.res_partner import format_address
CRM_LEAD_PENDING_STATES = (
crm.AVAILABLE_STATES[2][0], # Cancelled
crm.AVAILABLE_STATES[3][0], # Done
crm.AVAILABLE_STATES[4][0], # Pending
)
class crm_lead(base_stage, osv.osv):
class crm_lead(base_stage, format_address, osv.osv):
""" CRM Lead Case """
_name = "crm.lead"
_description = "Lead/Opportunity"
@@ -105,6 +107,12 @@ class crm_lead(base_stage, osv.osv):
return result, fold
def fields_view_get(self, cr, user, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
res = super(crm_lead,self).fields_view_get(cr, user, view_id, view_type, context, toolbar=toolbar, submenu=submenu)
if view_type == 'form':
res['arch'] = self.fields_view_get_address(cr, user, res['arch'], context=context)
return res
_group_by_full = {
'stage_id': _read_group_stage_ids
}
+1
View File
@@ -61,6 +61,7 @@ The main features of the module are:
'website': 'http://www.openerp.com',
'depends': ['base', 'base_tools', 'base_setup'],
'data': [
'wizard/invite_view.xml',
'wizard/mail_compose_message_view.xml',
'mail_message_subtype.xml',
'res_config_view.xml',
+29 -1
View File
@@ -20,11 +20,13 @@
##############################################################################
import logging
import openerp
import tools
from email.header import decode_header
from operator import itemgetter
from osv import osv, fields
from tools.translate import _
_logger = logging.getLogger(__name__)
@@ -35,6 +37,7 @@ def decode(text):
text = decode_header(text.replace('\r', ''))
return ''.join([tools.ustr(x[0], x[1]) for x in text])
class mail_message(osv.Model):
""" Messages model: system notification (replacing res.log notifications),
comments (OpenChatter discussion) and incoming emails. """
@@ -57,7 +60,10 @@ class mail_message(osv.Model):
for message in self.browse(cr, uid, ids, context=context):
if not message.model or not message.res_id:
continue
result[message.id] = self._shorten_name(self.pool.get(message.model).name_get(cr, uid, [message.res_id], context=context)[0][1])
try:
result[message.id] = self._shorten_name(self.pool.get(message.model).name_get(cr, uid, [message.res_id], context=context)[0][1])
except openerp.exceptions.AccessDenied, e:
pass
return result
def _get_unread(self, cr, uid, ids, name, arg, context=None):
@@ -351,3 +357,25 @@ class mail_message(osv.Model):
default = {}
default.update(message_id=False, headers=False)
return super(mail_message, self).copy(cr, uid, id, default=default, context=context)
#------------------------------------------------------
# Tools
#------------------------------------------------------
def check_partners_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,
}
}
+5 -17
View File
@@ -629,20 +629,14 @@ class mail_thread(osv.AbstractModel):
partner_ids = [user.partner_id.id for user in self.pool.get('res.users').browse(cr, uid, user_ids, context=context)]
return self.message_subscribe(cr, uid, ids, partner_ids, context=context)
def message_subscribe(self, cr, uid, ids, partner_ids,subtype_ids = None, 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)
def message_subscribe(self, cr, uid, ids, partner_ids, context=None):
""" Add partners to the records followers. """
if not subtype_ids:
subtype_obj = self.pool.get('mail.message.subtype')
subtype_ids = subtype_obj.search(cr, uid, [('default', '=', 'true'),('res_model', '=', self._name)],context=context)
if subtype_ids:
self.message_subscribe_udpate_subtypes(cr, uid, ids, partner_ids, subtype_ids, context=context)
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 []
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
@@ -652,14 +646,8 @@ class mail_thread(osv.AbstractModel):
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
-4
View File
@@ -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;
+39 -21
View File
@@ -33,20 +33,12 @@ openerp_mail_followers = function(session, mail) {
},
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.fetch_subtype();
this.$el.find('ul.oe_mail_recthread_subtype').click(function () {self.update_subtype();})
this.$el.find('button.oe_mail_button_follow').click(function () { self.do_follow(); self.fetch_subtype();})
.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();
this.bind_events();
},
_check_visibility: function() {
@@ -54,14 +46,40 @@ openerp_mail_followers = function(session, mail) {
if (this.view.get("actual_mode") !== "create"){this.fetch_subtype();}
},
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();
// this.$el.find('ul.oe_mail_recthread_subtype').hide()
},
bind_events: function() {
var self = this;
this.$('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.on('click', 'button.oe_mail_button_follow', function () { self.do_follow(); self.fetch_subtype(); });
this.$el.on('click','ul.oe_mail_recthread_subtype', function () {self.update_subtype();})
this.$el.on('click', 'button.oe_mail_button_invite', 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_) {
@@ -71,11 +89,11 @@ openerp_mail_followers = function(session, mail) {
this.$el.find('div.oe_mail_recthread_aside').hide();
return;
}
return this.fetch_followers(value_);
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 */
@@ -131,8 +149,8 @@ openerp_mail_followers = function(session, mail) {
},
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'));
},
//fetch subtype from subtype model
@@ -145,8 +163,8 @@ openerp_mail_followers = function(session, mail) {
},
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'));
},
});
};
+3 -3
View File
@@ -114,7 +114,7 @@
<!-- dropdown menu with message options and actions -->
<span class="oe_dropdown_toggle oe_dropdown_arrow">
<ul class="oe_dropdown_menu">
<li t-if="record.is_author &amp; options.show_dd_delete"><a class="oe_mail_msg_delete" t-attf-data-id='{record.id}'>Delete</a></li>
<li t-if="record.is_author and options.show_dd_delete"><a class="oe_mail_msg_delete" t-attf-data-id='{record.id}'>Delete</a></li>
<li t-if="options.show_dd_hide"><a class="oe_mail_msg_hide" t-attf-data-id='{record.id}'>Remove notification</a></li>
<!-- Uncomment when adding subtype hiding
<li t-if="display['show_hide']">
@@ -130,14 +130,14 @@
<t t-raw="record.subject"/>
</h1>
<div class="oe_mail_msg_body">
<t t-if="options.show_record_name &amp; (!record.subject) &amp; (options.thread_level > 0)">
<t t-if="options.show_record_name and record.record_name and (!record.subject) and (options.thread_level > 0)">
<a t-attf-href="#model=#{record.model}&amp;id=#{record.res_id}"><t t-raw="record.record_name"/></a>
</t>
<t t-raw="record.body"/>
</div>
<div class="oe_clear"/>
<ul class="oe_mail_msg_footer">
<li t-if="options.show_record_name &amp; record.subject &amp; options.thread_level > 0">
<li t-if="options.show_record_name and record.record_name and record.subject and options.thread_level > 0">
<a t-attf-href="#model=#{record.model}&amp;id=#{record.res_id}"><t t-raw="record.record_name"/></a>
</li>
<li><a t-attf-href="#model=res.partner&amp;id=#{record.author_id[0]}"><t t-raw="record.author_id[1]"/></a></li>
+59 -19
View File
@@ -19,6 +19,8 @@
#
##############################################################################
import tools
from openerp.tests import common
from openerp.tools.html_sanitize import html_sanitize
@@ -82,15 +84,42 @@ Sylvie
"""
class test_mail(common.TransactionCase):
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):
self._build_email_args = args
self._build_email_kwargs = kwargs
return self.build_email_real(*args, **kwargs)
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()
class test_mail(TestMailMockups):
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()
@@ -106,10 +135,9 @@ class test_mail(common.TransactionCase):
self.res_users = self.registry('res.users')
self.res_partner = self.registry('res.partner')
# Install mock SMTP gateway
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._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(self.cr, self.uid, [('model', '=', 'mail.group')])[0]
@@ -119,6 +147,11 @@ class test_mail(common.TransactionCase):
self.group_pigs_id = self.mail_group.create(self.cr, self.uid,
{'name': 'Pigs', 'description': 'Fans of Pigs, unite !'})
def tearDown(self):
# Remove mocks
self.registry('mail.mail').send_get_mail_body = self._send_get_mail_body
super(test_mail, self).tearDown()
def test_00_message_process(self):
cr, uid = self.cr, self.uid
# Incoming mail creates a new mail_group "frogs"
@@ -275,18 +308,20 @@ class test_mail(common.TransactionCase):
_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])
@@ -296,14 +331,16 @@ class test_mail(common.TransactionCase):
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
@@ -311,9 +348,11 @@ class test_mail(common.TransactionCase):
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])
@@ -323,7 +362,8 @@ class test_mail(common.TransactionCase):
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')
+1 -19
View File
@@ -193,24 +193,6 @@ class mail_compose_message(osv.TransientModel):
"""
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 @@ class mail_compose_message(osv.TransientModel):
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.check_partners_email(cr, uid, value[0][2], context=context))
return res
def dummy(self, cr, uid, ids, context=None):
@@ -30,7 +30,7 @@ class sale_advance_payment_inv(osv.osv_memory):
'advance_payment_method':fields.selection(
[('all', 'Invoice the whole sale order'), ('percentage','Percentage'), ('fixed','Fixed price (deposit)'),
('lines', 'Some order lines')],
'Invoice Method', required=True,
'What do you want to invoice?', required=True,
help="""Use All to create the final invoice.
Use Percentage to invoice a percentage of the total amount.
Use Fixed Price to invoice a specific amound in advance.