[MERGE] chm - improved subtype - fixes

bzr revid: fp@openerp.com-20121002204023-8v3jf2n84xe2jol9
This commit is contained in:
Fabien Pinckaers 2012-10-02 22:40:23 +02:00
commit c93d70e919
84 changed files with 2066 additions and 883 deletions

View File

@ -1308,17 +1308,19 @@ class account_invoice(osv.osv):
def create_send_note(self, cr, uid, ids, context=None): def create_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context): for obj in self.browse(cr, uid, ids, context=context):
self.message_post(cr, uid, [obj.id], body=_("%s <b>created</b>.") % (_(self._get_document_type(obj.type))), context=context) self.message_post(cr, uid, [obj.id], body=_("%s <b>created</b>.") % (self._get_document_type(obj.type)),
subtype="account.mt_invoice_new", context=context)
def confirm_paid_send_note(self, cr, uid, ids, context=None): def confirm_paid_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context): for obj in self.browse(cr, uid, ids, context=context):
self.message_post(cr, uid, [obj.id], body=_("%s <b>paid</b>.") % (_(self._get_document_type(obj.type))), context=context) self.message_post(cr, uid, [obj.id], body=_("%s <b>paid</b>.") % (self._get_document_type(obj.type)),
subtype="account.mt_invoice_paid", context=context)
def invoice_cancel_send_note(self, cr, uid, ids, context=None): def invoice_cancel_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context): for obj in self.browse(cr, uid, ids, context=context):
self.message_post(cr, uid, [obj.id], body=_("%s <b>cancelled</b>.") % (_(self._get_document_type(obj.type))), context=context) self.message_post(cr, uid, [obj.id], body=_("%s <b>cancelled</b>.") % (self._get_document_type(obj.type)),
context=context)
account_invoice()
class account_invoice_line(osv.osv): class account_invoice_line(osv.osv):

View File

@ -278,7 +278,6 @@
</notebook> </notebook>
</sheet> </sheet>
<div class="oe_chatter"> <div class="oe_chatter">
<field name="message_is_follower" invisible="1"/>
<field name="message_ids" widget="mail_thread"/> <field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/> <field name="message_follower_ids" widget="mail_followers"/>
</div> </div>
@ -438,7 +437,6 @@
</notebook> </notebook>
</sheet> </sheet>
<div class="oe_chatter"> <div class="oe_chatter">
<field name="message_is_follower" invisible="1"/>
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/> <field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
<field name="message_follower_ids" widget="mail_followers"/> <field name="message_follower_ids" widget="mail_followers"/>
</div> </div>

View File

@ -518,7 +518,6 @@
<!-- <!--
Account Statement Sequences Account Statement Sequences
--> -->
<record id="sequence_reconcile" model="ir.sequence.type"> <record id="sequence_reconcile" model="ir.sequence.type">
<field name="name">Account Reconcile</field> <field name="name">Account Reconcile</field>
<field name="code">account.reconcile</field> <field name="code">account.reconcile</field>
@ -554,8 +553,6 @@
<field eval="1" name="number_next"/> <field eval="1" name="number_next"/>
<field eval="1" name="number_increment"/> <field eval="1" name="number_increment"/>
</record> </record>
<!-- <!--
Invoice requests (deprecated) Invoice requests (deprecated)
--> -->
@ -564,7 +561,14 @@
<field name="object">account.invoice</field> <field name="object">account.invoice</field>
</record> </record>
<!-- mail: subtypes -->
<record id="mt_invoice_new" model="mail.message.subtype">
<field name="name">created</field>
<field name="res_model">account.invoice</field>
</record>
<record id="mt_invoice_paid" model="mail.message.subtype">
<field name="name">paid</field>
<field name="res_model">account.invoice</field>
</record>
</data> </data>
</openerp> </openerp>

View File

@ -1304,17 +1304,17 @@ class account_voucher(osv.osv):
def create_send_note(self, cr, uid, ids, context=None): def create_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context): for obj in self.browse(cr, uid, ids, context=context):
message = "%s <b>created</b>." % self._document_type[obj.type or False] message = "%s <b>created</b>." % self._document_type[obj.type or False]
self.message_post(cr, uid, [obj.id], body=message, context=context) self.message_post(cr, uid, [obj.id], body=message, subtype="account_voucher.mt_voucher", context=context)
def post_send_note(self, cr, uid, ids, context=None): def post_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context): for obj in self.browse(cr, uid, ids, context=context):
message = "%s '%s' is <b>posted</b>." % (self._document_type[obj.type or False], obj.move_id.name) message = "%s '%s' is <b>posted</b>." % (self._document_type[obj.type or False], obj.move_id.name)
self.message_post(cr, uid, [obj.id], body=message, context=context) self.message_post(cr, uid, [obj.id], body=message, subtype="account_voucher.mt_voucher", context=context)
def reconcile_send_note(self, cr, uid, ids, context=None): def reconcile_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context): for obj in self.browse(cr, uid, ids, context=context):
message = "%s <b>reconciled</b>." % self._document_type[obj.type or False] message = "%s <b>reconciled</b>." % self._document_type[obj.type or False]
self.message_post(cr, uid, [obj.id], body=message, context=context) self.message_post(cr, uid, [obj.id], body=message, subtype="account_voucher.mt_voucher", context=context)
account_voucher() account_voucher()

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<openerp> <openerp>
<data noupdate="1"> <data noupdate="1">
<!-- notify all employees of module installation --> <!-- notify all employees of module installation -->
<record model="mail.message" id="module_install_notification"> <record model="mail.message" id="module_install_notification">
<field name="model">mail.group</field> <field name="model">mail.group</field>
@ -13,5 +14,12 @@
<p>You can track customer payments easily and automate follow-ups. You get an overview of the discussion with your customers on each invoice for easier traceability. For advanced accounting features, you should install the "Accounting and Finance" module.</p> <p>You can track customer payments easily and automate follow-ups. You get an overview of the discussion with your customers on each invoice for easier traceability. For advanced accounting features, you should install the "Accounting and Finance" module.</p>
]]></field> ]]></field>
</record> </record>
<!-- mail: subtypes -->
<record id="mt_voucher" model="mail.message.subtype">
<field name="name">Status Change</field>
<field name="res_model">account.voucher</field>
</record>
</data> </data>
</openerp> </openerp>

View File

@ -108,7 +108,6 @@
</notebook> </notebook>
</sheet> </sheet>
<div class="oe_chatter"> <div class="oe_chatter">
<field name="message_is_follower" invisible="1"/>
<field name="message_ids" widget="mail_thread"/> <field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/> <field name="message_follower_ids" widget="mail_followers"/>
</div> </div>

View File

@ -243,7 +243,6 @@
</notebook> </notebook>
</sheet> </sheet>
<div class="oe_chatter"> <div class="oe_chatter">
<field name="message_is_follower" invisible="1"/>
<field name="message_ids" widget="mail_thread"/> <field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/> <field name="message_follower_ids" widget="mail_followers"/>
</div> </div>
@ -519,7 +518,6 @@
</notebook> </notebook>
</sheet> </sheet>
<div class="oe_chatter"> <div class="oe_chatter">
<field name="message_is_follower" invisible="1"/>
<field name="message_ids" widget="mail_thread"/> <field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/> <field name="message_follower_ids" widget="mail_followers"/>
</div> </div>

View File

@ -146,7 +146,6 @@
</notebook> </notebook>
</sheet> </sheet>
<div class="oe_chatter"> <div class="oe_chatter">
<field name="message_is_follower" invisible="1"/>
<field name="message_ids" widget="mail_thread"/> <field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/> <field name="message_follower_ids" widget="mail_followers"/>
</div> </div>
@ -302,7 +301,6 @@
</notebook> </notebook>
</sheet> </sheet>
<div class="oe_chatter"> <div class="oe_chatter">
<field name="message_is_follower" invisible="1"/>
<field name="message_ids" widget="mail_thread"/> <field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/> <field name="message_follower_ids" widget="mail_followers"/>
</div> </div>

View File

@ -38,7 +38,8 @@ that have no counterpart in the general financial accounts.
'security/analytic_security.xml', 'security/analytic_security.xml',
'security/ir.model.access.csv', 'security/ir.model.access.csv',
'analytic_sequence.xml', 'analytic_sequence.xml',
'analytic_view.xml' 'analytic_view.xml',
'analytic_data.xml'
], ],
'demo': [], 'demo': [],
'installable': True, 'installable': True,

View File

@ -284,7 +284,8 @@ class account_analytic_account(osv.osv):
def create_send_note(self, cr, uid, ids, context=None): def create_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context): for obj in self.browse(cr, uid, ids, context=context):
self.message_post(cr, uid, [obj.id], body=_("Contract for <em>%s</em> has been <b>created</b>.") % (obj.partner_id.name), context=context) self.message_post(cr, uid, [obj.id], body=_("Contract for <em>%s</em> has been <b>created</b>.") % (obj.partner_id.name),
subtype="analytic.mt_account_status", context=context)
account_analytic_account() account_analytic_account()

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="1">
<!-- mail: subtypes -->
<record id="mt_account_status" model="mail.message.subtype">
<field name="name">Status Change</field>
<field name="res_model">account.analytic.account</field>
</record>
</data>
</openerp>

View File

@ -53,7 +53,6 @@
</notebook> </notebook>
</sheet> </sheet>
<div class="oe_chatter"> <div class="oe_chatter">
<field name="message_is_follower" invisible="1"/>
<field name="message_ids" widget="mail_thread"/> <field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/> <field name="message_follower_ids" widget="mail_followers"/>
</div> </div>

View File

@ -223,7 +223,6 @@
</notebook> </notebook>
</sheet> </sheet>
<div class="oe_chatter"> <div class="oe_chatter">
<field name="message_is_follower" invisible="1"/>
<field name="message_ids" widget="mail_thread"/> <field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/> <field name="message_follower_ids" widget="mail_followers"/>
</div> </div>

View File

@ -359,6 +359,9 @@ class base_stage(object):
""" """
return '' return ''
def find_subtype(self, cr, uid, ids, name, context=None):
return "mail.mt_comment"
def stage_set_send_note(self, cr, uid, ids, stage_id, context=None): def stage_set_send_note(self, cr, uid, ids, stage_id, context=None):
""" Send a notification when the stage changes. This method has """ Send a notification when the stage changes. This method has
to be overriden, because each document will have its particular to be overriden, because each document will have its particular
@ -370,19 +373,22 @@ class base_stage(object):
def case_open_send_note(self, cr, uid, ids, context=None): def case_open_send_note(self, cr, uid, ids, context=None):
for id in ids: for id in ids:
msg = _('%s has been <b>opened</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context)) msg = _('%s has been <b>opened</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
self.message_post(cr, uid, [id], body=msg, context=context) xml_id = self.find_subtype(cr, uid, ids, context=context)
self.message_post(cr, uid, [id], body=msg, subtype=xml_id, context=context)
return True return True
def case_close_send_note(self, cr, uid, ids, context=None): def case_close_send_note(self, cr, uid, ids, context=None):
for id in ids: for id in ids:
msg = _('%s has been <b>closed</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context)) msg = _('%s has been <b>closed</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
self.message_post(cr, uid, [id], body=msg, context=context) xml_id = self.find_subtype(cr, uid, ids, context=context)
self.message_post(cr, uid, [id], body=msg, subtype=xml_id, context=context)
return True return True
def case_cancel_send_note(self, cr, uid, ids, context=None): def case_cancel_send_note(self, cr, uid, ids, context=None):
for id in ids: for id in ids:
msg = _('%s has been <b>canceled</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context)) msg = _('%s has been <b>cancelled</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
self.message_post(cr, uid, [id], body=msg, context=context) xml_id = self.find_subtype(cr, uid, ids, context=context)
self.message_post(cr, uid, [id], body=msg, subtype=xml_id, context=context)
return True return True
def case_pending_send_note(self, cr, uid, ids, context=None): def case_pending_send_note(self, cr, uid, ids, context=None):

View File

@ -223,7 +223,6 @@
</notebook> </notebook>
</sheet> </sheet>
<div class="oe_chatter"> <div class="oe_chatter">
<field name="message_is_follower" invisible="1"/>
<field name="message_ids" widget="mail_thread"/> <field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/> <field name="message_follower_ids" widget="mail_followers"/>
</div> </div>
@ -526,7 +525,6 @@
</notebook> </notebook>
</sheet> </sheet>
<div class="oe_chatter"> <div class="oe_chatter">
<field name="message_is_follower" invisible="1"/>
<field name="message_ids" widget="mail_thread"/> <field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/> <field name="message_follower_ids" widget="mail_followers"/>
</div> </div>

View File

@ -149,7 +149,6 @@
<field name="description" placeholder="Description..."/> <field name="description" placeholder="Description..."/>
</sheet> </sheet>
<div class="oe_chatter"> <div class="oe_chatter">
<field name="message_is_follower" invisible="1"/>
<field name="message_ids" widget="mail_thread"/> <field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/> <field name="message_follower_ids" widget="mail_followers"/>
</div> </div>

View File

@ -181,7 +181,6 @@
</group> </group>
</sheet> </sheet>
<div class="oe_chatter"> <div class="oe_chatter">
<field name="message_is_follower" invisible="1"/>
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/> <field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
<field name="message_follower_ids" widget="mail_followers"/> <field name="message_follower_ids" widget="mail_followers"/>
</div> </div>

View File

@ -95,7 +95,6 @@
</notebook> </notebook>
</sheet> </sheet>
<div class="oe_chatter"> <div class="oe_chatter">
<field name="message_is_follower" invisible="1"/>
<field name="message_ids" widget="mail_thread"/> <field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/> <field name="message_follower_ids" widget="mail_followers"/>
</div> </div>

View File

@ -167,8 +167,9 @@ class test_message_compose(test_mail.TestMailMockups):
# Test: partner_ids: p_a_id (default) + 3 newly created partners # Test: partner_ids: p_a_id (default) + 3 newly created partners
message_pigs_pids = [partner.id for partner in message_pigs.partner_ids] message_pigs_pids = [partner.id for partner in message_pigs.partner_ids]
message_bird_pids = [partner.id for partner in message_bird.partner_ids] message_bird_pids = [partner.id for partner in message_bird.partner_ids]
partner_ids = self.res_partner.search(cr, uid, [('email', 'in', ['b@b.b', 'c@c.c', 'd@d.d'])]) + [p_a_id] partner_ids = self.res_partner.search(cr, uid, [('email', 'in', ['b@b.b', 'c@c.c', 'd@d.d'])])
self.assertEqual(len(message_pigs_pids), len(partner_ids), 'mail.message on pigs incorrect number of partner_ids') self.assertEqual(len(message_pigs_pids), len(partner_ids), 'mail.message on pigs incorrect number of partner_ids')
self.assertEqual(len(message_bird_pids), len(partner_ids), 'mail.message on bird partner_ids incorrect')
self.assertEqual(set(message_pigs_pids), set(partner_ids), 'mail.message on pigs incorrect number of partner_ids') self.assertEqual(set(message_pigs_pids), set(partner_ids), 'mail.message on pigs incorrect number of partner_ids')
self.assertEqual(len(message_bird_pids), len(partner_ids), 'mail.message on bird partner_ids incorrect')
self.assertEqual(set(message_bird_pids), set(partner_ids), 'mail.message on bird partner_ids incorrect') self.assertEqual(set(message_bird_pids), set(partner_ids), 'mail.message on bird partner_ids incorrect')

View File

@ -45,6 +45,7 @@ Key Features
'security/ir.model.access.csv', 'security/ir.model.access.csv',
'wizard/event_confirm_view.xml', 'wizard/event_confirm_view.xml',
'event_view.xml', 'event_view.xml',
'event_data.xml',
'report/report_event_registration_view.xml', 'report/report_event_registration_view.xml',
'board_association_view.xml', 'board_association_view.xml',
'res_partner_view.xml', 'res_partner_view.xml',

View File

@ -360,8 +360,10 @@ class event_registration(osv.osv):
return self.write(cr, uid, ids, {'state': 'draft'}, context=context) return self.write(cr, uid, ids, {'state': 'draft'}, context=context)
def confirm_registration(self, cr, uid, ids, context=None): def confirm_registration(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, body=_('State set to open'), context=context) for reg in self.browse(cr, uid, ids, context=context or {}):
return self.write(cr, uid, ids, {'state': 'open'}, context=context) self.pool.get('event.event').message_post(cr, uid, [reg.event_id.id], body=_('New registration confirmed: %s.') % (reg.name or '', ),subtype="event.mt_event_registration", context=context)
self.message_post(cr, uid, ids, body=_('Registration confirmed.'), context=context)
return self.write(cr, uid, ids, {'state': 'open'},context=context)
def create(self, cr, uid, vals, context=None): def create(self, cr, uid, vals, context=None):
obj_id = super(event_registration, self).create(cr, uid, vals, context) obj_id = super(event_registration, self).create(cr, uid, vals, context)
@ -392,7 +394,7 @@ class event_registration(osv.osv):
self.write(cr, uid, ids, values) self.write(cr, uid, ids, values)
self.message_post(cr, uid, ids, body=_('State set to Done'), context=context) self.message_post(cr, uid, ids, body=_('State set to Done'), context=context)
else: else:
raise osv.except_osv(_('Error!'),_("You must wait for the starting day of the event to do this action.") ) raise osv.except_osv(_('Error!'), _("You must wait for the starting day of the event to do this action."))
return True return True
def button_reg_cancel(self, cr, uid, ids, context=None, *args): def button_reg_cancel(self, cr, uid, ids, context=None, *args):

View File

@ -12,14 +12,19 @@
<field name="state">open</field> <field name="state">open</field>
</record> </record>
<!-- notify all employees of module installation --> <!-- mail: event subtype -->
<record model="mail.message" id="module_install_notification"> <record id="event.mt_event_registration" model="mail.message.subtype">
<field name="model">mail.group</field> <field name="name">New Registrations</field>
<field name="res_id" ref="mail.group_all_employees"/> <field name="res_model">event.event</field>
<field name="type">notification</field> <field name="default" eval="False"/>
<field name="subject">Events Organisation application installed!</field>
<field name="body">From the top Events menu, you can organize events, manage registrations, automate communication around your event and sell events through your quotations.</field>
</record> </record>
<!-- notify all employees of module installation -->
<function model="mail.group" name="message_post">
<!-- ids, subject, body, parent_id=False, type='notification', content_subtype='html' -->
<value eval="[ref('mail.group_all_employees')]"/>
<value>From the top menu Events, you can organize events, manage registrations, automate communication around your event and sell events through your quotations.</value>
<value>Module Events has been installed</value>
</function>
</data> </data>
</openerp> </openerp>

View File

@ -196,7 +196,6 @@
</notebook> </notebook>
</sheet> </sheet>
<div class="oe_chatter"> <div class="oe_chatter">
<field name="message_is_follower" invisible="1"/>
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/> <field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
<field name="message_follower_ids" widget="mail_followers"/> <field name="message_follower_ids" widget="mail_followers"/>
</div> </div>
@ -479,7 +478,6 @@
</group> </group>
</sheet> </sheet>
<div class="oe_chatter"> <div class="oe_chatter">
<field name="message_is_follower" invisible="1"/>
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/> <field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
<field name="message_follower_ids" widget="mail_followers"/> <field name="message_follower_ids" widget="mail_followers"/>
</div> </div>

View File

@ -139,7 +139,6 @@
</notebook> </notebook>
</sheet> </sheet>
<div class="oe_chatter"> <div class="oe_chatter">
<field name="message_is_follower" invisible="1"/>
<field name="message_ids" widget="mail_thread"/> <field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/> <field name="message_follower_ids" widget="mail_followers"/>
</div> </div>

View File

@ -350,46 +350,43 @@ class hr_holidays(osv.osv):
# ----------------------------- # -----------------------------
def needaction_domain_get(self, cr, uid, ids, context=None): def needaction_domain_get(self, cr, uid, ids, context=None):
# to be tested, otherwise convert into employee_id in ...
emp_obj = self.pool.get('hr.employee') emp_obj = self.pool.get('hr.employee')
empids = emp_obj.search(cr, uid, [('parent_id.user_id','=',uid)], context=context) empids = emp_obj.search(cr, uid, [('parent_id.user_id', '=', uid)], context=context)
dom = [ dom = ['&', ('state', '=', 'confirm'), ('employee_id', 'in', empids)]
'&', ('state','=','confirm'),('employee_id', 'in', empids)
]
# if this user is a hr.manager, he should do second validations # if this user is a hr.manager, he should do second validations
if self.pool.get('res.users').has_group(cr, uid, 'base.group_hr_manager'): if self.pool.get('res.users').has_group(cr, uid, 'base.group_hr_manager'):
dom = ['|'] + dom + [ ('state','=','validate1') ] dom = ['|'] + dom + [('state', '=', 'validate1')]
return dom return dom
def create_notificate(self, cr, uid, ids, context=None): def create_notificate(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context): for obj in self.browse(cr, uid, ids, context=context):
self.message_post(cr, uid, ids, self.message_post(cr, uid, ids,
_("The request has been <b>created</b> and is waiting confirmation."), context=context) _("Request <b>created</b>, waiting confirmation."), context=context)
return True return True
def holidays_confirm_notificate(self, cr, uid, ids, context=None): def holidays_confirm_notificate(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids): for obj in self.browse(cr, uid, ids):
self.message_post(cr, uid, [obj.id], self.message_post(cr, uid, [obj.id],
_("The request has been <b>submitted</b> and is waiting for validation by the manager."), context=context) _("Request <b>submitted</b>, waiting for validation by the manager."), context=context)
def holidays_first_validate_notificate(self, cr, uid, ids, context=None): def holidays_first_validate_notificate(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context): for obj in self.browse(cr, uid, ids, context=context):
self.message_post(cr, uid, [obj.id], self.message_post(cr, uid, [obj.id],
_("The request has been <b>approved</b>. A second validation is necessary and is now pending."), context=context) _("Request <b>approved</b>, waiting second validation."), context=context)
def holidays_validate_notificate(self, cr, uid, ids, context=None): def holidays_validate_notificate(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids): for obj in self.browse(cr, uid, ids):
if obj.double_validation: if obj.double_validation:
self.message_post(cr, uid, [obj.id], self.message_post(cr, uid, [obj.id],
_("The request has been <b>double validated</b>. The validation process is now over."), context=context) _("Request <b>validated</b>."), context=context)
else: else:
self.message_post(cr, uid, [obj.id], self.message_post(cr, uid, [obj.id],
_("The request has been <b>approved</b>. The validation process is now over."), context=context) _("The request has been <b>approved</b>."), context=context)
def holidays_refuse_notificate(self, cr, uid, ids, context=None): def holidays_refuse_notificate(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids): for obj in self.browse(cr, uid, ids):
self.message_post(cr, uid, [obj.id], self.message_post(cr, uid, [obj.id],
_("The request has been <b>refused</b>. The validation process is now over."), context=context) _("Request <b>refused</b>"), context=context)
class resource_calendar_leaves(osv.osv): class resource_calendar_leaves(osv.osv):

View File

@ -121,7 +121,6 @@
</group> </group>
</sheet> </sheet>
<div class="oe_chatter"> <div class="oe_chatter">
<field name="message_is_follower" invisible="1"/>
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/> <field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
<field name="message_follower_ids" widget="mail_followers"/> <field name="message_follower_ids" widget="mail_followers"/>
</div> </div>
@ -159,7 +158,6 @@
<field name="notes" nolabel="1" colspan="4" placeholder="Add a reason..."/> <field name="notes" nolabel="1" colspan="4" placeholder="Add a reason..."/>
</sheet> </sheet>
<div class="oe_chatter"> <div class="oe_chatter">
<field name="message_is_follower" invisible="1"/>
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/> <field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
<field name="message_follower_ids" widget="mail_followers"/> <field name="message_follower_ids" widget="mail_followers"/>
</div> </div>

View File

@ -461,7 +461,7 @@ class hr_applicant(base_stage, osv.Model):
""" Override of the (void) default notification method. """ """ Override of the (void) default notification method. """
if not stage_id: return True if not stage_id: return True
stage_name = self.pool.get('hr.recruitment.stage').name_get(cr, uid, [stage_id], context=context)[0][1] stage_name = self.pool.get('hr.recruitment.stage').name_get(cr, uid, [stage_id], context=context)[0][1]
return self.message_post(cr, uid, ids, body= _("Stage changed to <b>%s</b>.") % (stage_name), context=context) return self.message_post(cr, uid, ids, body=_("Stage changed to <b>%s</b>.") % (stage_name), context=context)
def case_get_note_msg_prefix(self, cr, uid, id, context=None): def case_get_note_msg_prefix(self, cr, uid, id, context=None):
return 'Applicant' return 'Applicant'
@ -474,6 +474,8 @@ class hr_applicant(base_stage, osv.Model):
if context is None: if context is None:
context = {} context = {}
for applicant in self.browse(cr, uid, ids, context=context): for applicant in self.browse(cr, uid, ids, context=context):
if applicant.job_id:
self.pool.get('hr.job').message_post(cr, uid, [applicant.job_id.id], body=_('New employee joined the company %s.')%(applicant.name,), subtype="hr_recruitment.mt_hired", context=context)
if applicant.emp_id: if applicant.emp_id:
message = _("Applicant has been <b>hired</b> and created as an employee.") message = _("Applicant has been <b>hired</b> and created as an employee.")
self.message_post(cr, uid, [applicant.id], body=message, context=context) self.message_post(cr, uid, [applicant.id], body=message, context=context)
@ -492,6 +494,9 @@ class hr_applicant(base_stage, osv.Model):
def create_send_note(self, cr, uid, ids, context=None): def create_send_note(self, cr, uid, ids, context=None):
message = _("Applicant has been <b>created</b>.") message = _("Applicant has been <b>created</b>.")
for applicant in self.browse(cr, uid, ids, context=context):
if applicant.job_id:
self.pool.get('hr.job').message_post(cr, uid, [applicant.job_id.id], body=message, subtype="hr_recruitment.mt_applicant_new", context=context)
return self.message_post(cr, uid, ids, body=message, context=context) return self.message_post(cr, uid, ids, body=message, context=context)
class hr_job(osv.osv): class hr_job(osv.osv):

View File

@ -460,6 +460,15 @@ You can automatically receive job application though an email gateway, see the H
<field name="alias_model_id" ref="model_hr_applicant"/> <field name="alias_model_id" ref="model_hr_applicant"/>
<field name="alias_user_id" ref="base.user_root"/> <field name="alias_user_id" ref="base.user_root"/>
</record> </record>
<!-- mail: subtypes -->
<record id="mt_hired" model="mail.message.subtype">
<field name="name">Employee Hired</field>
<field name="res_model">hr.job</field>
</record>
<record id="mt_applicant_new" model="mail.message.subtype">
<field name="name">New Applicant</field>
<field name="res_model">hr.job</field>
</record>
</data> </data>
</openerp> </openerp>

View File

@ -183,7 +183,6 @@
<field name="description" placeholder="Feedback of interviews..."/> <field name="description" placeholder="Feedback of interviews..."/>
</sheet> </sheet>
<div class="oe_chatter"> <div class="oe_chatter">
<field name="message_is_follower" invisible="1"/>
<field name="message_ids" widget="mail_thread"/> <field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/> <field name="message_follower_ids" widget="mail_followers"/>
</div> </div>

View File

@ -85,7 +85,7 @@ class account_analytic_account(osv.osv):
return res return res
def on_change_partner_id(self, cr, uid, ids, partner_id, name, context=None): def on_change_partner_id(self, cr, uid, ids, partner_id, name, context=None):
res = super(account_analytic_account,self).on_change_partner_id(cr, uid, ids,partner_id, name, context=context) res = super(account_analytic_account, self).on_change_partner_id(cr, uid, ids, partner_id, name, context=context)
part = self.pool.get('res.partner').browse(cr, uid, partner_id, context=context) part = self.pool.get('res.partner').browse(cr, uid, partner_id, context=context)
pricelist = part.property_product_pricelist and part.property_product_pricelist.id or False pricelist = part.property_product_pricelist and part.property_product_pricelist.id or False
if pricelist: if pricelist:
@ -93,25 +93,25 @@ class account_analytic_account(osv.osv):
return res return res
def set_close(self, cr, uid, ids, context=None): def set_close(self, cr, uid, ids, context=None):
self.write(cr, uid, ids, {'state':'close'}, context=context) self.write(cr, uid, ids, {'state': 'close'}, context=context)
message = _("Contract has been <b>closed</b>.") message = _("Contract has been <b>closed</b>.")
self.message_post(cr, uid, ids, body=message, context=context) self.message_post(cr, uid, ids, body=message, subtype="mt_account_closed", context=context)
return True return True
def set_cancel(self, cr, uid, ids, context=None): def set_cancel(self, cr, uid, ids, context=None):
self.write(cr, uid, ids, {'state':'cancelled'}, context=context) self.write(cr, uid, ids, {'state': 'cancelled'}, context=context)
message = _("Contract has been <b>cancelled</b>.") message = _("Contract has been <b>canceled</b>.")
self.message_post(cr, uid, ids, body=message, context=context) self.message_post(cr, uid, ids, body=message, subtype="mt_account_canceled", context=context)
return True return True
def set_open(self, cr, uid, ids, context=None): def set_open(self, cr, uid, ids, context=None):
self.write(cr, uid, ids, {'state':'open'}, context=context) self.write(cr, uid, ids, {'state': 'open'}, context=context)
message = _("Contract has been <b>opened</b>.") message = _("Contract has been <b>opened</b>.")
self.message_post(cr, uid, ids, body=message, context=context) self.message_post(cr, uid, ids, body=message, context=context)
return True return True
def set_pending(self, cr, uid, ids, context=None): def set_pending(self, cr, uid, ids, context=None):
self.write(cr, uid, ids, {'state':'pending'}, context=context) self.write(cr, uid, ids, {'state': 'pending'}, context=context)
message = _("Contract has been set as <b>pending</b>.") message = _("Contract has been set as <b>pending</b>.")
self.message_post(cr, uid, ids, body=message, context=context) self.message_post(cr, uid, ids, body=message, context=context)
return True return True

View File

@ -71,19 +71,17 @@ class idea_idea(osv.osv):
self.message_post(cr, uid, ids, body=_('Idea canceled.'), context=context) self.message_post(cr, uid, ids, body=_('Idea canceled.'), context=context)
return True return True
def idea_open(self, cr, uid, ids, context=None): def idea_open(self, cr, uid, ids, context={}):
self.write(cr, uid, ids, { 'state': 'open'}) self.write(cr, uid, ids, {'state': 'open'}, context=context)
self.message_post(cr, uid, ids, body=_('Idea accepted.'), context=context) self.message_post(cr, uid, ids, body=_('Idea accepted.'), context=context)
return True return True
def idea_close(self, cr, uid, ids, context=None): def idea_close(self, cr, uid, ids, context={}):
self.message_post(cr, uid, ids, body=_('Idea done.'), context=context) self.write(cr, uid, ids, {'state': 'close'}, context=context)
self.write(cr, uid, ids, { 'state': 'close' }) self.message_post(cr, uid, ids, body=_('Idea closed.'), context=context)
return True return True
def idea_draft(self, cr, uid, ids, context=None): def idea_draft(self, cr, uid, ids, context={}):
self.write(cr, uid, ids, {'state': 'draft'}, context=context)
self.message_post(cr, uid, ids, body=_('Idea reset to draft.'), context=context) self.message_post(cr, uid, ids, body=_('Idea reset to draft.'), context=context)
self.write(cr, uid, ids, { 'state': 'draft' })
return True return True
idea_idea()

View File

@ -15,6 +15,7 @@
<record id="base.user_demo" model="res.users"> <record id="base.user_demo" model="res.users">
<field name="groups_id" eval="[(4,ref('base.group_tool_user'))]"/> <field name="groups_id" eval="[(4,ref('base.group_tool_user'))]"/>
</record> </record>
</data> </data>
</openerp> </openerp>

View File

@ -79,7 +79,6 @@
<field name="description"/> <field name="description"/>
</sheet> </sheet>
<div class="oe_chatter"> <div class="oe_chatter">
<field name="message_is_follower" invisible="1"/>
<field name="message_ids" widget="mail_thread"/> <field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/> <field name="message_follower_ids" widget="mail_followers"/>
</div> </div>

View File

@ -19,6 +19,7 @@
# #
############################################################################## ##############################################################################
import mail_message_subtype
import mail_alias import mail_alias
import mail_followers import mail_followers
import mail_message import mail_message

View File

@ -49,6 +49,7 @@ Main Features
'data': [ 'data': [
'wizard/invite_view.xml', 'wizard/invite_view.xml',
'wizard/mail_compose_message_view.xml', 'wizard/mail_compose_message_view.xml',
'mail_message_subtype.xml',
'res_config_view.xml', 'res_config_view.xml',
'mail_message_view.xml', 'mail_message_view.xml',
'mail_mail_view.xml', 'mail_mail_view.xml',

View File

@ -12,5 +12,9 @@
<field eval="'process_email_queue'" name="function"/> <field eval="'process_email_queue'" name="function"/>
<field eval="'()'" name="args"/> <field eval="'()'" name="args"/>
</record> </record>
<record id="mt_comment" model="mail.message.subtype">
<field name="name">comment</field>
</record>
</data> </data>
</openerp> </openerp>

View File

@ -11,3 +11,4 @@ Mail Module documentation topics
mail_needaction_howto mail_needaction_howto
mail_partner mail_partner
mail_state mail_state
mail_subtype

View File

@ -0,0 +1,70 @@
.. _mail_message_subtype:
OpenChatter Pi (3.1415): Message Subtype
========================================
To overcome the problems of crowdy walls in system notification, We have added features of **Message Subtype** in mail.
mail.message.subtype
++++++++++++++++++++
``mail.message.subtype`` has following fields:
- ``Name``: fields.char(' Message Subtype ', size = 128,required = True,help = 'Subtype Of Message'),
- ``model_ids``: fields.many2many('ir.model','mail_message_subtyp_message_rel','message_subtype_id', 'model_id', 'Model',help = "link some subtypes to several models, for projet/task"),
- ``default``: fields.boolean('Default', help = "When subscribing to the document, users will receive by default messages related to this subtype unless they uncheck this subtype"),
mail.followers
++++++++++++++
In ``mail.followers`` we have added additional many2many field subtype ids :
- ``subtype_ids``: fields.many2many('mail.message.subtype','mail_message_subtyp_rel','subscription_id', 'subtype_id', 'Subtype',help = "linking some subscription to several subtype for projet/task")
mail.message
++++++++++++
In mail_message we have added additional field subtype_id which Indicates the Type of Message
- ``subtype_id``: fields.many2one('mail.message.subtype', 'Subtype')
mail.thread
+++++++++++
- In **message_post** method add the *subtype_id* field as parameter and set as default subtype 'Other'.
def message_post(self, cr, uid, thread_id, body='', subject=False, msg_type='notification', parent_id=False, attachments=None, subtype='other', context=None, ``**kwargs``):
- In **message_subscribe** method add the *subtype_ids* field as parameter.In this method if subtype_ids is None, it fatch the default true subtypes in mail.message.subtypes otherwise pass selected subtypes.
For update subtypes call **message_subscribe_udpate_subtypes** method
def message_subscribe(self, cr, uid, ids, partner_ids,subtype_ids = None, context=None):
- Add **message_subscribe_udpate_subtypes** method to update the subtype_ids in followers.
def message_subscribe_udpate_subtypes(self, cr, uid, ids, user_id, subtype_ids,context=None):
followers_obj = self.pool.get('mail.followers')
followers_ids = followers_obj.search(cr, uid, [('res_model', '=', self._name), ('res_id', 'in', ids)])
return followers_obj.write(cr, uid, followers_ids, {'subtype_ids': [(6, 0 , subtype_ids)]}, context = context)
For Each Addons:
++++++++++++++++
- Add data of subtypes for each addons module.
- Add subtype field as parameter in **message_post** Method for each addons module.
How It Works:
+++++++++++++
- In addons module when we Follow a Perticular document It display under the followers button.
- In sybtypes there are 3 default subtypes for each addons
1) Email
2) Comment
3) Other
- In document display a default subtypes(which are true) related a perticular model_ids wise.
Example:-
If I have open crm.lead, It display only subtypes of crm.lead
- When we select subtype it update subtype_ids(which are checked) in mail.follower where match res_model & res_id of the current documents.
- when message created update subtype_id of that message in mail.message.
- In Feeds display only those notifications of documents which subtypes are selected

View File

@ -46,6 +46,8 @@ class mail_followers(osv.Model):
help='Id of the followed resource'), help='Id of the followed resource'),
'partner_id': fields.many2one('res.partner', string='Related Partner', 'partner_id': fields.many2one('res.partner', string='Related Partner',
ondelete='cascade', required=True, select=1), ondelete='cascade', required=True, select=1),
'subtype_ids': fields.many2many('mail.message.subtype', string='Subtype',
help="Message subtypes followed, meaning subtypes that will be pushed onto the user's Wall."),
} }
@ -81,18 +83,27 @@ class mail_notification(osv.Model):
return super(mail_notification, self).create(cr, uid, vals, context=context) return super(mail_notification, self).create(cr, uid, vals, context=context)
return False return False
def set_message_read(self, cr, uid, msg_id, context=None): def set_message_read(self, cr, uid, msg_ids, read=None, context=None):
partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id if msg_ids == None:
notif_ids = self.search(cr, uid, [('partner_id', '=', partner_id), ('message_id', '=', msg_id)], context=context) return False
return self.write(cr, uid, notif_ids, {'read': True}, context=context) if type(msg_ids) is not list:
msg_ids=[msg_ids]
def get_partners_to_notify(self, cr, uid, partner_ids, message, context=None): partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id
notif_ids = self.search(cr, uid, [('partner_id', '=', partner_id), ('message_id', 'in', msg_ids)], context=context)
return self.write(cr, uid, notif_ids, {'read': read}, context=context)
def get_partners_to_notify(self, cr, uid, message, context=None):
""" Return the list of partners to notify, based on their preferences. """ Return the list of partners to notify, based on their preferences.
:param browse_record message: mail.message to notify :param browse_record message: mail.message to notify
""" """
notify_pids = [] notify_pids = []
for partner in self.pool.get('res.partner').browse(cr, SUPERUSER_ID, partner_ids, context=context): for notification in message.notification_ids:
if notification.read:
continue
partner = notification.partner_id
# Do not send an email to the writer # Do not send an email to the writer
if partner.user_ids and partner.user_ids[0].id == uid: if partner.user_ids and partner.user_ids[0].id == uid:
continue continue
@ -111,15 +122,15 @@ class mail_notification(osv.Model):
notify_pids.append(partner.id) notify_pids.append(partner.id)
return notify_pids return notify_pids
def notify(self, cr, uid, partner_ids, msg_id, context=None): def _notify(self, cr, uid, msg_id, context=None):
""" Send by email the notification depending on the user preferences """ """ Send by email the notification depending on the user preferences """
context = context or {} context = context or {}
# mail_noemail (do not send email) or no partner_ids: do not send, return # mail_noemail (do not send email) or no partner_ids: do not send, return
if context.get('mail_noemail') or not partner_ids: if context.get('mail_noemail'):
return True return True
msg = self.pool.get('mail.message').browse(cr, uid, msg_id, context=context) 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) notify_partner_ids = self.get_partners_to_notify(cr, uid, msg, context=context)
if not notify_partner_ids: if not notify_partner_ids:
return True return True

View File

@ -16,6 +16,28 @@
</field> </field>
</record> </record>
<record model="ir.ui.view" id="view_mail_subscription_form">
<field name="name">mail.followers.form</field>
<field name="model">mail.followers</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Followers Form" version="7.0">
<sheet>
<group>
<group>
<field name="res_model"/>
<field name="partner_id"/>
</group>
<group>
<field name="res_id"/>
<field name="subtype_ids" widget="many2many_tags"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<!-- NOTIFICATION !--> <!-- NOTIFICATION !-->
<record model="ir.ui.view" id="view_notification_tree"> <record model="ir.ui.view" id="view_notification_tree">
<field name="name">mail.notification.tree</field> <field name="name">mail.notification.tree</field>

View File

@ -30,6 +30,7 @@ class mail_group(osv.Model):
group. The group mechanics are based on the followers. """ group. The group mechanics are based on the followers. """
_description = 'Discussion group' _description = 'Discussion group'
_name = 'mail.group' _name = 'mail.group'
_mail_autothread = False
_inherit = ['mail.thread'] _inherit = ['mail.thread']
_inherits = {'mail.alias': 'alias_id', 'ir.ui.menu': 'menu_id'} _inherits = {'mail.alias': 'alias_id', 'ir.ui.menu': 'menu_id'}
@ -157,7 +158,7 @@ class mail_group(osv.Model):
def write(self, cr, uid, ids, vals, context=None): def write(self, cr, uid, ids, vals, context=None):
result = super(mail_group, self).write(cr, uid, ids, vals, context=context) result = super(mail_group, self).write(cr, uid, ids, vals, context=context)
if vals.get('group_ids'): if vals.get('group_ids'):
self._subscribe_users(cr, uid, ids, vals.get('group_ids'), context=context) self._subscribe_users(cr, uid, ids, context=context)
return result return result
def action_follow(self, cr, uid, ids, context=None): def action_follow(self, cr, uid, ids, context=None):

View File

@ -15,9 +15,10 @@
<field name="priority" eval="10"/> <field name="priority" eval="10"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<kanban> <kanban>
<field name="message_is_follower"/>
<field name="message_follower_ids"/> <field name="message_follower_ids"/>
<field name="message_is_follower"/>
<field name="message_summary"/> <field name="message_summary"/>
<field name="description"/>
<templates> <templates>
<t t-name="kanban-description"> <t t-name="kanban-description">
<div class="oe_group_description" t-if="record.description.raw_value"> <div class="oe_group_description" t-if="record.description.raw_value">
@ -81,7 +82,6 @@
</group> </group>
</sheet> </sheet>
<div class="oe_chatter"> <div class="oe_chatter">
<field name="message_is_follower" invisible="1"/>
<field name="message_ids" widget="mail_thread" <field name="message_ids" widget="mail_thread"
options='{"thread_level": 1}'/> options='{"thread_level": 1}'/>
<field name="message_follower_ids" widget="mail_followers"/> <field name="message_follower_ids" widget="mail_followers"/>

View File

@ -131,6 +131,7 @@ class mail_message(osv.Model):
'unread': fields.function(_get_unread, fnct_search=_search_unread, 'unread': fields.function(_get_unread, fnct_search=_search_unread,
type='boolean', string='Unread', type='boolean', string='Unread',
help='Functional field to search for unread messages linked to uid'), help='Functional field to search for unread messages linked to uid'),
'subtype_id': fields.many2one('mail.message.subtype', 'Subtype'),
'vote_user_ids': fields.many2many('res.users', 'mail_vote', 'message_id', 'user_id', string='Votes', 'vote_user_ids': fields.many2many('res.users', 'mail_vote', 'message_id', 'user_id', string='Votes',
help='Users that voted for this message'), help='Users that voted for this message'),
} }
@ -167,7 +168,7 @@ class mail_message(osv.Model):
self.write(cr, SUPERUSER_ID, message.get('id'), {'vote_user_ids': [(4, user_id)]}, context=context) self.write(cr, SUPERUSER_ID, message.get('id'), {'vote_user_ids': [(4, user_id)]}, context=context)
else: else:
self.write(cr, SUPERUSER_ID, message.get('id'), {'vote_user_ids': [(3, user_id)]}, context=context) self.write(cr, SUPERUSER_ID, message.get('id'), {'vote_user_ids': [(3, user_id)]}, context=context)
return True return not(has_voted) or False
#------------------------------------------------------ #------------------------------------------------------
# Message loading for web interface # Message loading for web interface
@ -179,6 +180,7 @@ class mail_message(osv.Model):
fields allow to have the foreign record name without having fields allow to have the foreign record name without having
to check external access rights). to check external access rights).
""" """
child_nbr = len(msg.child_ids)
has_voted = False has_voted = False
vote_ids = self.pool.get('res.users').name_get(cr, SUPERUSER_ID, [user.id for user in msg.vote_user_ids], context=context) vote_ids = self.pool.get('res.users').name_get(cr, SUPERUSER_ID, [user.id for user in msg.vote_user_ids], context=context)
for vote in vote_ids: for vote in vote_ids:
@ -191,7 +193,7 @@ class mail_message(osv.Model):
attachment_ids = [] attachment_ids = []
try: try:
author_id = self.pool.get('res.partner').name_get(cr, uid, [msg.author_id.id], context=context)[0] author_id = self.pool.get('res.partner').name_get(cr, uid, [msg.author_id.id], context=context)[0]
is_author = uid in msg.author_id.user_ids is_author = uid == msg.author_id.user_ids[0].id
except (orm.except_orm, osv.except_osv): except (orm.except_orm, osv.except_osv):
author_id = False author_id = False
is_author = False is_author = False
@ -199,6 +201,7 @@ class mail_message(osv.Model):
partner_ids = self.pool.get('res.partner').name_get(cr, uid, [x.id for x in msg.partner_ids], context=context) partner_ids = self.pool.get('res.partner').name_get(cr, uid, [x.id for x in msg.partner_ids], context=context)
except (orm.except_orm, osv.except_osv): except (orm.except_orm, osv.except_osv):
partner_ids = [] partner_ids = []
return { return {
'id': msg.id, 'id': msg.id,
'type': msg.type, 'type': msg.type,
@ -212,12 +215,30 @@ class mail_message(osv.Model):
'author_id': author_id, 'author_id': author_id,
'is_author': is_author, 'is_author': is_author,
'partner_ids': partner_ids, 'partner_ids': partner_ids,
'child_ids': [], 'parent_id': msg.parent_id and msg.parent_id.id or False,
'vote_user_ids': vote_ids, 'vote_user_ids': vote_ids,
'has_voted': has_voted 'has_voted': has_voted,
'unread': msg.unread and msg.unread['unread'] or False
} }
def message_read_tree_flatten(self, cr, uid, messages, current_level, level, context=None): def message_read_tree_get_expandable(self, cr, uid, parent_message, last_message, domain=[], current_level=0, level=0, context=None):
""" . """
base_domain = [('id', '<', last_message['id'])]
if parent_message and current_level < level:
base_domain += [('parent_id', '=', parent_message['id'])]
elif parent_message:
base_domain += [('id', 'child_of', parent_message['id']), ('id', '!=', parent_message['id'])]
if domain:
base_domain += domain
extension = { 'type': 'expandable',
'domain': base_domain,
'thread_level': current_level,
'context': context,
'id': -1,
}
return extension
def message_read_tree_flatten(self, cr, uid, parent_message, messages, domain=[], level=0, current_level=0, context=None, limit=None, add_expandable=True):
""" Given a tree with several roots of following structure : """ Given a tree with several roots of following structure :
[ {'id': 1, 'child_ids': [ [ {'id': 1, 'child_ids': [
{'id': 11, 'child_ids': [...] },], {'id': 11, 'child_ids': [...] },],
@ -236,69 +257,143 @@ class mail_message(osv.Model):
child_ids = msg_dict.pop('child_ids', []) child_ids = msg_dict.pop('child_ids', [])
msg_dict['child_ids'] = [] msg_dict['child_ids'] = []
return [msg_dict] + child_ids return [msg_dict] + child_ids
# return sorted([msg_dict] + child_ids, key=itemgetter('id'), reverse=True)
context = context or {} context = context or {}
limit = limit or self._message_read_limit
# Depth-first flattening # Depth-first flattening
for message in messages: for message in messages:
if message.get('type') == 'expandable': if message.get('type') == 'expandable':
continue continue
message['child_ids'] = self.message_read_tree_flatten(cr, uid, message['child_ids'], current_level + 1, level, context=context) message['child_ids'] = self.message_read_tree_flatten(cr, uid, message, message['child_ids'], domain, level, current_level + 1, context=context, limit=limit)
for child in message['child_ids']:
if child.get('type') == 'expandable':
continue
message['child_nbr'] += child['child_nbr']
# Flatten if above maximum depth # Flatten if above maximum depth
if current_level < level: if current_level < level:
return_list = messages return_list = messages
else: else:
return_list = [] return_list = [flat_message for message in messages for flat_message in _flatten(message)]
for message in messages:
for flat_message in _flatten(message):
return_list.append(flat_message)
return sorted(return_list, key=itemgetter(context.get('sort_key', 'id')), reverse=context.get('sort_reverse', True))
def message_read(self, cr, uid, ids=False, domain=[], thread_level=0, limit=None, context=None): # Add expandable
""" If IDs are provided, fetch these records. Otherwise use the domain return_list = sorted(return_list, key=itemgetter(context.get('sort_key', 'id')), reverse=context.get('sort_reverse', True))
to fetch the matching records. if return_list and current_level == 0 and add_expandable:
After having fetched the records provided by IDs, it will fetch the expandable = self.message_read_tree_get_expandable(cr, uid, parent_message, return_list and return_list[-1] or parent_message, domain, current_level, level, context=context)
parents to have well-formed threads. return_list.append(expandable)
elif return_list and current_level <= level and add_expandable:
expandable = self.message_read_tree_get_expandable(cr, uid, parent_message, return_list and return_list[-1] or parent_message, domain, current_level, level, context=context)
return_list.append(expandable)
return return_list
def message_read(self, cr, uid, ids=False, domain=[], level=0, context=None, parent_id=False, limit=None):
""" Read messages from mail.message, and get back a structured tree
of messages to be displayed as discussion threads. If IDs is set,
fetch these records. Otherwise use the domain to fetch messages.
After having fetch messages, their parents will be added to obtain
well formed threads.
:param domain: optional domain for searching ids
:param level: level of threads to display, 0 being flat
:param limit: number of messages to fetch
:param parent_id: if parent_id reached, stop searching for
further parents
:return list: list of trees of messages :return list: list of trees of messages
""" """
message_loaded = context and context.get('message_loaded') or [0]
# don't read the message display by .js, in context message_loaded list
if context and context.get('message_loaded'):
domain += [ ['id','not in',message_loaded] ];
limit = limit or self._message_read_limit limit = limit or self._message_read_limit
context = context or {} context = context or {}
if not ids:
ids = self.search(cr, SUPERUSER_ID, domain, context=context, limit=limit)
messages = self.browse(cr, uid, ids, context=context)
tree = []
result = [] result = []
tree = {} # key: ID, value: record record = None
for msg in messages:
if len(result) < (limit - 1): # select ids
record = self._message_dict_get(cr, uid, msg, context=context) if ids:
if thread_level and msg.parent_id: for msg in self.browse(cr, uid, ids, context=context):
while msg.parent_id: result.append(self._message_dict_get(cr, uid, msg, context=context))
if msg.parent_id.id in tree: return result
record_parent = tree[msg.parent_id.id]
else: # key: ID, value: record
record_parent = self._message_dict_get(cr, uid, msg.parent_id, context=context) ids = self.search(cr, SUPERUSER_ID, domain, context=context, limit=limit)
if msg.parent_id.parent_id: for msg in self.browse(cr, uid, ids, context=context):
tree[msg.parent_id.id] = record_parent # if not in record and not in message_loded list
if record['id'] not in [x['id'] for x in record_parent['child_ids']]: if msg.id not in tree and msg.id not in message_loaded :
record_parent['child_ids'].append(record) record = self._message_dict_get(cr, uid, msg, context=context)
record = record_parent tree.append(msg.id)
msg = msg.parent_id result.append(record)
if msg.id not in tree:
result.append(record) while msg.parent_id and msg.parent_id.id != parent_id:
tree[msg.id] = record parent_id = msg.parent_id.id
else: if msg.parent_id.id not in tree:
result.append({ msg = msg.parent_id
'type': 'expandable', tree.append(msg.id)
'domain': [('id', '<=', msg.id)] + domain, # if not in record and not in message_loded list
'context': context, if msg.id not in message_loaded :
'thread_level': thread_level, # should be improve accodting to level of records record = self._message_dict_get(cr, uid, msg, context=context)
'id': -1, result.append(record)
})
break result = sorted(result, key=lambda k: k['id'])
tree_not = []
# expandable for not show message
for id_msg in tree:
# get all childs
not_loaded_ids = self.search(cr, SUPERUSER_ID, [['parent_id','=',id_msg],['id','not in',message_loaded]], None, limit=1000)
# group childs not read
id_min=None
id_max=None
nb=0
for not_loaded_id in not_loaded_ids:
if not_loaded_id not in tree:
nb+=1
if id_min==None or id_min>not_loaded_id:
id_min=not_loaded_id
if id_max==None or id_max<not_loaded_id:
id_max=not_loaded_id
tree_not.append(not_loaded_id)
else:
if nb>0:
result.append({
'domain': [['id','>=',id_min],['id','<=',id_max],['parent_id','=',id_msg]],
'nb_messages': nb,
'type': 'expandable',
'parent_id': id_msg,
'id': id_min
})
nb=0
if nb>0:
result.append({
'domain': [['id','>=',id_min],['parent_id','=',id_msg]],
'nb_messages': nb,
'type': 'expandable',
'parent_id': id_msg,
'id': id_min
})
# expandable for limit max
ids = self.search(cr, SUPERUSER_ID, domain+[['id','not in',message_loaded+tree+tree_not]], context=context, limit=1)
if len(ids) > 0:
result.append(
{
'domain': domain,
'nb_messages': 0,
'type': 'expandable',
'parent_id': parent_id,
'id': -1
});
result = sorted(result, key=lambda k: k['id'])
# Flatten the result
if thread_level > 0:
result = self.message_read_tree_flatten(cr, uid, result, 0, thread_level, context=context)
return result return result
#------------------------------------------------------ #------------------------------------------------------
@ -409,7 +504,7 @@ class mail_message(osv.Model):
if not values.get('message_id') and values.get('res_id') and values.get('model'): if not values.get('message_id') and values.get('res_id') and values.get('model'):
values['message_id'] = tools.generate_tracking_message_id('%(model)s-%(res_id)s' % values) values['message_id'] = tools.generate_tracking_message_id('%(model)s-%(res_id)s' % values)
newid = super(mail_message, self).create(cr, uid, values, context) newid = super(mail_message, self).create(cr, uid, values, context)
self.notify(cr, uid, newid, context=context) self._notify(cr, 1, newid, context=context)
return newid return newid
def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'): def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'):
@ -431,26 +526,39 @@ class mail_message(osv.Model):
self.pool.get('ir.attachment').unlink(cr, uid, attachments_to_delete, context=context) self.pool.get('ir.attachment').unlink(cr, uid, attachments_to_delete, context=context)
return super(mail_message, self).unlink(cr, uid, ids, context=context) return super(mail_message, self).unlink(cr, uid, ids, context=context)
def notify(self, cr, uid, newid, context=None): def _notify(self, cr, uid, newid, context=None):
""" Add the related record followers to the destination partner_ids. """ Add the related record followers to the destination partner_ids.
Call mail_notification.notify to manage the email sending Call mail_notification.notify to manage the email sending
""" """
message = self.browse(cr, uid, newid, context=context) message = self.browse(cr, uid, newid, context=context)
partners_to_notify = set([]) partners_to_notify = set([])
# add all partner_ids of the message # message has no subtype_id: pure log message -> no partners, no one notified
if not message.subtype_id:
message.write({'partner_ids': [5]})
return True
# all partner_ids of the mail.message have to be notified
if message.partner_ids: if message.partner_ids:
partners_to_notify |= set(partner.id for partner in message.partner_ids) partners_to_notify |= set(partner.id for partner in message.partner_ids)
# add all followers and set add them in partner_ids # all followers of the mail.message document have to be added as partners and notified
if message.model and message.res_id: if message.model and message.res_id:
record = self.pool.get(message.model).browse(cr, SUPERUSER_ID, message.res_id, context=context) fol_obj = self.pool.get("mail.followers")
extra_notified = set(partner.id for partner in record.message_follower_ids) fol_ids = fol_obj.search(cr, uid, [('res_model', '=', message.model), ('res_id', '=', message.res_id), ('subtype_ids', 'in', message.subtype_id.id)], context=context)
fol_objs = fol_obj.browse(cr, uid, fol_ids, context=context)
extra_notified = set(fol.partner_id.id for fol in fol_objs)
missing_notified = extra_notified - partners_to_notify missing_notified = extra_notified - partners_to_notify
missing_notified = missing_notified
if missing_notified: if missing_notified:
self.write(cr, SUPERUSER_ID, [newid], {'partner_ids': [(4, p_id) for p_id in missing_notified]}, context=context) self.write(cr, SUPERUSER_ID, [newid], {'partner_ids': [(4, p_id) for p_id in missing_notified]}, context=context)
partners_to_notify |= extra_notified partners_to_notify |= extra_notified
# # remove uid from partners
self.write(cr, SUPERUSER_ID, [newid], {'partner_ids': [(3, uid)]}, context=context) # add myself if I wrote on my wall,
self.pool.get('mail.notification').notify(cr, uid, list(partners_to_notify), newid, context=context) # unless remove myself author
if ((message.model=="res.partner" and message.res_id==message.author_id.id)):
self.write(cr, SUPERUSER_ID, [newid], {'partner_ids': [(4, message.author_id.id)]}, context=context)
else:
self.write(cr, SUPERUSER_ID, [newid], {'partner_ids': [(3, message.author_id.id)]}, context=context)
self.pool.get('mail.notification')._notify(cr, uid, newid, context=context)
def copy(self, cr, uid, id, default=None, context=None): def copy(self, cr, uid, id, default=None, context=None):
"""Overridden to avoid duplicating fields that are unique to each email""" """Overridden to avoid duplicating fields that are unique to each email"""

View File

@ -0,0 +1,45 @@
# -*- 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
from osv import fields
class mail_message_subtype(osv.osv):
""" Class holding subtype definition for messages. Subtypes allow to tune
the follower subscription, allowing only some subtypes to be pushed
on the Wall. """
_name = 'mail.message.subtype'
_description = 'mail_message_subtype'
_columns = {
'name': fields.char('Message Type', required=True, translate=True,
help='Message subtype, gives a more precise type on the message, '\
'especially for system notifications. For example, it can be '\
'a notification related to a new record (New), or to a stage '\
'change in a process (Stage change). Message subtypes allow to '\
'precisely tune the notifications the user want to receive on its wall.'),
'res_model': fields.char('Model', help="link subtype to model"),
'default': fields.boolean('Default',
help="When subscribing to the document, this subtype will be checked by default."),
}
_defaults = {
'default': True,
}

View File

@ -0,0 +1,43 @@
<?xml version="1.0"?>
<openerp>
<data>
<record model="ir.ui.view" id="view_message_subtype_tree">
<field name="name">mail.message.subtype.tree</field>
<field name="model">mail.message.subtype</field>
<field name="priority">10</field>
<field name="arch" type="xml">
<tree string="Subtype">
<field name="name"/>
<field name="res_model"/>
<field name="default"/>
</tree>
</field>
</record>
<record model="ir.ui.view" id="view_mail_message_subtype_form">
<field name="name">mail.message.subtype.form</field>
<field name="model">mail.message.subtype</field>
<field name="arch" type="xml">
<form string="Email message" version="7.0">
<sheet>
<group>
<field name="name"/>
<field name="res_model"/>
<field name="default"/>
</group>
</sheet>
</form>
</field>
</record>
<record model="ir.actions.act_window" id="action_view_message_subtype">
<field name="name">Subtypes</field>
<field name="res_model">mail.message.subtype</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem name="Subtypes" id="menu_message_subtype" parent="base.menu_email" action="action_view_message_subtype"/>
</data>
</openerp>

View File

@ -31,6 +31,7 @@
<field name="author_id"/> <field name="author_id"/>
<field name="date"/> <field name="date"/>
<field name="type"/> <field name="type"/>
<field name="subtype_id"/>
</group> </group>
<group> <group>
<field name="model"/> <field name="model"/>
@ -55,16 +56,18 @@
<field name="subject" string="Content" filter_domain="['|', ('subject', 'ilike', self), ('body', 'ilike', self)]" /> <field name="subject" string="Content" filter_domain="['|', ('subject', 'ilike', self), ('body', 'ilike', self)]" />
<field name="type"/> <field name="type"/>
<field name="author_id"/> <field name="author_id"/>
<filter icon="terp-personal+" string="Comments" <filter string="Unread"
name="unread_message" help="Show unread message"
domain="[('unread', '=', True)]"/>
<filter string="Comments"
name="comments" help="Comments" name="comments" help="Comments"
domain="[('type', '=', 'comment')]"/> domain="[('type', '=', 'comment')]"/>
<filter icon="terp-personal+" string="Notifications" <filter string="Notifications"
name="notifications" help="Notifications" name="notifications" help="Notifications"
domain="[('type', '=', 'notification')]"/> domain="[('type', '=', 'notification')]"/>
<filter icon="terp-personal+" string="Emails" <filter string="Emails"
name="emails" help="Emails" name="emails" help="Emails"
domain="[('type', '=', 'email')]"/> domain="[('type', '=', 'email')]"/>
<field name="author_id"/>
</search> </search>
</field> </field>
</record> </record>
@ -75,20 +78,28 @@
<field name="view_type">form</field> <field name="view_type">form</field>
<field name="view_mode">tree,form</field> <field name="view_mode">tree,form</field>
<field name="search_view_id" ref="view_message_search"/> <field name="search_view_id" ref="view_message_search"/>
<field name="context">{'search_default_unread_message':True}</field>
</record> </record>
<!-- Add menu entry in Settings/Email --> <!-- Add menu entry in Settings/Email -->
<menuitem name="Messages" id="menu_mail_message" parent="base.menu_email" action="action_view_mail_message"/> <menuitem name="Messages" id="menu_mail_message" parent="base.menu_email" action="action_view_mail_message"/>
<record id="action_mail_all_feeds" model="ir.actions.client"> <record id="action_mail_inbox_feeds" model="ir.actions.client">
<field name="name">News Feed</field> <field name="name">Inbox</field>
<field name="tag">mail.wall</field> <field name="tag">mail.wall</field>
<field name="params" eval="&quot;{'domain': [('notification_ids.partner_id.user_ids', 'in', [uid])], <field name="params" eval="&quot;{'domain': [('notification_ids.partner_id.user_ids', 'in', [uid]),('unread', '=', True)],
'context': {'default_model': 'res.users', 'default_res_id': uid} }&quot;"/> 'context': {'default_model': 'res.users', 'default_res_id': uid} }&quot;"/>
</record> </record>
<record id="action_mail_my_feeds" model="ir.actions.client"> <record id="action_mail_archives_feeds" model="ir.actions.client">
<field name="name">My Posts</field> <field name="name">Archives</field>
<field name="tag">mail.wall</field>
<field name="params" eval="&quot;{'domain': [('notification_ids.partner_id.user_ids', 'in', [uid]),('unread', '=', False)],
'context': {'default_model': 'res.users', 'default_res_id': uid} }&quot;"/>
</record>
<record id="action_mail_sent_feeds" model="ir.actions.client">
<field name="name">Sent</field>
<field name="tag">mail.wall</field> <field name="tag">mail.wall</field>
<field name="params" eval="&quot;{'domain': [('author_id.user_ids', 'in', [uid])], <field name="params" eval="&quot;{'domain': [('author_id.user_ids', 'in', [uid])],
'context': {'default_model': 'res.users', 'default_res_id': uid} }&quot;"/> 'context': {'default_model': 'res.users', 'default_res_id': uid} }&quot;"/>

View File

@ -96,6 +96,7 @@ class many2many_reference(fields.many2many):
else: else:
return super(many2many_reference, self).set(cr, model, id, name, values, user, context) return super(many2many_reference, self).set(cr, model, id, name, values, user, context)
class mail_thread(osv.AbstractModel): class mail_thread(osv.AbstractModel):
''' mail_thread model is meant to be inherited by any model that needs to ''' mail_thread model is meant to be inherited by any model that needs to
act as a discussion topic on which messages can be attached. Public act as a discussion topic on which messages can be attached. Public
@ -116,10 +117,13 @@ class mail_thread(osv.AbstractModel):
''' '''
_name = 'mail.thread' _name = 'mail.thread'
_description = 'Email Thread' _description = 'Email Thread'
_mail_autothread = True
def _get_message_data(self, cr, uid, ids, name, args, context=None): def _get_message_data(self, cr, uid, ids, name, args, context=None):
""" Computes:
- message_unread: has uid unread message for the document
- message_summary: html snippet summarizing the Chatter for kanban views """
res = dict((id, dict(message_unread=False, message_summary='')) for id in ids) res = dict((id, dict(message_unread=False, message_summary='')) for id in ids)
partner_id = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
# search for unread messages, by reading directly mail.notification, as SUPERUSER # search for unread messages, by reading directly mail.notification, as SUPERUSER
notif_obj = self.pool.get('mail.notification') notif_obj = self.pool.get('mail.notification')
@ -132,10 +136,41 @@ class mail_thread(osv.AbstractModel):
for notif in notif_obj.browse(cr, SUPERUSER_ID, notif_ids, context=context): for notif in notif_obj.browse(cr, SUPERUSER_ID, notif_ids, context=context):
res[notif.message_id.res_id]['message_unread'] = True res[notif.message_id.res_id]['message_unread'] = True
for thread in self.read(cr, uid, ids, ['message_follower_ids', 'message_comment_ids', 'message_ids'], context=context): for thread in self.browse(cr, uid, ids, context=context):
cls = res[thread['id']]['message_unread'] and ' class="oe_kanban_mail_new"' or '' cls = res[thread.id]['message_unread'] and ' class="oe_kanban_mail_new"' or ''
res[thread['id']]['message_summary'] = "<span%s><span class='oe_e'>9</span> %d</span> <span><span class='oe_e'>+</span> %d</span>" % (cls, len(thread['message_comment_ids']), len(thread['message_follower_ids'])) res[thread.id]['message_summary'] = "<span%s><span class='oe_e'>9</span> %d</span> <span><span class='oe_e'>+</span> %d</span>" % (cls, len(thread.message_comment_ids), len(thread.message_follower_ids))
res[thread['id']]['message_is_follower'] = partner_id in thread['message_follower_ids']
return res
def _get_subscription_data(self, cr, uid, ids, name, args, context=None):
""" Computes:
- message_is_follower: is uid in the document followers
- message_subtype_data: data about document subtypes: which are
available, which are followed if any """
res = dict((id, dict(message_subtype_data='', message_is_follower=False)) for id in ids)
user_pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
# find current model subtypes, add them to a dictionary
subtype_obj = self.pool.get('mail.message.subtype')
subtype_ids = subtype_obj.search(cr, uid, ['|', ('res_model', '=', self._name), ('res_model', '=', False)], context=context)
subtype_dict = dict((subtype.name, dict(default=subtype.default, followed=False, id=subtype.id)) for subtype in subtype_obj.browse(cr, uid, subtype_ids, context=context))
for id in ids:
res[id]['message_subtype_data'] = subtype_dict.copy()
# find the document followers, update the data
fol_obj = self.pool.get('mail.followers')
fol_ids = fol_obj.search(cr, uid, [
('partner_id', '=', user_pid),
('res_id', 'in', ids),
('res_model', '=', self._name),
], context=context)
for fol in fol_obj.browse(cr, uid, fol_ids, context=context):
thread_subtype_dict = res[fol.res_id]['message_subtype_data']
res[fol.res_id]['message_is_follower'] = True
for subtype in fol.subtype_ids:
thread_subtype_dict[subtype.name]['followed'] = True
res[fol.res_id]['message_subtype_data'] = thread_subtype_dict
return res return res
def _search_unread(self, cr, uid, obj=None, name=None, domain=None, context=None): def _search_unread(self, cr, uid, obj=None, name=None, domain=None, context=None):
@ -152,8 +187,13 @@ class mail_thread(osv.AbstractModel):
return [('id', 'in', res.keys())] return [('id', 'in', res.keys())]
_columns = { _columns = {
'message_is_follower': fields.function(_get_message_data, 'message_is_follower': fields.function(_get_subscription_data,
type='boolean', string='Is a Follower', multi='_get_message_data'), type='boolean', string='Is a Follower', multi='_get_subscription_data,'),
'message_subtype_data': fields.function(_get_subscription_data,
type='text', string='Subscription data', multi="_get_subscription_data",
help="Holds data about the subtypes. The content of this field "\
"is a structure holding the current model subtypes, and the "\
"current document followed subtypes."),
'message_follower_ids': many2many_reference('res.partner', 'message_follower_ids': many2many_reference('res.partner',
'mail_followers', 'res_id', 'partner_id', 'mail_followers', 'res_id', 'partner_id',
reference_column='res_model', string='Followers'), reference_column='res_model', string='Followers'),
@ -571,12 +611,12 @@ class mail_thread(osv.AbstractModel):
"now deprecated res.log.") "now deprecated res.log.")
self.message_post(cr, uid, [id], message, context=context) self.message_post(cr, uid, [id], message, context=context)
def message_post(self, cr, uid, thread_id, body='', subject=False, def message_post(self, cr, uid, thread_id, body='', subject=None, type='notification',
type='notification', parent_id=False, attachments=None, context=None, **kwargs): subtype=None, parent_id=False, attachments=None, context=None, **kwargs):
""" Post a new message in an existing thread, returning the new """ Post a new message in an existing thread, returning the new
mail.message ID. Extra keyword arguments will be used as default mail.message ID. Extra keyword arguments will be used as default
column values for the new mail.message record. column values for the new mail.message record.
Auto link messages for same id and object
:param int thread_id: thread ID to post into, or list with one ID :param int thread_id: thread ID to post into, or list with one ID
:param str body: body of the message, usually raw HTML that will :param str body: body of the message, usually raw HTML that will
be sanitized be sanitized
@ -587,9 +627,10 @@ class mail_thread(osv.AbstractModel):
``(name,content)``, where content is NOT base64 encoded ``(name,content)``, where content is NOT base64 encoded
:return: ID of newly created mail.message :return: ID of newly created mail.message
""" """
context = context or {} context = context or {}
attachments = attachments or [] attachments = attachments or []
assert (not thread_id) or isinstance(thread_id, (int,long)) or \ assert (not thread_id) or isinstance(thread_id, (int, long)) or \
(isinstance(thread_id, (list, tuple)) and len(thread_id) == 1), "Invalid thread_id" (isinstance(thread_id, (list, tuple)) and len(thread_id) == 1), "Invalid thread_id"
if isinstance(thread_id, (list, tuple)): if isinstance(thread_id, (list, tuple)):
thread_id = thread_id and thread_id[0] thread_id = thread_id and thread_id[0]
@ -608,39 +649,86 @@ class mail_thread(osv.AbstractModel):
} }
attachment_ids.append((0, 0, data_attach)) attachment_ids.append((0, 0, data_attach))
# get subtype
if not subtype:
subtype = 'mail.mt_comment'
s = subtype.split('.')
if len(s)==1:
s = ('mail', s[0])
ref = self.pool.get('ir.model.data').get_object_reference(cr, uid, s[0], s[1])
subtype_id = ref and ref[1] or False
model = context.get('thread_model', self._name) if thread_id else False
messages = self.pool.get('mail.message')
#auto link messages for same id and object
if self._mail_autothread and thread_id:
message_ids = messages.search(cr, uid, ['&',('res_id', '=', thread_id),('model','=',model)], context=context)
if len(message_ids):
parent_id = min(message_ids)
values = kwargs values = kwargs
values.update({ values.update({
'model': context.get('thread_model', self._name) if thread_id else False, 'model': model,
'res_id': thread_id or False, 'res_id': thread_id or False,
'body': body, 'body': body,
'subject': subject, 'subject': subject or False,
'type': type, 'type': type,
'parent_id': parent_id, 'parent_id': parent_id,
'attachment_ids': attachment_ids, 'attachment_ids': attachment_ids,
'subtype_id': subtype_id,
}) })
for x in ('from', 'to', 'cc'): values.pop(x, None) # Avoid warnings # Avoid warnings about non-existing fields
return self.pool.get('mail.message').create(cr, uid, values, context=context) for x in ('from', 'to', 'cc'):
values.pop(x, None)
return messages.create(cr, uid, values, context=context)
#------------------------------------------------------ #------------------------------------------------------
# Followers API # Followers API
#------------------------------------------------------ #------------------------------------------------------
def message_subscribe_users(self, cr, uid, ids, user_ids=None, context=None): def message_post_api(self, cr, uid, thread_id, body='', subject=False, type='notification',
subtype=None, parent_id=False, attachments=None, context=None, **kwargs):
added_message_id = self.message_post(cr, uid, thread_id=thread_id, body=body, subject=subject, type=type,
subtype=subtype, parent_id=parent_id, attachments=attachments, context=context)
added_message = self.pool.get('mail.message').message_read(cr, uid, [added_message_id])
return added_message
def get_message_subtypes(self, cr, uid, ids, context=None):
""" message_subtype_data: data about document subtypes: which are
available, which are followed if any """
return self._get_subscription_data(cr, uid, ids, None, None, context=context)
def message_subscribe_users(self, cr, uid, ids, user_ids=None, subtype_ids=None, context=None):
""" Wrapper on message_subscribe, using users. If user_ids is not """ Wrapper on message_subscribe, using users. If user_ids is not
provided, subscribe uid instead. """ provided, subscribe uid instead. """
if not user_ids: user_ids = [uid] if not user_ids:
partner_ids = [user['partner_id'][0] for user in self.pool.get('res.users').read(cr, uid, user_ids, ['partner_id'], context=context)] user_ids = [uid]
return self.message_subscribe(cr, uid, ids, partner_ids, context=context) 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, subtype_ids=subtype_ids, context=context)
def message_subscribe(self, cr, uid, ids, partner_ids, context=None): def message_subscribe(self, cr, uid, ids, partner_ids, subtype_ids=None, context=None):
""" Add partners to the records followers. """ """ Add partners to the records followers. """
return self.write(cr, uid, ids, {'message_follower_ids': [(4, pid) for pid in partner_ids]}, context=context) self.write(cr, uid, ids, {'message_follower_ids': [(4, pid) for pid in partner_ids]}, context=context)
# 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, 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, uid, [('res_model', '=', self._name), ('res_id', 'in', ids), ('partner_id', 'in', partner_ids)], context=context)
fol_obj.write(cr, uid, fol_ids, {'subtype_ids': [(6, 0, subtype_ids)]}, context=context)
return True
def message_unsubscribe_users(self, cr, uid, ids, user_ids=None, context=None): def message_unsubscribe_users(self, cr, uid, ids, user_ids=None, context=None):
""" Wrapper on message_subscribe, using users. If user_ids is not """ Wrapper on message_subscribe, using users. If user_ids is not
provided, unsubscribe uid instead. """ provided, unsubscribe uid instead. """
if not user_ids: user_ids = [uid] if not user_ids:
partner_ids = [user['partner_id'][0] for user in self.pool.get('res.users').read(cr, uid, user_ids, ['partner_id'], context=context)] user_ids = [uid]
partner_ids = [user.partner_id.id for user in self.pool.get('res.users').browse(cr, uid, user_ids, context=context)]
return self.message_unsubscribe(cr, uid, ids, partner_ids, context=context) return self.message_unsubscribe(cr, uid, ids, partner_ids, context=context)
def message_unsubscribe(self, cr, uid, ids, partner_ids, context=None): def message_unsubscribe(self, cr, uid, ids, partner_ids, context=None):
@ -674,3 +762,5 @@ class mail_thread(osv.AbstractModel):
partner_id = %s partner_id = %s
''', (ids, self._name, partner_id)) ''', (ids, self._name, partner_id))
return True return True
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -12,16 +12,22 @@
<menuitem id="mail_feeds" name="Feeds" parent="mail.mail_feeds_main" groups="base.group_user" sequence="10"/> <menuitem id="mail_feeds" name="Feeds" parent="mail.mail_feeds_main" groups="base.group_user" sequence="10"/>
<menuitem id="mail_my_stuff" name="Organizer" parent="mail.mail_feeds_main"/> <menuitem id="mail_my_stuff" name="Organizer" parent="mail.mail_feeds_main"/>
<record id="mail_wallfeeds" model="ir.ui.menu"> <record id="mail_inboxfeeds" model="ir.ui.menu">
<field name="name">My Feeds</field> <field name="name">Inbox</field>
<field name="sequence" eval="10"/> <field name="sequence" eval="10"/>
<field name="action" ref="action_mail_all_feeds"/> <field name="action" ref="action_mail_inbox_feeds"/>
<field name="parent_id" ref="mail_feeds"/> <field name="parent_id" ref="mail_feeds"/>
</record> </record>
<record id="mail_myfeeds" model="ir.ui.menu"> <record id="mail_archivesfeeds" model="ir.ui.menu">
<field name="name">My Posts</field> <field name="name">Archives</field>
<field name="sequence" eval="11"/> <field name="sequence" eval="11"/>
<field name="action" ref="action_mail_my_feeds"/> <field name="action" ref="action_mail_archives_feeds"/>
<field name="parent_id" ref="mail_feeds"/>
</record>
<record id="mail_sentfeeds" model="ir.ui.menu">
<field name="name">Sent</field>
<field name="sequence" eval="12"/>
<field name="action" ref="action_mail_sent_feeds"/>
<field name="parent_id" ref="mail_feeds"/> <field name="parent_id" ref="mail_feeds"/>
</record> </record>

View File

@ -25,6 +25,7 @@ class res_partner_mail(osv.Model):
""" Update partner to add a field about notification preferences """ """ Update partner to add a field about notification preferences """
_name = "res.partner" _name = "res.partner"
_inherit = ['res.partner', 'mail.thread'] _inherit = ['res.partner', 'mail.thread']
_mail_autothread = False
_columns = { _columns = {
'notification_email_send': fields.selection([ 'notification_email_send': fields.selection([
@ -41,5 +42,4 @@ class res_partner_mail(osv.Model):
'notification_email_send': lambda *args: 'comment' 'notification_email_send': lambda *args: 'comment'
} }
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -9,7 +9,6 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//sheet" position="after"> <xpath expr="//sheet" position="after">
<div class="oe_chatter"> <div class="oe_chatter">
<field name="message_is_follower" invisible="1"/>
<field name="message_ids" widget="mail_thread" <field name="message_ids" widget="mail_thread"
options='{"thread_level": 1}'/> options='{"thread_level": 1}'/>
<field name="message_follower_ids" widget="mail_followers"/> <field name="message_follower_ids" widget="mail_followers"/>

View File

@ -11,4 +11,7 @@ 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 access_mail_group_user,mail.group.user,model_mail_group,base.group_user,1,1,1,1
access_mail_alias_all,mail.alias.all,model_mail_alias,,1,0,0,0 access_mail_alias_all,mail.alias.all,model_mail_alias,,1,0,0,0
access_mail_alias_user,mail.alias,model_mail_alias,base.group_user,1,1,1,0 access_mail_alias_user,mail.alias,model_mail_alias,base.group_user,1,1,1,0
access_mail_alias_system,mail.alias,model_mail_alias,base.group_system,1,1,1,1
access_mail_message_subtype,mail.message.subtype,model_mail_message_subtype,,1,1,1,1
access_mail_mail_user,mail.mail,model_mail_mail,base.group_user,1,1,1,0
access_mail_vote_all,mail.vote.all,model_mail_vote,,1,1,1,1 access_mail_vote_all,mail.vote.all,model_mail_vote,,1,1,1,1

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
11 access_mail_group_user mail.group.user model_mail_group base.group_user 1 1 1 1
12 access_mail_alias_all mail.alias.all model_mail_alias 1 0 0 0
13 access_mail_alias_user mail.alias model_mail_alias base.group_user 1 1 1 0
14 access_mail_alias_system mail.alias model_mail_alias base.group_system 1 1 1 1
15 access_mail_message_subtype mail.message.subtype model_mail_message_subtype 1 1 1 1
16 access_mail_mail_user mail.mail model_mail_mail base.group_user 1 1 1 0
17 access_mail_vote_all mail.vote.all model_mail_vote 1 1 1 1

View File

@ -14,7 +14,6 @@
margin: 0; margin: 0;
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/* Wall /* Wall
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@ -37,6 +36,21 @@
list-style-type: none; list-style-type: none;
} }
/* ------------------------------------------------------------ */
/* Followers
/* ------------------------------------------------------------ */
.openerp div.oe_mail_recthread_aside h4 {
display: inline-block;
}
.openerp div.oe_mail_recthread_aside button {
position: relative;
}
.openerp div.oe_mail_recthread_aside label,
.openerp div.oe_mail_recthread_aside input {
cursor:pointer;
}
/* Specific display of threads in the wall */ /* Specific display of threads in the wall */
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@ -50,22 +64,12 @@
border-top: 0; border-top: 0;
} }
.openerp div.oe_mail_thread_subthread img { .openerp div.oe_thread_placeholder img {
width: 28px; width: 28px;
height: 28px; height: 28px;
} }
.openerp div.oe_mail_msg_content { .openerp div.oe_thread_placeholder div.oe_mail_msg_content {
position: relative;
width: 486px;
}
.openerp div.oe_mail_msg_content > li {
float: left;
margin-right: 3px;
}
.openerp div.oe_mail_thread_subthread div.oe_mail_msg_content {
width: 440px; width: 440px;
} }
@ -78,7 +82,7 @@
overflow: auto; overflow: auto;
} }
.openerp div.oe_mail_recthread_main { .openerp .oe_mail_record_wall > .oe_mail_wall_threads {
float: left; float: left;
width: 560px; width: 560px;
} }
@ -96,7 +100,7 @@
width: 120px; width: 120px;
} }
.openerp button.oe_mail_button_mouseout { .openerp .oe_mail_recthread_aside .oe_follower.oe_follow {
color: white; color: white;
background-color: #8a89ba; background-color: #8a89ba;
background-image: -webkit-gradient(linear, left top, left bottom, from(#8a89ba), to(#807fb4)); background-image: -webkit-gradient(linear, left top, left bottom, from(#8a89ba), to(#807fb4));
@ -106,9 +110,7 @@
background-image: -o-linear-gradient(top, #8a89ba, #807fb4); background-image: -o-linear-gradient(top, #8a89ba, #807fb4);
background-image: linear-gradient(to bottom, #8a89ba, #807fb4); background-image: linear-gradient(to bottom, #8a89ba, #807fb4);
} }
.openerp .oe_mail_recthread_aside .oe_follower.oe_following {
.openerp button.oe_mail_button_mouseover {
display: none;
color: white; color: white;
background-color: #dc5f59; background-color: #dc5f59;
background-image: -webkit-gradient(linear, left top, left bottom, from(#dc5f59), to(#b33630)); background-image: -webkit-gradient(linear, left top, left bottom, from(#dc5f59), to(#b33630));
@ -119,17 +121,38 @@
background-image: linear-gradient(to bottom, #dc5f59, #b33630); background-image: linear-gradient(to bottom, #dc5f59, #b33630);
} }
.openerp .oe_mail_recthread_aside .oe_follower span {
display:none;
}
.openerp .oe_mail_recthread_aside .oe_following span.oe_following,
.openerp .oe_mail_recthread_aside .oe_notfollow span.oe_follow {
display:block;
}
.openerp div.oe_mail_recthread_followers { .openerp div.oe_mail_recthread_followers {
margin-bottom: 8px; margin-bottom: 8px;
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/* Followers /* subtypes
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
.openerp div.oe_mail_recthread_aside h4 { .openerp .oe_mouse_subtypes {
display: inline-block; display:inline-block;
position: relative;
z-index: 5;
}
.openerp .oe_mouse_subtypes .oe_recthread_subtypes {
position: absolute;
z-index: 2;
}
.openerp .oe_mouse_subtypes.oe_mouseout .oe_recthread_subtypes {
display: none;
}
.openerp .oe_mouse_subtypes.oe_mouseover .oe_recthread_subtypes {
display: block;
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@ -140,6 +163,8 @@
display: none; display: none;
white-space: normal; white-space: normal;
padding: 8px; padding: 8px;
z-index:5;
background: #fff;
} }
.openerp div.oe_mail_thread_action:after { .openerp div.oe_mail_thread_action:after {
@ -168,15 +193,20 @@
-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6); -box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6);
} }
.openerp .oe_mail_vote_count,
.openerp .oe_mail_msg_vote{
vertical-align: bottom;
}
.openerp div.oe_mail_thread_display { .openerp div.oe_mail_thread_display {
white-space: normal; white-space: normal;
} }
.openerp div.oe_mail_thread_subthread { .openerp div.oe_thread_placeholder {
margin-left: 66px; margin-left: 66px;
} }
.openerp div.oe_mail_thread_subthread li.oe_mail_thread_msg:last-child { .openerp div.oe_thread_placeholder li.oe_mail_thread_msg:last-child {
margin-bottom: 8px; margin-bottom: 8px;
} }
@ -191,6 +221,15 @@
clear: both; clear: both;
} }
.openerp li.oe_mail_thread_msg.oe_mail_read,
.openerp li.oe_mail_thread_msg.oe_mail_read div {
background-color: #F0F0F0;
}
.openerp li.oe_mail_thread_msg.oe_mail_read li.oe_mail_thread_msg.oe_mail_unread,
.openerp li.oe_mail_thread_msg.oe_mail_read li.oe_mail_thread_msg.oe_mail_unread div {
background-color: #F6F6F6;
}
.openerp li.oe_mail_thread_msg > div:after { .openerp li.oe_mail_thread_msg > div:after {
content: ""; content: "";
display: block; display: block;
@ -202,18 +241,15 @@
margin: 0 0 4px 0; margin: 0 0 4px 0;
} }
.openerp .oe_mail_msg_notification, .openerp .oe_mail_msg_notification,
.openerp .oe_mail_msg_comment, .openerp .oe_mail_msg_expandable,
.openerp .oe_mail_msg_comment,
.openerp .oe_mail_msg_email { .openerp .oe_mail_msg_email {
padding: 8px; padding: 8px;
background: white; background: white;
border-top: 1px solid #ebebeb; border-top: 1px solid #ebebeb;
} }
.openerp div.oe_mail_thread_subthread .oe_mail_msg_comment {
background: #eee;
}
.openerp .oe_mail_msg_notification:after, .openerp .oe_mail_msg_notification:after,
.openerp .oe_mail_msg_comment:after, .openerp .oe_mail_msg_comment:after,
.openerp .oe_mail_msg_email:after { .openerp .oe_mail_msg_email:after {
@ -222,8 +258,15 @@
clear: both; clear: both;
} }
.openerp .oe_mail_msg_content { .openerp div.oe_mail_msg_content {
float: right;
position: relative;
width: 486px;
}
.openerp div.oe_mail_msg_content > li {
float: left; float: left;
margin-right: 3px;
} }
.openerp .oe_mail_msg_content:after { .openerp .oe_mail_msg_content:after {
@ -266,7 +309,6 @@
display: none; display: none;
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/* mail.compose.message form view & OpenERP hacks /* mail.compose.message form view & OpenERP hacks
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@ -371,15 +413,38 @@
} }
/* Dropdown menu */ /* Dropdown menu */
.openerp .oe_mail_msg_content .oe_dropdown_toggle { /*.openerp .oe_mail_msg_content .oe_dropdown_toggle {
position: absolute; position: absolute;
top: 0px; top: 0px;
right: 3px; right: 3px;
}*/
.openerp .oe_mail .oe_semantic_html_override {
position: relative;
} }
.openerp .oe_mail ul.oe_header {
position: absolute;
right: 3px;
top: -6px;
display: none;
z-index: 10;
height: 18px;
}
.openerp .oe_mail ul.oe_header a {
text-decoration: none;
}
.openerp .oe_mail .oe_semantic_html_override:hover > ul.oe_header {
display: block;
}
.openerp .oe_mail ul.oe_header>li {
display: inline-block;
}
.openerp .oe_mail_msg_content .oe_dropdown_arrow:after { .openerp .oe_mail_msg_content .oe_dropdown_arrow:after {
border-top: 4px solid transparent;
}
.openerp .oe_mail_msg_content:hover .oe_dropdown_arrow:after {
border-top: 4px solid #404040; border-top: 4px solid #404040;
} }

File diff suppressed because it is too large Load Diff

View File

@ -29,7 +29,9 @@ openerp_mail_followers = function(session, mail) {
this.options.context = this.node.attrs.context; this.options.context = this.node.attrs.context;
this.options.comment = this.node.attrs.help || false; this.options.comment = this.node.attrs.help || false;
this.ds_model = new session.web.DataSetSearch(this, this.view.model); this.ds_model = new session.web.DataSetSearch(this, this.view.model);
this.sub_model = new session.web.DataSetSearch(this,'mail.message.subtype');
this.ds_follow = new session.web.DataSetSearch(this, this.field.relation); this.ds_follow = new session.web.DataSetSearch(this, this.field.relation);
this.follower_model = new session.web.DataSetSearch(this,'mail.followers');
}, },
start: function() { start: function() {
@ -51,11 +53,33 @@ openerp_mail_followers = function(session, mail) {
bind_events: function() { bind_events: function() {
var self = this; var self = this;
this.$('button.oe_mail_button_unfollow').on('click', function () { self.do_unfollow(); }) this.$('div.oe_mouse_subtypes')
.mouseover(function () { $(this).html('Unfollow').removeClass('oe_mail_button_mouseout').addClass('oe_mail_button_mouseover'); }) .on('mouseover', function () {
.mouseleave(function () { $(this).html('Following').removeClass('oe_mail_button_mouseover').addClass('oe_mail_button_mouseout'); }); $(this).removeClass('oe_mouseout').addClass('oe_mouseover');
this.$el.on('click', 'button.oe_mail_button_follow', function () { self.do_follow(); }); self.display_subtypes();
this.$el.on('click', 'a.oe_mail_invite', function(event) { })
.on('mouseleave', function () {
$(this).removeClass('oe_mouseover').addClass('oe_mouseout');
self.display_subtypes();
});
this.$('button.oe_follower')
.on('click', function () {
if($(this).hasClass('oe_notfollow'))
self.do_follow();
else
self.do_unfollow();
})
.on('mouseover', function () {
$(this).removeClass('oe_mouseout').addClass('oe_mouseover');
})
.on('mouseleave', function () {
$(this).removeClass('oe_mouseover').addClass('oe_mouseout');
});
this.$el.on('click', 'ul.oe_subtypes input', function () { self.do_update_subscription(); })
this.$el.on('click', 'button.oe_invite', function(event) {
action = { action = {
type: 'ir.actions.act_window', type: 'ir.actions.act_window',
res_model: 'mail.wizard.invite', res_model: 'mail.wizard.invite',
@ -74,28 +98,35 @@ openerp_mail_followers = function(session, mail) {
read_value: function() { read_value: function() {
var self = this; var self = this;
return this.ds_model.read_ids([this.view.datarecord.id], ['message_is_follower', 'message_follower_ids']).then(function (results) { return this.ds_model.read_ids([this.view.datarecord.id], ['message_follower_ids']).pipe(function (results) {
self.set_value(results[0].message_follower_ids, results[0].message_is_follower); self.set_value(results[0].message_follower_ids);
}); });
}, },
set_value: function(value_, message_is_follower) { set_value: function(value_) {
this.reinit(); this.reinit();
if (! this.view.datarecord.id || return this.fetch_followers(value_ || this.get_value());
session.web.BufferedDataSet.virtual_id_regex.test(this.view.datarecord.id)) { },
this.$('div.oe_mail_recthread_aside').hide();
return; set_is_follower: function(value_) {
for(var i in value_){
if(value_[i]['user_ids'][0]==this.session.uid)
this.message_is_follower=true;
this.display_buttons();
return true;
} }
return this.fetch_followers(value_ || this.get_value(), message_is_follower); this.message_is_follower=false;
this.display_buttons();
return false;
}, },
fetch_followers: function (value_, message_is_follower) { fetch_followers: function (value_) {
this.value = value_; this.value = value_ || {};
this.message_is_follower = message_is_follower || (this.getParent().fields.message_is_follower && this.getParent().fields.message_is_follower.get_value()); this.message_is_follower = (this.getParent().fields.message_is_follower && this.getParent().fields.message_is_follower.get_value());
return this.ds_follow.call('read', [value_, ['name', 'user_ids']]).pipe(this.proxy('display_followers'), this.proxy('display_generic')); if(value_)
return this.ds_follow.call('read', [this.value, ['name', 'user_ids']]).pipe(this.proxy('display_followers'), this.proxy('display_generic'));
}, },
/* Display generic info about follower, for people not having access to res_partner */ /* Display generic info about follower, for people not having access to res_partner */
display_generic: function (error, event) { display_generic: function (error, event) {
event.preventDefault(); event.preventDefault();
@ -117,32 +148,88 @@ openerp_mail_followers = function(session, mail) {
display_followers: function (records) { display_followers: function (records) {
var self = this; var self = this;
var node_user_list = this.$('ul.oe_mail_followers_display').empty(); var node_user_list = this.$('ul.oe_mail_followers_display').empty();
this.$('div.oe_mail_recthread_followers h4').html(this.options.title + ' (' + records.length + ')'); this.$('div.oe_mail_recthread_followers h4').html(this.options.title + (records.length>=5 ? ' (' + records.length + ')' : '') );
_(records).each(function (record) { console.log(records);
for(var i=0; i<records.length&&i<5; i++) {
var record=records[i];
record.avatar_url = mail.ChatterUtils.get_image(self.session, 'res.partner', 'image_small', record.id); record.avatar_url = mail.ChatterUtils.get_image(self.session, 'res.partner', 'image_small', record.id);
$(session.web.qweb.render('mail.followers.partner', {'record': record})).appendTo(node_user_list); $(session.web.qweb.render('mail.followers.partner', {'record': record})).appendTo(node_user_list);
}); }
this.display_buttons(); self.set_is_follower(records);
}, },
display_buttons: function () { display_buttons: function () {
this.$('button.oe_mail_button_follow').hide(); if (this.message_is_follower) {
this.$('button.oe_mail_button_unfollow').hide(); this.$('button.oe_follower').removeClass('oe_notfollow').addClass('oe_following');
this.$('span.oe_mail_invite_wrapper').hide(); }
if (! this.view.is_action_enabled('edit')) return; else {
this.$('span.oe_mail_invite_wrapper').show(); this.$('button.oe_follower').removeClass('oe_following').addClass('oe_notfollow');
if (this.message_is_follower) { this.$('button.oe_mail_button_unfollow').show(); } }
else if (this.message_is_follower == false) { this.$('button.oe_mail_button_follow').show(); }
if (this.view.is_action_enabled('edit'))
this.$('span.oe_mail_invite_wrapper').hide();
else
this.$('span.oe_mail_invite_wrapper').show();
}, },
set_subtypes:function(data){
var self = this;
var records = data[this.view.datarecord.id].message_subtype_data;
_(records).each(function (record, record_name) {
record.name = record_name;
record.followed = record.followed || undefined;
$(session.web.qweb.render('mail.followers.subtype', {'record': record})).appendTo( self.$('ul.oe_subtypes') );
});
},
/** Display subtypes: {'name': default, followed} */
display_subtypes: function (visible) {
var self = this;
var recthread_subtypes = self.$('.oe_recthread_subtypes');
subtype_list_ul = self.$('ul.oe_subtypes');
if(subtype_list_ul.is(":empty")) {
var context = new session.web.CompoundContext(this.build_context(), {});
this.ds_model.call('get_message_subtypes',[[self.view.datarecord.id], context]).pipe(this.proxy('set_subtypes'));
}
},
do_follow: function () { do_follow: function () {
var self =this;
_(this.$('.oe_msg_subtype_check')).each(function(record){
$(record).attr('checked','checked');
});
var context = new session.web.CompoundContext(this.build_context(), {}); 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')); return this.ds_model.call('message_subscribe_users', [[this.view.datarecord.id], undefined, undefined, context]).pipe(function(value_){
self.read_value(value_);
if(!self.$('.oe_recthread_subtypes').is(":visible"))
self.display_subtypes(true);
});
}, },
do_unfollow: function () { do_unfollow: function () {
_(this.$('.oe_msg_subtype_check')).each(function(record){
$(record).attr('checked',false);
});
var context = new session.web.CompoundContext(this.build_context(), {}); 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')); return this.ds_model.call('message_unsubscribe_users', [[this.view.datarecord.id], undefined, context]).pipe(this.proxy('read_value'));
}, },
do_update_subscription: function () {
var context = new session.web.CompoundContext(this.build_context(), {});
var self = this;
var checklist = new Array();
_(this.$('.oe_msg_subtype_check')).each(function(record){
if($(record).is(':checked')) {
checklist.push(parseInt($(record).data('id')))}
});
if(!checklist.length)
return this.do_unfollow();
else
return this.ds_model.call('message_subscribe_users',[[self.view.datarecord.id], undefined, checklist, context]).pipe(this.proxy('read_value'));
},
}); });
}; };

View File

@ -47,11 +47,11 @@
Template used to display the communication history in documents Template used to display the communication history in documents
form view. form view.
--> -->
<div t-name="mail.record_thread"> <div t-name="mail.record_thread" class="oe_mail_record_wall">
<!-- <h4>History and Comments</h4> --> <!-- <h4>History and Comments</h4> -->
<div class="oe_mail_recthread_main"> <ul class="oe_mail_wall_threads">
<!-- contains the document thread --> <!-- contains the document thread -->
</div> </ul>
</div> </div>
<!-- <!--
@ -62,12 +62,12 @@
for main thread composition form in document form view. for main thread composition form in document form view.
--> -->
<t t-name="mail.compose_message"> <t t-name="mail.compose_message">
<div> <div class="oe_mail_compose_textarea">
<img class="oe_mail_icon oe_mail_frame oe_left" alt="User img"/> <img class="oe_mail_icon oe_mail_frame oe_left" alt="User img"/>
<div class="oe_mail_msg_content"> <div class="oe_mail_msg_content">
<!-- contains the composition form --> <!-- contains the composition form -->
<!-- default content: old basic textarea --> <!-- default content: old basic textarea -->
<textarea class="oe_mail_compose_textarea" placeholder="Add your comment here..." onfocus="this.value = '';"/> <textarea placeholder="Add your comment here..."/>
</div> </div>
<div class="oe_clear"/> <div class="oe_clear"/>
</div> </div>
@ -93,72 +93,90 @@
container, holding the composition form. Then come the various container, holding the composition form. Then come the various
messages. Then comes the 'more' button. messages. Then comes the 'more' button.
--> -->
<ul t-name="mail.thread" class="oe_mail oe_mail_thread oe_semantic_html_override"> <div t-name="mail.thread" class="oe_mail oe_mail_thread oe_semantic_html_override">
<div class="oe_mail_thread_action"> <div class="oe_mail_thread_action">
<!-- contains the composition box (form + image) --> <!-- contains the composition box (form + image) -->
<t t-call="mail.compose_message"/> <t t-call="mail.compose_message"/>
</div> </div>
<div class="oe_mail_thread_display"> <ul class="oe_mail_thread_display">
<!-- contains the threads --> <!-- contains the threads -->
</div> </ul>
<div class="oe_mail_thread_more"> </div>
<button class="oe_mail_button_more" type="button">Load more messages</button>
</div>
</ul>
<!-- default layout --> <!-- default layout -->
<li t-name="mail.thread.message" class="oe_mail oe_mail_thread_msg"> <li t-name="mail.thread.message" t-attf-class="oe_mail oe_mail_thread_msg #{widget.unread?'oe_mail_unread':'oe_mail_read'}" t-attf-data-msg_id="{widget.id}">
<div t-attf-class="oe_mail_msg_#{record.type} oe_semantic_html_override"> <div t-attf-class="oe_mail_msg_#{widget.type} oe_semantic_html_override">
<img class="oe_mail_icon oe_mail_frame oe_left" t-att-src="record.avatar"/> <!-- message actions (read/unread, reply, delete...) -->
<ul class="oe_header">
<li class="placeholder-mail-vote"><t t-call="mail.thread.message.vote"/></li>
<li t-if="!widget.options.thread.display_on_flat and widget.unread" title="Read"><a class="oe_read oe_e">W</a></li>
<li t-if="!widget.options.thread.display_on_flat and !widget.unread" title="Set back to unread"><a class="oe_unread oe_e">h</a></li>
<li t-if="!widget.options.message.show_reply_by_email" title="Reply"><a class="oe_reply oe_e">)</a></li>
<li t-if="widget.options.message.show_reply_by_email"><a class="oe_reply_by_email oe_e" title="Reply by mail">)</a></li>
<t t-if="(widget.options.message.show_reply || widget.options.message.show_reply_by_email || (widget.is_author and widget.options.message.show_dd_delete) || widget.type == 'email')">
<li>
<span class="oe_dropdown_toggle">
<a class="oe_e" title="More options">í</a>
<ul class="oe_dropdown_menu">
<li t-if="widget.is_author and widget.options.message.show_dd_delete"><a class="oe_mail_msg_delete">Delete</a></li>
<!-- Uncomment when adding subtype hiding
<li t-if="display['show_hide']">
<a href="#" class="oe_mail_msg_hide_type" t-attf-data-subtype='{widget.subtype}'>Hide '<t t-esc="widget.subtype"/>' for this document</a>
</li> -->
<li t-if="widget.options.message.show_reply" title="Reply"><a class="oe_reply oe_full_reply">Full reply</a></li>
<li t-if="widget.options.message.show_reply_by_email"><a class="oe_reply_by_email oe_full_reply" title="Reply by mail">Full reply</a></li>
<li t-if="widget.type == 'email'"><a class="oe_mail_msg_details" t-attf-href="#model=mail.message&amp;id=#{widget.id}" >Details</a></li>
</ul>
</span>
</li>
</t>
</ul>
<a t-attf-href="#model=res.partner&amp;id=#{widget.author_id[0]}" t-att-title="widget.author_id[1]">
<img class="oe_mail_icon oe_mail_frame oe_left" t-att-src="widget.avatar"/>
</a>
<div class="oe_mail_msg_content"> <div class="oe_mail_msg_content">
<!-- 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 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']">
<a href="#" class="oe_mail_msg_hide_type" t-attf-data-subtype='{record.subtype}'>Hide '<t t-esc="record.subtype"/>' for this document</a>
</li> -->
<li t-if="options.show_dd_reply_by_email"><a class="oe_mail_msg_reply_by_email" t-attf-data-msg_id="{record.id}">Quote and reply</a></li>
<li t-if="record.type == 'email'"><a class="oe_mail_msg_details" t-attf-href="#model=mail.message&amp;id=#{record.id}" >Details</a></li>
</ul>
</span>
<!-- message itself --> <!-- message itself -->
<div class="oe_mail_msg"> <div class="oe_mail_msg">
<h1 t-if="record.subject" class="oe_mail_msg_title"> <h1 t-if="widget.subject" class="oe_mail_msg_title">
<t t-raw="record.subject"/> <t t-raw="widget.subject"/>
</h1> </h1>
<div class="oe_mail_msg_body">
<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"> <ul class="oe_mail_msg_footer">
<li t-if="options.show_record_name and record.record_name and record.subject and options.thread_level > 0"> <li t-if="widget.author_id"><a t-attf-href="#model=res.partner&amp;id=#{widget.author_id[0]}"><t t-raw="widget.author_id[1]"/></a></li>
<a t-attf-href="#model=#{record.model}&amp;id=#{record.res_id}"><t t-raw="record.record_name"/></a> <li><span t-att-title="widget.date"><t t-raw="widget.timerelative"/></span></li>
</li>
<li t-if="record.author_id"><a t-attf-href="#model=res.partner&amp;id=#{record.author_id[0]}"><t t-raw="record.author_id[1]"/></a></li> <li t-if="widget.attachment_ids.length > 0">
<li><span t-att-title="record.date"><t t-raw="record.timerelative"/></span></li>
<t t-call="mail.thread.message.vote"/>
<li t-if="options.show_reply"><a class="oe_mail_msg_reply">Reply</a></li>
<li t-if="options.show_reply_by_email"><a class="oe_mail_msg_reply_by_email" t-attf-data-msg_id="{record.id}">Reply</a></li>
<li t-if="record.attachment_ids.length > 0">
<a class="oe_mail_msg_view_attachments"> <a class="oe_mail_msg_view_attachments">
<t t-if="record.attachment_ids.length == 1">1 Attachment</t> <t t-if="widget.attachment_ids.length == 1">1 Attachment</t>
<t t-if="record.attachment_ids.length > 1"><t t-raw="record.attachment_ids.length"/> Attachments</t> <t t-if="widget.attachment_ids.length > 1"><t t-raw="widget.attachment_ids.length"/> Attachments</t>
</a> </a>
</li> </li>
</ul> </ul>
<t t-if="record.attachment_ids.length > 0"> <div class="oe_clear"/>
<div class="oe_mail_msg_body">
<t t-if="widget.options.message.show_record_name and widget.record_name and (!widget.subject) and widget.options.thread._parents.length&lt;=widget.options.thread.thread_level and widget.model!='res.partner'">
<a class="oe_mail_action_model" t-attf-href="#model=#{widget.model}&amp;id=#{widget.res_id}"><t t-raw="widget.record_name"/></a>
</t>
<t t-raw="widget.body"/>
</div>
<t t-if="widget.attachment_ids.length > 0">
<div class="oe_clear"></div> <div class="oe_clear"></div>
<t t-call="mail.thread.message.attachments"/> <t t-call="mail.thread.message.attachments"/>
</t> </t>
</div> </div>
</div> </div>
</div> </div>
<div class="oe_thread_placeholder"></div>
</li>
<!-- expandable message layout -->
<li t-name="mail.thread.expandable" t-attf-class="oe_mail oe_mail_thread_msg oe_mail_unread" t-attf-data-thread_id="{widget.id}">
<div t-attf-class="oe_mail_msg_#{widget.type} oe_semantic_html_override">
<div class="oe_mail_msg_content oe_mail_msg_more_message">
<a class="oe_mail_fetch_more">Load more messages <span t-if="widget.nb_messages>0">(<t t-raw="widget.nb_messages"/> messages not display)</span>...</a>
</div>
</div>
</li> </li>
<!-- <!--
@ -181,14 +199,25 @@
<!-- mail.thread.message.vote <!-- mail.thread.message.vote
Template used to display Like/Unlike in a mail.message Template used to display Like/Unlike in a mail.message
--> -->
<li t-name="mail.thread.message.vote"> <span t-name="mail.thread.message.vote">
<t t-if='record.vote_user_ids.length > 0'> <span class="oe_left oe_mail_vote_count">
<span class="oe_left oe_mail_vote_count"><t t-esc="record.vote_user_ids.length"/> votes</span> <t t-if='widget.has_voted'>
</t> You
<button t-attf-class="oe_mail_msg_vote oe_tag" t-attf-data-msg_id="{record.id}"> </t>
<t t-if="! record.has_voted"><span>+1</span></t> <t t-if='(widget.vote_user_ids.length-(widget.has_voted?1:0)) > 0'>
<t t-if="record.has_voted"><span>-1</span></t> <t t-if='widget.has_voted'> and </t>
<t t-esc="widget.vote_user_ids.length"/> people
</t>
<t t-if='widget.vote_user_ids.length > 0'>
agree
</t>
</span>
<button t-attf-class="oe_mail_msg_vote oe_tag">
<span>
<t t-if="!widget.has_voted">Agree</t>
<t t-if="widget.has_voted">Unagree</t>
</span>
</button> </button>
</li> </span>
</template> </template>

View File

@ -3,12 +3,20 @@
<!-- <!--
followers main template followers main template
Template used to display the followers and the actions in a record. Template used to display the followers, the actions and the subtypes in a record.
--> -->
<div t-name="mail.followers" class="oe_mail_recthread_aside oe_semantic_html_override"> <div t-name="mail.followers" class="oe_mail_recthread_aside oe_semantic_html_override">
<div class="oe_mail_recthread_actions"> <div class="oe_mail_recthread_actions">
<button type="button" class="oe_mail_button_follow">Follow</button> <div class="oe_mouse_subtypes">
<button type="button" class="oe_mail_button_unfollow oe_mail_button_mouseout">Following</button> <button type="button" class="oe_follower oe_notfollow">
<span class="oe_follow">Follow</span>
<span class="oe_unfollow">Unfollow</span>
<span class="oe_following">Following</span>
</button>
<div class="oe_recthread_subtypes">
<ul class="oe_subtypes"></ul>
</div>
</div>
</div> </div>
<div class="oe_grey"> <div class="oe_grey">
<t t-if="widget.options.comment"> <t t-if="widget.options.comment">
@ -16,8 +24,9 @@
</t> </t>
</div> </div>
<div class="oe_mail_recthread_followers"> <div class="oe_mail_recthread_followers">
<button type="button" class="oe_invite"><span>Invite</span></button>
<t t-if="widget.options.title"> <t t-if="widget.options.title">
<h4><t t-raw="widget.options.title"/></h4><span class="oe_mail_invite_wrapper"> · <a class="oe_mail_invite" >Invite partners</a></span> <h4><t t-raw="widget.options.title"/></h4>
</t> </t>
<ul class="oe_mail_followers_display"></ul> <ul class="oe_mail_followers_display"></ul>
</div> </div>
@ -30,6 +39,19 @@
<li t-name="mail.followers.partner"> <li t-name="mail.followers.partner">
<img class="oe_mail_thumbnail oe_mail_frame" t-attf-src="{record.avatar_url}"/> <img class="oe_mail_thumbnail oe_mail_frame" t-attf-src="{record.avatar_url}"/>
<a t-attf-href="#model=res.partner&amp;id=#{record.id}"><t t-raw="record.name"/></a> <a t-attf-href="#model=res.partner&amp;id=#{record.id}"><t t-raw="record.name"/></a>
</li>\
<!--
followers.subtype template
Template used to display message subtypes of a follower subscription
-->
<li t-name="mail.followers.subtype">
<table width="50%">
<tr>
<td><label t-att-for="'input_mail_followers_subtype_'+record.id"><t t-raw="record.name"/></label></td>
<td width="10%"><input type="checkbox" t-att-checked="record.followed" t-att-id="'input_mail_followers_subtype_'+record.id" t-att-data-id="record.id" t-att-name="record.name" class="oe_msg_subtype_check"/></td>
</tr>
</table>
</li> </li>
</template> </template>

View File

@ -131,9 +131,12 @@ class test_mail(TestMailMockups):
self.mail_message = self.registry('mail.message') self.mail_message = self.registry('mail.message')
self.mail_notification = self.registry('mail.notification') self.mail_notification = self.registry('mail.notification')
self.mail_followers = self.registry('mail.followers') 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_users = self.registry('res.users')
self.res_partner = self.registry('res.partner') self.res_partner = self.registry('res.partner')
self.user_demo = self.registry('ir.model.data').get_object_reference(self.cr, self.uid, 'base', 'user_demo')[1]
# Mock send_get_mail_body to test its functionality without other addons override # 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._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 self.registry('mail.mail').send_get_mail_body = self._mock_send_get_mail_body
@ -252,30 +255,64 @@ class test_mail(TestMailMockups):
follower_ids = set([follower.partner_id.id for follower in self.mail_followers.browse(cr, uid, fol_obj_ids)]) follower_ids = set([follower.partner_id.id for follower in self.mail_followers.browse(cr, uid, fol_obj_ids)])
self.assertEqual(follower_ids, set([partner_bert_id, user_admin.partner_id.id]), 'Bert and Admin should be the followers of dummy mail.group data') self.assertEqual(follower_ids, set([partner_bert_id, user_admin.partner_id.id]), 'Bert and Admin should be the followers of dummy mail.group data')
def test_11_message_followers(self): def test_11_message_followers_and_subtypes(self):
""" Tests designed for the subscriber API. """ """ Tests designed for the subscriber API as well as message subtypes """
cr, uid = self.cr, self.uid cr, uid = self.cr, self.uid
user_admin = self.res_users.browse(cr, uid, uid) user_admin = self.res_users.browse(cr, uid, uid)
group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id) group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
# Data: user Raoul
# Create user Raoul
user_raoul_id = self.res_users.create(cr, uid, {'name': 'Raoul Grosbedon', 'login': 'raoul'}) user_raoul_id = self.res_users.create(cr, uid, {'name': 'Raoul Grosbedon', 'login': 'raoul'})
user_raoul = self.res_users.browse(cr, uid, user_raoul_id) user_raoul = self.res_users.browse(cr, uid, user_raoul_id)
# Data: message subtypes
self.mail_message_subtype.create(cr, uid, {'name': 'mt_mg_def', 'default': True, 'res_model': 'mail.group'})
self.mail_message_subtype.create(cr, uid, {'name': 'mt_other_def', 'default': True, 'res_model': 'crm.lead'})
self.mail_message_subtype.create(cr, uid, {'name': 'mt_all_def', 'default': True, 'res_model': False})
mt_mg_nodef = self.mail_message_subtype.create(cr, uid, {'name': 'mt_mg_nodef', 'default': False, 'res_model': 'mail.group'})
mt_all_nodef = self.mail_message_subtype.create(cr, uid, {'name': 'mt_all_nodef', 'default': False, 'res_model': False})
default_group_subtypes = self.mail_message_subtype.search(cr, uid, [('default', '=', True), '|', ('res_model', '=', 'mail.group'), ('res_model', '=', False)])
# Subscribe Raoul three times (niak niak) through message_subscribe_users # ----------------------------------------
# CASE1: test subscriptions with subtypes
# ----------------------------------------
# Do: Subscribe Raoul three times (niak niak) through message_subscribe_users
group_pigs.message_subscribe_users([user_raoul_id, user_raoul_id]) group_pigs.message_subscribe_users([user_raoul_id, user_raoul_id])
group_pigs.message_subscribe_users([user_raoul_id]) group_pigs.message_subscribe_users([user_raoul_id])
group_pigs.refresh() group_pigs.refresh()
# Test: 2 followers (Admin and Raoul)
follower_ids = [follower.id for follower in group_pigs.message_follower_ids] follower_ids = [follower.id for follower in group_pigs.message_follower_ids]
self.assertEqual(len(follower_ids), 2, 'There should be 2 Pigs fans') self.assertEqual(len(follower_ids), 2, 'There should be 2 Pigs fans')
self.assertEqual(set(follower_ids), set([user_raoul.partner_id.id, user_admin.partner_id.id]), 'Admin and Raoul should be the only 2 Pigs fans') self.assertEqual(set(follower_ids), set([user_raoul.partner_id.id, user_admin.partner_id.id]), 'Admin and Raoul should be the only 2 Pigs fans')
# Test: Raoul follows default subtypes
fol_ids = self.mail_followers.search(cr, uid, [('res_model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id), ('partner_id', '=', user_raoul.partner_id.id)])
fol_obj = self.mail_followers.browse(cr, uid, fol_ids)[0]
fol_subtype_ids = set([subtype.id for subtype in fol_obj.subtype_ids])
self.assertEqual(set(fol_subtype_ids), set(default_group_subtypes), 'subscription subtypes are incorrect')
# Unsubscribe Raoul twice through message_unsubscribe_users # Do: Unsubscribe Raoul twice through message_unsubscribe_users
group_pigs.message_unsubscribe_users([user_raoul_id, user_raoul_id]) group_pigs.message_unsubscribe_users([user_raoul_id, user_raoul_id])
group_pigs.refresh() group_pigs.refresh()
# Test: 1 follower (Admin)
follower_ids = [follower.id for follower in group_pigs.message_follower_ids] follower_ids = [follower.id for follower in group_pigs.message_follower_ids]
self.assertEqual(follower_ids, [user_admin.partner_id.id], 'Admin must be the only Pigs fan') self.assertEqual(follower_ids, [user_admin.partner_id.id], 'Admin must be the only Pigs fan')
# Do: subscribe Admin with subtype_ids
group_pigs.message_subscribe_users([uid], [mt_mg_nodef, mt_all_nodef])
fol_ids = self.mail_followers.search(cr, uid, [('res_model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id), ('partner_id', '=', user_admin.partner_id.id)])
fol_obj = self.mail_followers.browse(cr, uid, fol_ids)[0]
fol_subtype_ids = set([subtype.id for subtype in fol_obj.subtype_ids])
self.assertEqual(set(fol_subtype_ids), set([mt_mg_nodef, mt_all_nodef]), 'subscription subtypes are incorrect')
# ----------------------------------------
# CASE2: test mail_thread fields
# ----------------------------------------
group_pigs.refresh()
self.assertEqual(set(group_pigs.message_subtype_data.keys()), set(['comment', 'mt_mg_def', 'mt_all_def', 'mt_mg_nodef', 'mt_all_nodef']), 'mail.group available subtypes incorrect')
self.assertFalse(group_pigs.message_subtype_data['comment']['followed'], 'Admin should not follow comments in pigs')
self.assertTrue(group_pigs.message_subtype_data['mt_mg_nodef']['followed'], 'Admin should follow mt_mg_nodef in pigs')
self.assertTrue(group_pigs.message_subtype_data['mt_all_nodef']['followed'], 'Admin should follow mt_all_nodef in pigs')
def test_20_message_post(self): def test_20_message_post(self):
""" Tests designed for message_post. """ """ Tests designed for message_post. """
cr, uid = self.cr, self.uid cr, uid = self.cr, self.uid
@ -283,8 +320,6 @@ class test_mail(TestMailMockups):
user_admin = self.res_users.browse(cr, uid, uid) user_admin = self.res_users.browse(cr, uid, uid)
group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id) group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
# 0 - Admin
p_a_id = user_admin.partner_id.id
# 1 - Bert Tartopoils, with email, should receive emails for comments and emails # 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'}) p_b_id = self.res_partner.create(cr, uid, {'name': 'Bert Tartopoils', 'email': 'b@b'})
# 2 - Carine Poilvache, with email, should never receive emails # 2 - Carine Poilvache, with email, should never receive emails
@ -306,9 +341,12 @@ class test_mail(TestMailMockups):
_mail_bodyalt2 = 'Pigs rules\nAdmin' _mail_bodyalt2 = 'Pigs rules\nAdmin'
_attachments = [('First', 'My first attachment'), ('Second', 'My second attachment')] _attachments = [('First', 'My first attachment'), ('Second', 'My second attachment')]
# ----------------------------------------
# CASE1: post comment, body and subject specified # CASE1: post comment, body and subject specified
# ----------------------------------------
self._init_mock_build_email() self._init_mock_build_email()
msg_id = self.mail_group.message_post(cr, uid, self.group_pigs_id, body=_body1, subject=_subject, type='comment') msg_id = self.mail_group.message_post(cr, uid, self.group_pigs_id, body=_body1, subject=_subject, type='comment', subtype='mt_comment')
message = self.mail_message.browse(cr, uid, msg_id) message = self.mail_message.browse(cr, uid, msg_id)
sent_emails = self._build_email_kwargs_list sent_emails = self._build_email_kwargs_list
# Test: notifications have been deleted # Test: notifications have been deleted
@ -325,7 +363,7 @@ class test_mail(TestMailMockups):
self.assertIn(sent_email['body_alternative'], _mail_bodyalt1 + '\nBert Tartopoils\n', 'sent_email body_alternative is incorrect') self.assertIn(sent_email['body_alternative'], _mail_bodyalt1 + '\nBert Tartopoils\n', 'sent_email body_alternative is incorrect')
# Test: mail_message: partner_ids = group followers # Test: mail_message: partner_ids = group followers
message_pids = set([partner.id for partner in message.partner_ids]) message_pids = set([partner.id for partner in message.partner_ids])
test_pids = set([p_a_id, p_b_id, p_c_id]) test_pids = set([p_b_id, p_c_id])
self.assertEqual(test_pids, message_pids, 'mail.message partners incorrect') self.assertEqual(test_pids, message_pids, 'mail.message partners incorrect')
# Test: notification linked to this message = group followers = partner_ids # Test: notification linked to this message = group followers = partner_ids
notif_ids = self.mail_notification.search(cr, uid, [('message_id', '=', message.id)]) notif_ids = self.mail_notification.search(cr, uid, [('message_id', '=', message.id)])
@ -335,10 +373,13 @@ class test_mail(TestMailMockups):
for sent_email in sent_emails: for sent_email in sent_emails:
self.assertEqual(sent_email['email_to'], ['b@b'], 'sent_email email_to is incorrect') self.assertEqual(sent_email['email_to'], ['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
# ----------------------------------------
# TESTS: automatic subject, signature in body_html, attachments propagation # TESTS: automatic subject, signature in body_html, attachments propagation
self._init_mock_build_email() self._init_mock_build_email()
msg_id2 = self.mail_group.message_post(cr, uid, self.group_pigs_id, body=_body2, type='email', msg_id2 = self.mail_group.message_post(cr, uid, self.group_pigs_id, body=_body2, type='email', subtype='mt_comment',
partner_ids=[(6, 0, [p_d_id])], parent_id=msg_id, attachments=_attachments) partner_ids=[(6, 0, [p_d_id])], parent_id=msg_id, attachments=_attachments)
message = self.mail_message.browse(cr, uid, msg_id2) message = self.mail_message.browse(cr, uid, msg_id2)
sent_emails = self._build_email_kwargs_list sent_emails = self._build_email_kwargs_list
@ -356,7 +397,7 @@ class test_mail(TestMailMockups):
self.assertIn(_mail_bodyalt2, sent_email['body_alternative'], 'sent_email body_alternative incorrect') self.assertIn(_mail_bodyalt2, sent_email['body_alternative'], 'sent_email body_alternative incorrect')
# Test: mail_message: partner_ids = group followers # Test: mail_message: partner_ids = group followers
message_pids = set([partner.id for partner in message.partner_ids]) 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]) test_pids = set([p_b_id, p_c_id, p_d_id])
self.assertEqual(message_pids, test_pids, 'mail.message partners incorrect') self.assertEqual(message_pids, test_pids, 'mail.message partners incorrect')
# Test: notifications linked to this message = group followers = partner_ids # Test: notifications linked to this message = group followers = partner_ids
notif_ids = self.mail_notification.search(cr, uid, [('message_id', '=', message.id)]) notif_ids = self.mail_notification.search(cr, uid, [('message_id', '=', message.id)])
@ -394,8 +435,6 @@ class test_mail(TestMailMockups):
_attachments_test = [('first.txt', 'My first attachment'), ('second.txt', 'My second attachment')] _attachments_test = [('first.txt', 'My first attachment'), ('second.txt', 'My second attachment')]
# Create partners # Create partners
# 0 - Admin
p_a_id = user_admin.partner_id.id
# 1 - Bert Tartopoils, with email, should receive emails for comments and emails # 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'}) p_b_id = self.res_partner.create(cr, uid, {'name': 'Bert Tartopoils', 'email': 'b@b'})
# 2 - Carine Poilvache, with email, should never receive emails # 2 - Carine Poilvache, with email, should never receive emails
@ -429,9 +468,10 @@ class test_mail(TestMailMockups):
self.assertEqual(message.body, _msg_body, 'mail.message incorrect body') self.assertEqual(message.body, _msg_body, 'mail.message incorrect body')
# Test: mail.message: partner_ids = entries in mail.notification: group_pigs fans (a, b) + mail.compose.message partner_ids (c, d) # Test: mail.message: partner_ids = entries in mail.notification: group_pigs fans (a, b) + mail.compose.message partner_ids (c, d)
msg_pids = [partner.id for partner in message.partner_ids] msg_pids = [partner.id for partner in message.partner_ids]
test_pids = [p_a_id, p_b_id, p_c_id, p_d_id] test_pids = [p_b_id, p_c_id, p_d_id]
notif_ids = self.mail_notification.search(cr, uid, [('message_id', '=', message.id)]) notif_ids = self.mail_notification.search(cr, uid, [('message_id', '=', message.id)])
self.assertEqual(len(notif_ids), 4, 'mail.message: too much notifications created')
self.assertEqual(len(notif_ids), 3, 'mail.message: too much notifications created')
self.assertEqual(set(msg_pids), set(test_pids), 'mail.message partner_ids incorrect') self.assertEqual(set(msg_pids), set(test_pids), 'mail.message partner_ids incorrect')
# ---------------------------------------- # ----------------------------------------
@ -449,22 +489,15 @@ class test_mail(TestMailMockups):
self.assertEqual(compose.res_id, self.group_pigs_id, 'mail.compose.message incorrect res_id') self.assertEqual(compose.res_id, self.group_pigs_id, 'mail.compose.message incorrect res_id')
self.assertEqual(compose.parent_id.id, message.id, 'mail.compose.message incorrect parent_id') self.assertEqual(compose.parent_id.id, message.id, 'mail.compose.message incorrect parent_id')
self.assertEqual(compose.content_subtype, 'html', 'mail.compose.message incorrect content_subtype') self.assertEqual(compose.content_subtype, 'html', 'mail.compose.message incorrect content_subtype')
# 2. Post the comment, get created message
parent_id = message.id
mail_compose.send_mail(cr, uid, [compose_id])
group_pigs.refresh()
message = group_pigs.message_ids[0]
# Test: mail.message: subject as Re:.., body in html, parent_id # Test: mail.message: subject as Re:.., body in html, parent_id
self.assertEqual(message.subject, _msg_reply, 'mail.message incorrect subject') self.assertEqual(compose.subject, _msg_reply, 'mail.message incorrect subject')
self.assertIn('Administrator wrote:<blockquote><pre>Pigs rules</pre></blockquote></div>', message.body, 'mail.message body is incorrect') self.assertIn('Administrator wrote:<blockquote><pre>Pigs rules</pre></blockquote>', compose.body, 'mail.message body is incorrect')
self.assertEqual(message.parent_id and message.parent_id.id, parent_id, 'mail.message parent_id incorrect') self.assertEqual(compose.parent_id and compose.parent_id.id, message.id, 'mail.message parent_id incorrect')
# Test: mail.message: attachments # Test: mail.message: attachments
for attach in message.attachment_ids: for attach in compose.attachment_ids:
self.assertEqual(attach.res_model, 'mail.group', 'mail.message attachment res_model incorrect') 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.assertEqual(attach.res_id, self.group_pigs_id, 'mail.message attachment res_id incorrect')
self.assertIn((attach.name, attach.datas.decode('base64')), _attachments_test, self.assertIn((attach.datas_fname, attach.datas.decode('base64')), _attachments_test, 'mail.message attachment name / data incorrect')
'mail.message attachment name / data incorrect')
# ---------------------------------------- # ----------------------------------------
# CASE3: mass_mail on Pigs and Bird # CASE3: mass_mail on Pigs and Bird
@ -472,8 +505,8 @@ class test_mail(TestMailMockups):
# 1. mass_mail on pigs and bird # 1. mass_mail on pigs and bird
compose_id = mail_compose.create(cr, uid, compose_id = mail_compose.create(cr, uid,
{'subject': _subject, 'body': '${object.description}'}, {'subject': _subject, 'body': '${object.description}', 'content_type': 'html'},
{'default_composition_mode': 'mass_mail', 'default_model': 'mail.group', 'default_res_id': -1, {'default_composition_mode': 'mass_mail', 'default_model': 'mail.group', 'default_res_id': False,
'active_ids': [self.group_pigs_id, group_bird_id]}) 'active_ids': [self.group_pigs_id, group_bird_id]})
compose = mail_compose.browse(cr, uid, compose_id) compose = mail_compose.browse(cr, uid, compose_id)
# Test: content_subtype is html # Test: content_subtype is html
@ -496,107 +529,114 @@ class test_mail(TestMailMockups):
self.assertEqual(message2.subject, _subject, 'mail.message subject incorrect') self.assertEqual(message2.subject, _subject, 'mail.message subject incorrect')
self.assertEqual(message2.body, group_bird.description, 'mail.message body incorrect') self.assertEqual(message2.body, group_bird.description, 'mail.message body incorrect')
def test_30_message_read(self): # FP Note: to be reviewed to be more generic, not depending on the algorythm of
""" Tests designed for message_read. """ # message_read
# TDE NOTE: this test is not finished, as the message_read method is not fully specified. #def test_30_message_read(self):
# It will be updated as soon as we have fixed specs ! # """ Tests designed for message_read. """
cr, uid = self.cr, self.uid # # TDE NOTE: this test is not finished, as the message_read method is not fully specified.
group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id) # # It will be updated as soon as we have fixed specs !
# cr, uid = self.cr, self.uid
# group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
def _compare_structures(struct1, struct2, n=0): # def _compare_structures(struct1, struct2, n=0):
# print '%scompare structure' % ('\t' * n) # # print '%scompare structure' % ('\t' * n)
self.assertEqual(len(struct1), len(struct2), 'message_read structure number of childs incorrect') # # self.assertEqual(len(struct1), len(struct2), 'message_read structure number of childs incorrect')
for x in range(len(struct1)):
# print '%s' % ('\t' * n), struct1[x]['id'], struct2[x]['id'], struct1[x].get('subject') or ''
self.assertEqual(struct1[x]['id'], struct2[x]['id'], 'message_read failure %s' % struct1[x].get('subject'))
_compare_structures(struct1[x]['child_ids'], struct2[x]['child_ids'], n + 1)
# print '%send compare' % ('\t' * n)
# ---------------------------------------- # for x in range(len(struct1)):
# CASE1: Flattening test # if struct1[x].get('type') == 'expandable':
# ---------------------------------------- # continue
# # print '%s' % ('\t' * n), struct1[x]['id'], struct1[x]['child_nbr'], struct2[x]['id'], struct2[x].get('child_nbr', 'XX'), struct1[x].get('subject') or ''
# self.assertEqual(struct1[x]['id'], struct2[x]['id'], 'message_read failure %s' % struct1[x].get('subject'))
# _compare_structures(struct1[x]['child_ids'], struct2[x]['child_ids'], n + 1)
# # print '%send compare' % ('\t' * n)
# Create dummy message structure # # ----------------------------------------
import copy # # CASE1: Flattening test
tree = [{'id': 2, 'child_ids': [ # # ----------------------------------------
{'id': 6, 'child_ids': [
{'id': 8, 'child_ids': []},
]},
]},
{'id': 1, 'child_ids':[
{'id': 7, 'child_ids': [
{'id': 9, 'child_ids': []},
]},
{'id': 4, 'child_ids': [
{'id': 10, 'child_ids': []},
{'id': 5, 'child_ids': []},
]},
{'id': 3, 'child_ids': []},
]},
]
# Test: completely flat
new_tree = self.mail_message.message_read_tree_flatten(cr, uid, copy.deepcopy(tree), 0, 0)
self.assertEqual(len(new_tree), 10, 'message_read_tree_flatten wrong in flat')
# Test: 1 thread level
tree_test = [{'id': 2, 'child_ids': [
{'id': 8, 'child_ids': []}, {'id': 6, 'child_ids': []},
]},
{'id': 1, 'child_ids': [
{'id': 10, 'child_ids': []}, {'id': 9, 'child_ids': []},
{'id': 7, 'child_ids': []}, {'id': 5, 'child_ids': []},
{'id': 4, 'child_ids': []}, {'id': 3, 'child_ids': []},
]},
]
new_tree = self.mail_message.message_read_tree_flatten(cr, uid, copy.deepcopy(tree), 0, 1)
_compare_structures(new_tree, tree_test)
# Test: 2 thread levels
new_tree = self.mail_message.message_read_tree_flatten(cr, uid, copy.deepcopy(tree), 0, 2)
_compare_structures(new_tree, tree)
# ---------------------------------------- # # Create dummy message structure
# CASE2: message_read test # import copy
# ---------------------------------------- # tree = [{'id': 2, 'child_nbr': 1, 'child_ids': [
# {'id': 6, 'child_nbr': 1, 'child_ids': [
# {'id': 8, 'child_nbr': 0, 'child_ids': []},
# ]},
# ]},
# {'id': 1, 'child_nbr': 3, 'child_ids':[
# {'id': 7, 'child_nbr': 1, 'child_ids': [
# {'id': 9, 'child_nbr': 0, 'child_ids': []},
# ]},
# {'id': 4, 'child_nbr': 2, 'child_ids': [
# {'id': 10, 'child_nbr': 0, 'child_ids': []},
# {'id': 5, 'child_nbr': 0, 'child_ids': []},
# ]},
# {'id': 3, 'child_nbr': 0, 'child_ids': []},
# ]},
# ]
# # Test: completely flat
# new_tree = self.mail_message.message_read_tree_flatten(cr, uid, None, copy.deepcopy(tree), [('type', 'in', 'borderlands')], 0, limit=15, add_expandable=False)
# _compare_structures(new_tree, new_tree)
# self.assertEqual(len(new_tree), 10, 'message_read_tree_flatten wrong in flat')
# # Test: 1 thread level
# tree_test = [{'id': 2, 'child_ids': [
# {'id': 8, 'child_ids': []}, {'id': 6, 'child_ids': []},
# ]},
# {'id': 1, 'child_ids': [
# {'id': 10, 'child_ids': []}, {'id': 9, 'child_ids': []},
# {'id': 7, 'child_ids': []}, {'id': 5, 'child_ids': []},
# {'id': 4, 'child_ids': []}, {'id': 3, 'child_ids': []},
# ]},
# ]
# new_tree = self.mail_message.message_read_tree_flatten(cr, uid, None, copy.deepcopy(tree), [('type', 'in', 'borderlands')], 1, limit=15, add_expandable=False)
# _compare_structures(new_tree, tree_test)
# # Test: 2 thread levels
# new_tree = self.mail_message.message_read_tree_flatten(cr, uid, None, copy.deepcopy(tree), [('type', 'in', 'borderlands')], 2, limit=15, add_expandable=False)
# _compare_structures(new_tree, tree)
# 1. Add a few messages to pigs group # # ----------------------------------------
msgid1 = group_pigs.message_post(body='1', subject='1', parent_id=False) # # CASE2: message_read test
msgid2 = group_pigs.message_post(body='2', subject='1-1', parent_id=msgid1) # # ----------------------------------------
msgid3 = group_pigs.message_post(body='3', subject='1-2', parent_id=msgid1)
msgid4 = group_pigs.message_post(body='4', subject='2', parent_id=False)
msgid5 = group_pigs.message_post(body='5', subject='1-1-1', parent_id=msgid2)
msgid6 = group_pigs.message_post(body='6', subject='2-1', parent_id=msgid4)
# Test: read all messages flat # # 1. Add a few messages to pigs group
tree_test = [{'id': msgid6, 'child_ids': []}, {'id': msgid5, 'child_ids': []}, # msgid1 = group_pigs.message_post(body='1', subject='1', parent_id=False)
{'id': msgid4, 'child_ids': []}, {'id': msgid3, 'child_ids': []}, # msgid2 = group_pigs.message_post(body='2', subject='1-1', parent_id=msgid1)
{'id': msgid2, 'child_ids': []}, {'id': msgid1, 'child_ids': []}] # msgid3 = group_pigs.message_post(body='3', subject='1-2', parent_id=msgid1)
tree = self.mail_message.message_read(cr, uid, ids=False, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)], thread_level=0, limit=10) # msgid4 = group_pigs.message_post(body='4', subject='2', parent_id=False)
_compare_structures(tree, tree_test) # msgid5 = group_pigs.message_post(body='5', subject='1-1-1', parent_id=msgid2)
# Test: read with 1 level of thread # msgid6 = group_pigs.message_post(body='6', subject='2-1', parent_id=msgid4)
tree_test = [{'id': msgid4, 'child_ids': [{'id': msgid6, 'child_ids': []}, ]},
{'id': msgid1, 'child_ids': [
{'id': msgid5, 'child_ids': []}, {'id': msgid3, 'child_ids': []},
{'id': msgid2, 'child_ids': []},
]},
]
tree = self.mail_message.message_read(cr, uid, ids=False, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)], thread_level=1, limit=10)
_compare_structures(tree, tree_test)
# Test: read with 2 levels of thread
tree_test = [{'id': msgid4, 'child_ids': [{'id': msgid6, 'child_ids': []}, ]},
{'id': msgid1, 'child_ids': [
{'id': msgid3, 'child_ids': []},
{'id': msgid2, 'child_ids': [{'id': msgid5, 'child_ids': []}, ]},
]},
]
tree = self.mail_message.message_read(cr, uid, ids=False, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)], thread_level=2, limit=10)
_compare_structures(tree, tree_test)
# 2. Test expandables # # Test: read all messages flat
# TDE FIXME: add those tests when expandables are specified and implemented # tree_test = [{'id': msgid6, 'child_ids': []}, {'id': msgid5, 'child_ids': []},
# {'id': msgid4, 'child_ids': []}, {'id': msgid3, 'child_ids': []},
# {'id': msgid2, 'child_ids': []}, {'id': msgid1, 'child_ids': []}]
# tree = self.mail_message.message_read(cr, uid, ids=False, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)], level=0, limit=15)
# _compare_structures(tree, tree_test)
# # Test: read with 1 level of thread
# tree_test = [{'id': msgid4, 'child_ids': [{'id': msgid6, 'child_ids': []}, ]},
# {'id': msgid1, 'child_ids': [
# {'id': msgid5, 'child_ids': []}, {'id': msgid3, 'child_ids': []},
# {'id': msgid2, 'child_ids': []},
# ]},
# ]
# tree = self.mail_message.message_read(cr, uid, ids=False, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)], level=1, limit=15)
# _compare_structures(tree, tree_test)
# # Test: read with 2 levels of thread
# tree_test = [{'id': msgid4, 'child_ids': [{'id': msgid6, 'child_ids': []}, ]},
# {'id': msgid1, 'child_ids': [
# {'id': msgid3, 'child_ids': []},
# {'id': msgid2, 'child_ids': [{'id': msgid5, 'child_ids': []}, ]},
# ]},
# ]
# tree = self.mail_message.message_read(cr, uid, ids=False, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)], level=2, limit=15)
# _compare_structures(tree, tree_test)
# # 2. Test expandables
# # TDE FIXME: add those tests when expandables are specified and implemented
def test_40_needaction(self): def test_40_needaction(self):
""" Tests for mail.message needaction. """ """ Tests for mail.message needaction. """
cr, uid = self.cr, self.uid cr, uid = self.cr, self.uid
group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id) group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
group_pigs_demo = self.mail_group.browse(cr, self.user_demo, self.group_pigs_id)
user_admin = self.res_users.browse(cr, uid, uid) user_admin = self.res_users.browse(cr, uid, uid)
# Demo values: check unread notification = needaction on mail.message # Demo values: check unread notification = needaction on mail.message
@ -607,9 +647,12 @@ class test_mail(TestMailMockups):
na_count = self.mail_message._needaction_count(cr, uid, domain=[]) na_count = self.mail_message._needaction_count(cr, uid, domain=[])
self.assertEqual(len(notif_ids), na_count, 'unread notifications count does not match needaction count') self.assertEqual(len(notif_ids), na_count, 'unread notifications count does not match needaction count')
# Post 4 message on group_pigs na_count1 = self.mail_message._needaction_count(cr, uid, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)])
for dummy in range(4): # Post 2 message on group_pigs as admin, 3 messages as demo user
group_pigs.message_post(body='My Body') for dummy in range(2):
group_pigs.message_post(body='My Body', subtype='mt_comment')
for dummy in range(3):
group_pigs_demo.message_post(body='My Demo Body', subtype='mt_comment')
# Check there are 4 new needaction on mail.message # Check there are 4 new needaction on mail.message
notif_ids = self.mail_notification.search(cr, uid, [ notif_ids = self.mail_notification.search(cr, uid, [
@ -621,7 +664,19 @@ class test_mail(TestMailMockups):
# Check there are 4 needaction on mail.message with particular domain # Check there are 4 needaction on mail.message with particular domain
na_count = self.mail_message._needaction_count(cr, uid, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)]) na_count = self.mail_message._needaction_count(cr, uid, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)])
self.assertEqual(na_count, 4, 'posted message count does not match needaction count') notif_ids = self.mail_notification.search(cr, uid, [
('partner_id', '=', user_admin.partner_id.id),
('read', '=', False),
('message_id.model','=','mail.group'),
('message_id.res_id','=',self.group_pigs_id)
])
self.assertEqual(len(notif_ids), na_count, 'posted message count does not match needaction count')
na_count3 = self.mail_message._needaction_count(cr, self.user_demo, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)])
self.assertEqual(na_count3-na_count1, 0, 'demo has 0 message: not a follower and do not follow his own messages')
na_count2 = self.mail_message._needaction_count(cr, uid, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)])
self.assertEqual(na_count2-na_count1, 3, 'admin has 3 messages: 0 from itself as they are marked as read, 3 from demo')
def test_50_thread_parent_resolution(self): def test_50_thread_parent_resolution(self):
"""Verify parent/child relationships are correctly established when processing incoming mails""" """Verify parent/child relationships are correctly established when processing incoming mails"""
@ -636,15 +691,18 @@ class test_mail(TestMailMockups):
# 1. In-Reply-To header # 1. In-Reply-To header
reply_msg = MAIL_TEMPLATE.format(to='Pretty Pigs <group+pigs@example.com>, other@gmail.com', subject='Re: 1', 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) extra='In-Reply-To: %s' % msg1.message_id)
self.mail_thread.message_process(cr, uid, None, reply_msg) self.mail_group.message_process(cr, uid, None, reply_msg)
# 2. References header # 2. References header
reply_msg2 = MAIL_TEMPLATE.format(to='Pretty Pigs <group+pigs@example.com>, other@gmail.com', subject='Re: Re: 1', 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) extra='References: <2233@a.com>\r\n\t<3edss_dsa@b.com> %s' % msg1.message_id)
self.mail_thread.message_process(cr, uid, None, reply_msg2) 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 # 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', reply_msg3 = MAIL_TEMPLATE.format(to='Pretty Pigs <group+pigs@example.com>, other@gmail.com',
extra='', subject='Re: [%s] 1' % self.group_pigs_id) extra='', subject='Re: [%s] 1' % self.group_pigs_id)
self.mail_thread.message_process(cr, uid, 'mail.group', reply_msg3) self.mail_group.message_process(cr, uid, 'mail.group', reply_msg3)
group_pigs.refresh() group_pigs.refresh()
msg1.refresh() msg1.refresh()
self.assertEqual(5, len(group_pigs.message_ids), 'group should contain 5 messages') self.assertEqual(5, len(group_pigs.message_ids), 'group should contain 5 messages')
@ -653,8 +711,8 @@ class test_mail(TestMailMockups):
def test_60_vote(self): def test_60_vote(self):
""" Test designed for the vote/unvote feature. """ """ Test designed for the vote/unvote feature. """
cr, uid = self.cr, self.uid cr, uid = self.cr, self.uid
group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
user_admin = self.res_users.browse(cr, uid, uid) user_admin = self.res_users.browse(cr, uid, uid)
group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
msg1 = group_pigs.message_post(body='My Body', subject='1') msg1 = group_pigs.message_post(body='My Body', subject='1')
msg1 = self.mail_message.browse(cr, uid, msg1) msg1 = self.mail_message.browse(cr, uid, msg1)

View File

@ -102,53 +102,53 @@ class test_mail_access_rights(test_mail.TestMailMockups):
""" Test mail_message search override about access rights. """ """ Test mail_message search override about access rights. """
self.assertTrue(1 == 1, 'Test not implemented, do not replace by return True') self.assertTrue(1 == 1, 'Test not implemented, do not replace by return True')
def test_10_mail_flow_access_rights(self): # def test_10_mail_flow_access_rights(self):
""" Test a Chatter-looks alike flow. """ # """ Test a Chatter-looks alike flow. """
cr, uid = self.cr, self.uid # cr, uid = self.cr, self.uid
partner_bert_id, partner_raoul_id = self.partner_bert_id, self.partner_raoul_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 # user_bert_id, user_raoul_id = self.user_bert_id, self.user_raoul_id
# Prepare groups: Pigs (employee), Jobs (public) # # Prepare groups: Pigs (employee), Jobs (public)
self.mail_group.message_post(cr, uid, self.group_pigs_id, body='Message') # 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'}) # self.group_jobs_id = self.mail_group.create(cr, uid, {'name': 'Jobs', 'public': 'public'})
# ---------------------------------------- # # ----------------------------------------
# CASE1: Bert, without groups # # CASE1: Bert, without groups
# ---------------------------------------- # # ----------------------------------------
# Do: Bert creates a group, should crash because perm_create only for employees # # Do: Bert creates a group, should crash because perm_create only for employees
self.assertRaises(except_orm, # self.assertRaises(except_orm,
self.mail_group.create, # self.mail_group.create,
cr, user_bert_id, {'name': 'Bert\'s Group'}) # cr, user_bert_id, {'name': 'Bert\'s Group'})
# Do: Bert reads Jobs basic fields, ok because public = read access on the 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']) # self.mail_group.read(cr, user_bert_id, self.group_jobs_id, ['name', 'description'])
# Do: Bert browse Pigs, ok (no direct browse of partners) # # Do: Bert browse Pigs, ok (no direct browse of partners)
self.mail_group.browse(cr, user_bert_id, self.group_jobs_id) # 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 # # 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'] # 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) # 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 # # 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'] # 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.assertRaises(except_orm,
self.res_partner.read, # self.res_partner.read,
cr, user_bert_id, jobs_followers_ids) # cr, user_bert_id, jobs_followers_ids)
# Do: Bert comments Jobs, ko because no write access on the group and not in the followers # # Do: Bert comments Jobs, ko because no write access on the group and not in the followers
self.assertRaises(except_orm, # self.assertRaises(except_orm,
self.mail_group.message_post, # self.mail_group.message_post,
cr, user_bert_id, self.group_jobs_id, body='I love Pigs') # cr, user_bert_id, self.group_jobs_id, body='I love Pigs')
# Do: add Bert to jobs followers # # Do: add Bert to jobs followers
self.mail_group.message_subscribe(cr, uid, [self.group_jobs_id], [partner_bert_id]) # 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 # # 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') # 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 # # Do: Bert reads Pigs, should crash because mail.group security=groups only for employee group
self.assertRaises(except_orm, # self.assertRaises(except_orm,
self.mail_group.read, # self.mail_group.read,
cr, user_bert_id, self.group_pigs_id) # cr, user_bert_id, self.group_pigs_id)
# ---------------------------------------- # # ----------------------------------------
# CASE1: Raoul, employee # # CASE1: Raoul, employee
# ---------------------------------------- # # ----------------------------------------
# Do: Bert read Pigs, ok because public # # Do: Bert read Pigs, ok because public
self.mail_group.read(cr, user_raoul_id, self.group_pigs_id) # self.mail_group.read(cr, user_raoul_id, self.group_pigs_id)
# Do: Bert read Jobs, ok because group_public_id = employee # # Do: Bert read Jobs, ok because group_public_id = employee
self.mail_group.read(cr, user_raoul_id, self.group_jobs_id) # self.mail_group.read(cr, user_raoul_id, self.group_jobs_id)

View File

@ -102,9 +102,9 @@ class mail_compose_message(osv.TransientModel):
'partner_ids': fields.many2many('res.partner', 'partner_ids': fields.many2many('res.partner',
'mail_compose_message_res_partner_rel', 'mail_compose_message_res_partner_rel',
'wizard_id', 'partner_id', 'Additional contacts'), 'wizard_id', 'partner_id', 'Additional contacts'),
'attachment_ids': fields.many2many('ir.attachment', 'attachment_ids': fields.one2many('ir.attachment', 'res_id',
'mail_compose_message_ir_attachments_rel', domain=lambda self: [('res_model', '=', self._name)],
'wizard_id', 'attachment_id', 'Attachments'), string='Attachments'),
'filter_id': fields.many2one('ir.filters', 'Filters'), 'filter_id': fields.many2one('ir.filters', 'Filters'),
'body_text': fields.text('Plain-text Contents'), 'body_text': fields.text('Plain-text Contents'),
'content_subtype': fields.char('Message content subtype', size=32, readonly=1, 'content_subtype': fields.char('Message content subtype', size=32, readonly=1,
@ -219,19 +219,29 @@ class mail_compose_message(osv.TransientModel):
email(s), rendering any template patterns on the fly if needed. """ email(s), rendering any template patterns on the fly if needed. """
if context is None: if context is None:
context = {} context = {}
print '**', context
active_ids = context.get('active_ids') active_ids = context.get('active_ids')
for wizard in self.browse(cr, uid, ids, context=context): for wizard in self.browse(cr, uid, ids, context=context):
mass_mail_mode = wizard.composition_mode == 'mass_mail' mass_mail_mode = wizard.composition_mode == 'mass_mail'
active_model_pool = self.pool.get(wizard.model if wizard.model else 'mail.thread') active_model_pool = self.pool.get(wizard.model if wizard.model else 'mail.thread')
if wizard.content_subtype == 'html':
if not wizard.body:
return False
body = wizard.body
else: # wizard.content_subtype == 'plain':
if not wizard.body_text:
return False
body = '<pre>%s</pre>' % tools.ustr(wizard.body_text or '')
# wizard works in batch mode: [res_id] or active_ids # wizard works in batch mode: [res_id] or active_ids
res_ids = active_ids if mass_mail_mode and wizard.model and active_ids else [wizard.res_id] res_ids = active_ids if mass_mail_mode and wizard.model and active_ids else [wizard.res_id]
for res_id in res_ids: for res_id in res_ids:
# default values, according to the wizard options # default values, according to the wizard options
post_values = { post_values = {
'subject': wizard.subject if wizard.content_subtype == 'html' else False, 'subject': wizard.subject if wizard.content_subtype == 'html' else False,
'body': wizard.body if wizard.content_subtype == 'html' else '<pre>%s</pre>' % tools.ustr(wizard.body_text), 'body': body,
'parent_id': wizard.parent_id and wizard.parent_id.id, 'parent_id': wizard.parent_id and wizard.parent_id.id,
'partner_ids': [(4, partner.id) for partner in wizard.partner_ids], 'partner_ids': [(4, partner.id) for partner in wizard.partner_ids],
'attachments': [(attach.datas_fname or attach.name, base64.b64decode(attach.datas)) for attach in wizard.attachment_ids], 'attachments': [(attach.datas_fname or attach.name, base64.b64decode(attach.datas)) for attach in wizard.attachment_ids],
@ -245,11 +255,13 @@ class mail_compose_message(osv.TransientModel):
post_values['attachments'] += new_attachments post_values['attachments'] += new_attachments
post_values.update(email_dict) post_values.update(email_dict)
# post the message # post the message
active_model_pool.message_post(cr, uid, [res_id], type='comment', context=context, **post_values) id=active_model_pool.message_post(cr, uid, [res_id], type='comment', subtype='mt_comment', context=context, **post_values)
# post process: update attachments, because id is not necessarily known when adding attachments in Chatter
self.pool.get('ir.attachment').write(cr, uid, [attach.id for attach in wizard.attachment_ids], {'res_id': wizard.id}, context=context)
return {'type': 'ir.actions.act_window_close'} # post process: update attachments, because id is not necessarily known when adding attachments in Chatter
# self.pool.get('ir.attachment').write(cr, uid, [attach.id for attach in wizard.attachment_ids], {
# 'res_id': wizard.id, 'res_model': wizard.model or False}, context=context)
return {'type': 'ir.actions.act_window_close', 'res_model':'mail.compose.message', 'id': id}
def render_message(self, cr, uid, wizard, res_id, context=None): def render_message(self, cr, uid, wizard, res_id, context=None):
""" Generate an email from the template for given (wizard.model, res_id) """ Generate an email from the template for given (wizard.model, res_id)

View File

@ -75,7 +75,7 @@
<div colspan="2" class="oe_mail_compose_message_attachments"/> <div colspan="2" class="oe_mail_compose_message_attachments"/>
<!-- buttons, with as few Chatter logic as possible --> <!-- buttons, with as few Chatter logic as possible -->
<div> <div>
<button name="send_mail" string="Post" type="object" <button name="send_mail" string="Post message" type="object"
class="oe_mail_compose_message_button_send"/> class="oe_mail_compose_message_button_send"/>
</div> </div>
<div class='oe_mail_compose_message_icons'> <div class='oe_mail_compose_message_icons'>

View File

@ -26,6 +26,6 @@ From the Manufacturing Settings, you can choose to compute production schedules
<field name="number_next">1</field> <field name="number_next">1</field>
<field name="number_increment">1</field> <field name="number_increment">1</field>
</record> </record>
</data> </data>
</openerp> </openerp>

View File

@ -409,9 +409,8 @@
</page> </page>
</notebook> </notebook>
<div class="oe_chatter"> <div class="oe_chatter">
<field name="message_is_follower" invisible="1"/> <field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/> <field name="message_follower_ids" widget="mail_followers"/>
<field name="message_follower_ids" widget="mail_followers"/>
</div> </div>
</form> </form>
</field> </field>
@ -798,7 +797,6 @@
</notebook> </notebook>
</sheet> </sheet>
<div class="oe_chatter"> <div class="oe_chatter">
<field name="message_is_follower" invisible="1"/>
<field name="message_ids" widget="mail_thread"/> <field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/> <field name="message_follower_ids" widget="mail_followers"/>
</div> </div>

View File

@ -58,6 +58,7 @@ So, that we can compare the theoretic delay and real delay.
'depends': ['mrp'], 'depends': ['mrp'],
'data': [ 'data': [
'security/ir.model.access.csv', 'security/ir.model.access.csv',
'mrp_operation_data.xml',
'mrp_operations_workflow.xml', 'mrp_operations_workflow.xml',
'mrp_operations_view.xml', 'mrp_operations_view.xml',
'mrp_operations_report.xml', 'mrp_operations_report.xml',
@ -65,7 +66,7 @@ So, that we can compare the theoretic delay and real delay.
'process/mrp_operation_process.xml', 'process/mrp_operation_process.xml',
'mrp_operations_workflow_instance.xml' 'mrp_operations_workflow_instance.xml'
], ],
'demo': ['mrp_operation_data.xml', 'demo': [
'mrp_operations_demo.yml' 'mrp_operations_demo.yml'
], ],
'test': [ 'test': [

View File

@ -107,7 +107,6 @@
</notebook> </notebook>
</sheet> </sheet>
<div class="oe_chatter"> <div class="oe_chatter">
<field name="message_is_follower" invisible="1"/>
<field name="message_ids" widget="mail_thread"/> <field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/> <field name="message_follower_ids" widget="mail_followers"/>
</div> </div>

View File

@ -43,6 +43,7 @@ The following topics should be covered by this module:
'data': [ 'data': [
'security/ir.model.access.csv', 'security/ir.model.access.csv',
'security/mrp_repair_security.xml', 'security/mrp_repair_security.xml',
'mrp_repair_data.xml',
'mrp_repair_sequence.xml', 'mrp_repair_sequence.xml',
'wizard/mrp_repair_cancel_view.xml', 'wizard/mrp_repair_cancel_view.xml',
'wizard/mrp_repair_make_invoice_view.xml', 'wizard/mrp_repair_make_invoice_view.xml',

View File

@ -0,0 +1,5 @@
<?xml version="1.0"?>
<openerp>
<data>
</data>
</openerp>

View File

@ -189,7 +189,6 @@
</notebook> </notebook>
</sheet> </sheet>
<div class="oe_chatter"> <div class="oe_chatter">
<field name="message_is_follower" invisible="1"/>
<field name="message_ids" widget="mail_thread"/> <field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/> <field name="message_follower_ids" widget="mail_followers"/>
</div> </div>

View File

@ -108,22 +108,21 @@
<!-- New note Form View --> <!-- New note Form View -->
<record model="ir.ui.view" id="view_note_note_form"> <record model="ir.ui.view" id="view_note_note_form">
<field name="name">note.note.form</field> <field name="name">note.note.form</field>
<field name="model">note.note</field> <field name="model">note.note</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="Note" version="7.0"> <form string="Note" version="7.0">
<header> <header>
<field name="tag_ids" widget="many2many_tags" class="oe_inline" placeholder="Tags"/> <field name="tag_ids" widget="many2many_tags" class="oe_inline" placeholder="Tags"/>
<field name="stage_id" domain="[('user_id','=',uid)]" widget="statusbar" clickable="1"/> <field name="stage_id" domain="[('user_id','=',uid)]" widget="statusbar" clickable="1"/>
</header> </header>
<field name="memo" widget="html"/><!-- editor_width="100%%" editor_height="60%%" --> <field name="memo" widget="html"/><!-- editor_width="100%%" editor_height="60%%" -->
<div class="oe_chatter"> <div class="oe_chatter">
<field name="message_is_follower" invisible="1"/> <field name="message_follower_ids" widget="mail_followers"/>
<field name="message_ids" widget="mail_thread"/> <field class="oe_chatter" name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/> </div>
</div> </form>
</form> </field>
</field>
</record> </record>
<!-- Search note --> <!-- Search note -->

View File

@ -498,22 +498,22 @@ class procurement_order(osv.osv):
return obj_id return obj_id
def create_send_note(self, cr, uid, ids, context=None): def create_send_note(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, body=_("Procurement has been <b>created</b>."), context=context) self.message_post(cr, uid, ids, body=_("Procurement <b>created</b>."), context=context)
def confirm_send_note(self, cr, uid, ids, context=None): def confirm_send_note(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, body=_("Procurement has been <b>confirmed</b>."), context=context) self.message_post(cr, uid, ids, body=_("Procurement <b>confirmed</b>."), context=context)
def running_send_note(self, cr, uid, ids, context=None): def running_send_note(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, body=_("Procurement has been set to <b>running</b>."), context=context) self.message_post(cr, uid, ids, body=_("Procurement set to <b>running</b>."), context=context)
def ready_send_note(self, cr, uid, ids, context=None): def ready_send_note(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, body=_("Procurement has been set to <b>ready</b>."), context=context) self.message_post(cr, uid, ids, body=_("Procurement set to <b>ready</b>."), context=context)
def cancel_send_note(self, cr, uid, ids, context=None): def cancel_send_note(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, body=_("Procurement has been <b>cancelled</b>."), context=context) self.message_post(cr, uid, ids, body=_("Procurement <b>cancelled</b>."), context=context)
def done_send_note(self, cr, uid, ids, context=None): def done_send_note(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, body=_("Procurement has been <b>done</b>."), context=context) self.message_post(cr, uid, ids, body=_("Procurement <b>done</b>."), context=context)
procurement_order() procurement_order()

View File

@ -103,7 +103,6 @@
</notebook> </notebook>
</sheet> </sheet>
<div class="oe_chatter"> <div class="oe_chatter">
<field name="message_is_follower" invisible="1"/>
<field name="message_ids" widget="mail_thread"/> <field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/> <field name="message_follower_ids" widget="mail_followers"/>
</div> </div>

View File

@ -203,7 +203,6 @@
</notebook> </notebook>
</sheet> </sheet>
<div class="oe_chatter"> <div class="oe_chatter">
<field name="message_is_follower" invisible="1"/>
<field name="message_ids" widget="mail_thread"/> <field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/> <field name="message_follower_ids" widget="mail_followers"/>
</div> </div>

View File

@ -519,20 +519,16 @@ def Project():
return self.message_post(cr, uid, ids, body=_("Project has been <b>created</b>."), context=context) return self.message_post(cr, uid, ids, body=_("Project has been <b>created</b>."), context=context)
def set_open_send_note(self, cr, uid, ids, context=None): def set_open_send_note(self, cr, uid, ids, context=None):
message = _("Project has been <b>opened</b>.") return self.message_post(cr, uid, ids, body=_("Project has been <b>opened</b>."), context=context)
return self.message_post(cr, uid, ids, body=message, context=context)
def set_pending_send_note(self, cr, uid, ids, context=None): def set_pending_send_note(self, cr, uid, ids, context=None):
message = _("Project is now <b>pending</b>.") return self.message_post(cr, uid, ids, body=_("Project is now <b>pending</b>."), context=context)
return self.message_post(cr, uid, ids, body=message, context=context)
def set_cancel_send_note(self, cr, uid, ids, context=None): def set_cancel_send_note(self, cr, uid, ids, context=None):
message = _("Project has been <b>cancelled</b>.") return self.message_post(cr, uid, ids, body=_("Project has been <b>canceled</b>."), context=context)
return self.message_post(cr, uid, ids, body=message, context=context)
def set_close_send_note(self, cr, uid, ids, context=None): def set_close_send_note(self, cr, uid, ids, context=None):
message = _("Project has been <b>closed</b>.") return self.message_post(cr, uid, ids, body=_("Project has been <b>closed</b>."), context=context)
return self.message_post(cr, uid, ids, body=message, context=context)
def write(self, cr, uid, ids, vals, context=None): def write(self, cr, uid, ids, vals, context=None):
# if alias_model has been changed, update alias_model_id accordingly # if alias_model has been changed, update alias_model_id accordingly
@ -1100,10 +1096,10 @@ class task(base_stage, osv.osv):
def create(self, cr, uid, vals, context=None): def create(self, cr, uid, vals, context=None):
task_id = super(task, self).create(cr, uid, vals, context=context) task_id = super(task, self).create(cr, uid, vals, context=context)
project_id = self.browse(cr, uid, task_id, context=context).project_id task_record = self.browse(cr, uid, task_id, context=context)
if project_id: if task_record.project_id:
followers = [follower.id for follower in project_id.message_follower_ids] project_follower_ids = [follower.id for follower in task_record.project_id.message_follower_ids]
self.message_subscribe(cr, uid, [task_id], followers, context=context) self.message_subscribe(cr, uid, [task_id], project_follower_ids, context=context)
self._store_history(cr, uid, [task_id], context=context) self._store_history(cr, uid, [task_id], context=context)
self.create_send_note(cr, uid, [task_id], context=context) self.create_send_note(cr, uid, [task_id], context=context)
return task_id return task_id
@ -1235,14 +1231,14 @@ class task(base_stage, osv.osv):
def stage_set_send_note(self, cr, uid, ids, stage_id, context=None): def stage_set_send_note(self, cr, uid, ids, stage_id, context=None):
""" Override of the (void) default notification method. """ """ Override of the (void) default notification method. """
stage_name = self.pool.get('project.task.type').name_get(cr, uid, [stage_id], context=context)[0][1] stage_name = self.pool.get('project.task.type').name_get(cr, uid, [stage_id], context=context)[0][1]
return self.message_post(cr, uid, ids, body= _("Stage changed to <b>%s</b>.") % (stage_name), context=context) return self.message_post(cr, uid, ids, body=_("Stage changed to <b>%s</b>.") % (stage_name),
context=context)
def create_send_note(self, cr, uid, ids, context=None): def create_send_note(self, cr, uid, ids, context=None):
return self.message_post(cr, uid, ids, body=_("Task has been <b>created</b>."), context=context) return self.message_post(cr, uid, ids, body=_("Task has been <b>created</b>."), context=context)
def case_draft_send_note(self, cr, uid, ids, context=None): def case_draft_send_note(self, cr, uid, ids, context=None):
msg = _('Task has been set as <b>draft</b>.') return self.message_post(cr, uid, ids, body=_('Task has been set as <b>draft</b>.'), context=context)
return self.message_post(cr, uid, ids, body=msg, context=context)
def do_delegation_send_note(self, cr, uid, ids, context=None): def do_delegation_send_note(self, cr, uid, ids, context=None):
for task in self.browse(cr, uid, ids, context=context): for task in self.browse(cr, uid, ids, context=context):

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<openerp> <openerp>
<data noupdate="1"> <data>
<!-- This will set the unit of measure used in projects and tasks.--> <!-- This will set the unit of measure used in projects and tasks.-->
<record id="base.main_company" model="res.company"> <record id="base.main_company" model="res.company">
<field name="project_time_mode_id" ref="product.product_uom_hour"></field> <field name="project_time_mode_id" ref="product.product_uom_hour"></field>
@ -84,6 +84,42 @@
<field name="fold" eval="True"/> <field name="fold" eval="True"/>
</record> </record>
<!-- mail: subtypes -->
<record id="mt_project_new" model="mail.message.subtype">
<field name="name">New Task</field>
<field name="res_model">project.project</field>
<field name="default" eval="False"/>
</record>
<record id="mt_project_closed" model="mail.message.subtype">
<field name="name">Task Closed</field>
<field name="res_model">project.project</field>
</record>
<record id="mt_project_canceled" model="mail.message.subtype">
<field name="name">Task Canceled</field>
<field name="res_model">project.task</field>
</record>
<record id="mt_project_stage" model="mail.message.subtype">
<field name="name">Task Stage Changed</field>
<field name="res_model">project.task</field>
</record>
<record id="mt_task_new" model="mail.message.subtype">
<field name="name">New Task</field>
<field name="res_model">project.task</field>
</record>
<record id="mt_task_closed" model="mail.message.subtype">
<field name="name">Task Closed</field>
<field name="res_model">project.task</field>
</record>
<record id="mt_task_canceled" model="mail.message.subtype">
<field name="name">Task canceled</field>
<field name="res_model">project.task</field>
</record>
<record id="mt_task_change" model="mail.message.subtype">
<field name="name">Task Stage Changed</field>
<field name="res_model">project.task</field>
</record>
<!-- notify all employees of module installation --> <!-- notify all employees of module installation -->
<record model="mail.message" id="module_install_notification"> <record model="mail.message" id="module_install_notification">
<field name="model">mail.group</field> <field name="model">mail.group</field>

View File

@ -150,7 +150,6 @@
</notebook> </notebook>
</sheet> </sheet>
<div class="oe_chatter"> <div class="oe_chatter">
<field name="message_is_follower" invisible="1"/>
<field name="message_ids" widget="mail_thread"/> <field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers" help="Follow this project to automatically follow all related tasks and issues."/> <field name="message_follower_ids" widget="mail_followers" help="Follow this project to automatically follow all related tasks and issues."/>
</div> </div>
@ -468,7 +467,6 @@
</notebook> </notebook>
</sheet> </sheet>
<div class="oe_chatter"> <div class="oe_chatter">
<field name="message_is_follower" invisible="1"/>
<field name="message_ids" widget="mail_thread"/> <field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/> <field name="message_follower_ids" widget="mail_followers"/>
</div> </div>

View File

@ -511,7 +511,7 @@ class project_issue(base_stage, osv.osv):
def stage_set_send_note(self, cr, uid, ids, stage_id, context=None): def stage_set_send_note(self, cr, uid, ids, stage_id, context=None):
""" Override of the (void) default notification method. """ """ Override of the (void) default notification method. """
stage_name = self.pool.get('project.task.type').name_get(cr, uid, [stage_id], context=context)[0][1] stage_name = self.pool.get('project.task.type').name_get(cr, uid, [stage_id], context=context)[0][1]
return self.message_post(cr, uid, ids, body= _("Stage changed to <b>%s</b>.") % (stage_name), context=context) return self.message_post(cr, uid, ids, body= _("Stage changed to <b>%s</b>.") % (stage_name), subtype="mt_issue_new", context=context)
def case_get_note_msg_prefix(self, cr, uid, id, context=None): def case_get_note_msg_prefix(self, cr, uid, id, context=None):
""" Override of default prefix for notifications. """ """ Override of default prefix for notifications. """
@ -523,7 +523,7 @@ class project_issue(base_stage, osv.osv):
def create_send_note(self, cr, uid, ids, context=None): def create_send_note(self, cr, uid, ids, context=None):
message = _("Project issue <b>created</b>.") message = _("Project issue <b>created</b>.")
return self.message_post(cr, uid, ids, body=message, context=context) return self.message_post(cr, uid, ids, body=message, subtype="mt_issue_new", context=context)
def case_escalate_send_note(self, cr, uid, ids, context=None): def case_escalate_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context): for obj in self.browse(cr, uid, ids, context=context):

View File

@ -42,5 +42,21 @@ You can record issues, assign them to a responsible person, and keep track of th
Access all issues from the top Project menu, and access the issues of a specific project via the projects gallery view.</field> Access all issues from the top Project menu, and access the issues of a specific project via the projects gallery view.</field>
</record> </record>
<!-- Mail subtypes -->
<record id="mail.mt_issue_new" model="mail.message.subtype">
<field name="name">created</field>
<field name="res_model">project.issue</field>
<field name="default" eval="False"/>
</record>
<record id="mail.mt_issue_new" model="mail.message.subtype">
<field name="name">stage change</field>
<field name="res_model">project.issue</field>
<field name="default" eval="False"/>
</record>
<record id="mail.mt_issue_closed" model="mail.message.subtype">
<field name="name">closed</field>
<field name="res_model">project.issue</field>
</record>
</data> </data>
</openerp> </openerp>

View File

@ -161,7 +161,6 @@
</notebook> </notebook>
</sheet> </sheet>
<div class="oe_chatter"> <div class="oe_chatter">
<field name="message_is_follower" invisible="1"/>
<field name="message_ids" widget="mail_thread"/> <field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/> <field name="message_follower_ids" widget="mail_followers"/>
</div> </div>

View File

@ -285,7 +285,6 @@
</notebook> </notebook>
</sheet> </sheet>
<div class="oe_chatter"> <div class="oe_chatter">
<field name="message_is_follower" invisible="1"/>
<field name="message_ids" widget="mail_thread"/> <field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/> <field name="message_follower_ids" widget="mail_followers"/>
</div> </div>

View File

@ -92,13 +92,13 @@ class purchase_requisition(osv.osv):
def in_progress_send_note(self, cr, uid, ids, context=None): def in_progress_send_note(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, body=_("Draft Requisition has been <b>sent to suppliers</b>."), context=context) self.message_post(cr, uid, ids, body=_("Draft Requisition has been <b>sent to suppliers</b>."), context=context)
def reset_send_note(self, cr, uid, ids, context=None): def reset_send_note(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, body=_("Purchase Requisition has been set to <b>draft</b>."), context=context) self.message_post(cr, uid, ids, body=_("Purchase Requisition has been set to <b>draft</b>."), context=context)
def done_to_send_note(self, cr, uid, ids, context=None): def done_to_send_note(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, body=_("Purchase Requisition has been <b>done</b>."), context=context) self.message_post(cr, uid, ids, body=_("Purchase Requisition has been <b>done</b>."), context=context)
def cancel_send_note(self, cr, uid, ids, context=None): def cancel_send_note(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, body=_("Purchase Requisition has been <b>cancelled</b>."), context=context) self.message_post(cr, uid, ids, body=_("Purchase Requisition has been <b>cancelled</b>."), context=context)

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<openerp> <openerp>
<data noupdate="1"> <data noupdate="1">
<function <function
eval="('default',False,'warehouse_id', [('purchase.requisition', False)], ref('stock.warehouse0'), True, False, False, False, True)" eval="('default',False,'warehouse_id', [('purchase.requisition', False)], ref('stock.warehouse0'), True, False, False, False, True)"
id="purchase_default_set" id="purchase_default_set"
model="ir.values" model="ir.values"

View File

@ -102,9 +102,8 @@
</notebook> </notebook>
</sheet> </sheet>
<div class="oe_chatter"> <div class="oe_chatter">
<field name="message_is_follower" invisible="1"/> <field name="message_ids" widget="mail_thread"/>
<field name="message_ids" widget="mail_thread"/> <field name="message_follower_ids" widget="mail_followers"/>
<field name="message_follower_ids" widget="mail_followers"/>
</div> </div>
</form> </form>
</field> </field>

View File

@ -310,7 +310,6 @@
</notebook> </notebook>
</sheet> </sheet>
<div class="oe_chatter"> <div class="oe_chatter">
<field name="message_is_follower" invisible="1"/>
<field name="message_ids" widget="mail_thread"/> <field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/> <field name="message_follower_ids" widget="mail_followers"/>
</div> </div>

View File

@ -1450,7 +1450,7 @@ class stock_picking(osv.osv):
'internal': _("Products have been <b>moved</b>."), 'internal': _("Products have been <b>moved</b>."),
} }
for obj in self.browse(cr, uid, ids, context=context): for obj in self.browse(cr, uid, ids, context=context):
self.message_post(cr, uid, [obj.id], body=type_dict.get(obj.type, _('Products have been moved.')), context=context) self.message_post(cr, uid, [obj.id], body=_("Products have been <b>%s</b>.") % (type_dict.get(obj.type, 'move done')), context=context)
def ship_cancel_send_note(self, cr, uid, ids, context=None): def ship_cancel_send_note(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context): for obj in self.browse(cr, uid, ids, context=context):

View File

@ -914,7 +914,6 @@
</xpath> </xpath>
<xpath expr="/form/sheet" position="after"> <xpath expr="/form/sheet" position="after">
<div class="oe_chatter"> <div class="oe_chatter">
<field name="message_is_follower" invisible="1"/>
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/> <field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
<field name="message_follower_ids" widget="mail_followers"/> <field name="message_follower_ids" widget="mail_followers"/>
</div> </div>
@ -1041,7 +1040,6 @@
</xpath> </xpath>
<xpath expr="/form/sheet" position="after"> <xpath expr="/form/sheet" position="after">
<div class="oe_chatter"> <div class="oe_chatter">
<field name="message_is_follower" invisible="1"/>
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/> <field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
<field name="message_follower_ids" widget="mail_followers"/> <field name="message_follower_ids" widget="mail_followers"/>
</div> </div>