[REF] mail: _track update.

_track is now a method that returns the subtype to trigger in a given
tracking context. A tracking will now lead to only one subtype
being trigerred, instead of having multiple possible subtypes.

Now, one tracking leads to one message with one subtype, or without subtype
if there is none matching. This simplifies the model for future evolution of
chatter and mail.

[IMP] project, project_issue: cleaned task and issue subtypes, now having
opened for created / assigned.
This commit is contained in:
Thibault Delavallée
2015-02-24 16:28:51 +01:00
parent 3b6a7a9c42
commit d4e9640e34
21 changed files with 239 additions and 261 deletions
+12 -9
View File
@@ -51,15 +51,6 @@ class account_invoice(models.Model):
_inherit = ['mail.thread']
_description = "Invoice"
_order = "number desc, id desc"
_track = {
'type': {
},
'state': {
'account.mt_invoice_paid': lambda self, cr, uid, obj, ctx=None: obj.state == 'paid' and obj.type in ('out_invoice', 'out_refund'),
'account.mt_invoice_validated': lambda self, cr, uid, obj, ctx=None: obj.state == 'open' and obj.type in ('out_invoice', 'out_refund'),
'account.mt_invoice_created': lambda self, cr, uid, obj, ctx=None: obj.state == 'draft' and obj.type in ('out_invoice', 'out_refund'),
},
}
@api.one
@api.depends('invoice_line.price_subtotal', 'tax_line.amount')
@@ -1206,6 +1197,18 @@ class account_invoice(models.Model):
return recs.pay_and_reconcile(pay_amount, pay_account_id, period_id, pay_journal_id,
writeoff_acc_id, writeoff_period_id, writeoff_journal_id, name=name)
@api.multi
def _track_subtype(self, init_values):
self.ensure_one()
if 'state' in init_values and self.state == 'paid' and self.type in ('out_invoice', 'out_refund'):
return 'account.mt_invoice_paid'
elif 'state' in init_values and self.state == 'open' and self.type in ('out_invoice', 'out_refund'):
return 'account.mt_invoice_validated'
elif 'state' in init_values and self.state == 'draft' and self.type in ('out_invoice', 'out_refund'):
return 'account.mt_invoice_created'
return super(account_invoice, self)._track_subtype(init_values)
class account_invoice_line(models.Model):
_name = "account.invoice.line"
_description = "Invoice Line"
+6 -6
View File
@@ -287,12 +287,6 @@ class account_voucher(osv.osv):
_description = 'Accounting Voucher'
_inherit = ['mail.thread']
_order = "date desc, id desc"
# _rec_name = 'number'
_track = {
'state': {
'account_voucher.mt_voucher_state_change': lambda self, cr, uid, obj, ctx=None: True,
},
}
_columns = {
'type':fields.selection([
@@ -1414,6 +1408,12 @@ class account_voucher(osv.osv):
reconcile = move_line_pool.reconcile_partial(cr, uid, rec_ids, writeoff_acc_id=voucher.writeoff_acc_id.id, writeoff_period_id=voucher.period_id.id, writeoff_journal_id=voucher.journal_id.id)
return True
def _track_subtype(self, cr, uid, ids, init_values, context=None):
if 'state' in init_values:
return 'account_voucher.mt_voucher_state_change'
return super(account_voucher, self)._track_subtype(cr, uid, ids, init_values, context=context)
class account_voucher_line(osv.osv):
_name = 'account.voucher.line'
_description = 'Voucher Lines'
+11 -7
View File
@@ -32,13 +32,6 @@ class account_analytic_account(osv.osv):
_name = 'account.analytic.account'
_inherit = ['mail.thread']
_description = 'Analytic Account'
_track = {
'state': {
'analytic.mt_account_pending': lambda self, cr, uid, obj, ctx=None: obj.state == 'pending',
'analytic.mt_account_closed': lambda self, cr, uid, obj, ctx=None: obj.state == 'close',
'analytic.mt_account_opened': lambda self, cr, uid, obj, ctx=None: obj.state == 'open',
},
}
def _compute_level_tree(self, cr, uid, ids, child_ids, res, field_names, context=None):
currency_obj = self.pool.get('res.currency')
@@ -316,6 +309,17 @@ class account_analytic_account(osv.osv):
account_ids = self.search(cr, uid, args, limit=limit, context=context)
return self.name_get(cr, uid, account_ids, context=context)
def _track_subtype(self, cr, uid, ids, init_values, context=None):
record = self.browse(cr, uid, ids[0], context=context)
if 'state' in init_values and record.state == 'open':
return 'analytic.mt_account_opened'
elif 'state' in init_values and record.state == 'close':
return 'analytic.mt_account_closed'
elif 'state' in init_values and record.state == 'pending':
return 'analytic.mt_account_pending'
return super(account_analytic_account, self)._track_subtype(cr, uid, ids, init_values, context=context)
class account_analytic_line(osv.osv):
_name = 'account.analytic.line'
_description = 'Analytic Line'
+8 -8
View File
@@ -905,14 +905,6 @@ class calendar_event(osv.Model):
duration = float(diff.days) * 24 + (float(diff.seconds) / 3600)
values['duration'] = round(duration, 2)
_track = {
'location': {
'calendar.subtype_invitation': lambda self, cr, uid, obj, ctx=None: True,
},
'start': {
'calendar.subtype_invitation': lambda self, cr, uid, obj, ctx=None: True,
},
}
_columns = {
'id': fields.integer('ID', readonly=True),
'state': fields.selection([('draft', 'Unconfirmed'), ('open', 'Confirmed')], string='Status', readonly=True, track_visibility='onchange'),
@@ -1371,6 +1363,14 @@ class calendar_event(osv.Model):
res[virtual_id] = result[real_id]
return res
def _track_subtype(self, cr, uid, ids, init_values, context=None):
record = self.browse(cr, uid, ids[0], context=context)
if 'start' in init_values and record.start:
return 'calendar.subtype_invitation'
elif 'location' in init_values and record.location:
return 'calendar.subtype_invitation'
return super(calendar_event, self)._track_subtype(cr, uid, ids, init_values, context=context)
def onchange_partner_ids(self, cr, uid, ids, value, context=None):
""" The basic purpose of this method is to check that destination partners
effectively have email addresses. Otherwise a warning is thrown.
+12 -10
View File
@@ -72,16 +72,6 @@ class crm_lead(format_address, osv.osv):
_description = "Lead/Opportunity"
_order = "priority desc,date_action,id desc"
_inherit = ['mail.thread', 'ir.needaction_mixin', 'utm.mixin']
_track = {
'stage_id': {
# this is only an heuristics; depending on your particular stage configuration it may not match all 'new' stages
'crm.mt_lead_create': lambda self, cr, uid, obj, ctx=None: obj.probability == 0 and obj.stage_id and obj.stage_id.sequence <= 1,
'crm.mt_lead_stage': lambda self, cr, uid, obj, ctx=None: (obj.stage_id and obj.stage_id.sequence > 1) and obj.probability < 100,
'crm.mt_lead_won': lambda self, cr, uid, obj, ctx=None: obj.probability == 100 and obj.stage_id and obj.stage_id.fold,
'crm.mt_lead_lost': lambda self, cr, uid, obj, ctx=None: obj.probability == 0 and obj.stage_id and obj.stage_id.fold and obj.stage_id.sequence > 1,
},
}
_mail_mass_mailing = _('Leads / Opportunities')
def get_empty_list_help(self, cr, uid, help, context=None):
@@ -979,6 +969,18 @@ class crm_lead(format_address, osv.osv):
# Mail Gateway
# ----------------------------------------
def _track_subtype(self, cr, uid, ids, init_values, context=None):
record = self.browse(cr, uid, ids[0], context=context)
if 'stage_id' in init_values and record.probability == 100 and record.stage_id and record.stage_id.fold:
return 'crm.mt_lead_won'
elif 'stage_id' in init_values and record.probability == 0 and record.stage_id and record.stage_id.fold and record.stage_id.sequence > 1:
return 'crm.mt_lead_lost'
elif 'stage_id' in init_values and record.probability == 0 and record.stage_id and record.stage_id.sequence <= 1:
return 'crm.mt_lead_create'
elif 'stage_id' in init_values:
return 'crm.mt_lead_stage'
return super(crm_lead, self)._track_subtype(cr, uid, ids, init_values, context=context)
def message_get_reply_to(self, cr, uid, ids, context=None):
""" Override to get the reply_to of the parent project. """
leads = self.browse(cr, SUPERUSER_ID, ids, context=context)
+8 -7
View File
@@ -73,13 +73,6 @@ class hr_contract(osv.osv):
_description = 'Contract'
_inherit = ['mail.thread', 'ir.needaction_mixin']
_track = {
'state': {
'hr_contract.mt_contract_pending': lambda self, cr, uid, obj, ctx=None: obj.state == 'pending',
'hr_contract.mt_contract_close': lambda self, cr, uid, obj, ctx=None: obj.state == 'close',
},
}
_columns = {
'name': fields.char('Contract Reference', required=True),
'employee_id': fields.many2one('hr.employee', "Employee", required=True),
@@ -139,3 +132,11 @@ class hr_contract(osv.osv):
def set_as_close(self, cr, uid, ids, context=None):
return self.write(cr, uid, ids, {'state': 'close'}, context=context)
def _track_subtype(self, cr, uid, ids, init_values, context=None):
record = self.browse(cr, uid, ids[0], context=context)
if 'state' in init_values and record.state == 'pending':
return 'hr_contract.mt_contract_pending'
elif 'state' in init_values and record.state == 'close':
return 'hr_contract.mt_contract_close'
return super(hr_contract, self)._track_subtype(cr, uid, ids, init_values, context=context)
+10 -7
View File
@@ -57,13 +57,6 @@ class hr_expense_expense(osv.osv):
_inherit = ['mail.thread', 'ir.needaction_mixin']
_description = "Expense"
_order = "id desc"
_track = {
'state': {
'hr_expense.mt_expense_approved': lambda self, cr, uid, obj, ctx=None: obj.state == 'accepted',
'hr_expense.mt_expense_refused': lambda self, cr, uid, obj, ctx=None: obj.state == 'cancelled',
'hr_expense.mt_expense_confirmed': lambda self, cr, uid, obj, ctx=None: obj.state == 'confirm',
},
}
_columns = {
'name': fields.char('Description', required=True, readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}),
@@ -152,6 +145,16 @@ class hr_expense_expense(osv.osv):
def expense_canceled(self, cr, uid, ids, context=None):
return self.write(cr, uid, ids, {'state': 'cancelled'}, context=context)
def _track_subtype(self, cr, uid, ids, init_values, context=None):
record = self.browse(cr, uid, ids[0], context=context)
if 'state' in init_values and record.state == 'accepted':
return 'hr_expense.mt_expense_approved'
elif 'state' in init_values and record.state == 'confirm':
return 'hr_expense.mt_expense_confirmed'
elif 'state' in init_values and record.state == 'cancelled':
return 'hr_expense.mt_expense_refused'
return super(hr_expense_expense, self)._track_subtype(cr, uid, ids, init_values, context=context)
def account_move_get(self, cr, uid, expense_id, context=None):
'''
This method prepare the creation of the account move related to the given expense.
+12 -8
View File
@@ -138,14 +138,6 @@ class hr_holidays(osv.osv):
_description = "Leave"
_order = "type desc, date_from asc"
_inherit = ['mail.thread', 'ir.needaction_mixin']
_track = {
'state': {
'hr_holidays.mt_holidays_confirmed': lambda self, cr, uid, obj, ctx=None: obj.state == 'confirm',
'hr_holidays.mt_holidays_first_validated': lambda self, cr, uid, obj, ctx=None: obj.state == 'validate1',
'hr_holidays.mt_holidays_approved': lambda self, cr, uid, obj, ctx=None: obj.state == 'validate',
'hr_holidays.mt_holidays_refused': lambda self, cr, uid, obj, ctx=None: obj.state == 'refuse',
},
}
def _employee_get(self, cr, uid, context=None):
emp_id = context.get('default_employee_id', False)
@@ -488,6 +480,18 @@ class hr_holidays(osv.osv):
ids_to_set_false = list(set(ids) - set(ids_to_set_true))
return self.write(cr, uid, ids_to_set_true, {'payslip_status': True}, context=context) and self.write(cr, uid, ids_to_set_false, {'payslip_status': False}, context=context)
def _track_subtype(self, cr, uid, ids, init_values, context=None):
record = self.browse(cr, uid, ids[0], context=context)
if 'state' in init_values and record.state == 'validate':
return 'hr_holidays.mt_holidays_approved'
elif 'state' in init_values and record.state == 'validate1':
return 'hr_holidays.mt_holidays_first_validated'
elif 'state' in init_values and record.state == 'confirm':
return 'hr_holidays.mt_holidays_confirmed'
elif 'state' in init_values and record.state == 'refuse':
return 'hr_holidays.mt_holidays_refused'
return super(hr_holidays, self)._track_subtype(cr, uid, ids, init_values, context=context)
class resource_calendar_leaves(osv.osv):
_inherit = "resource.calendar.leaves"
+10 -11
View File
@@ -132,17 +132,6 @@ class hr_applicant(osv.Model):
_description = "Applicant"
_order = "priority desc, id desc"
_inherit = ['mail.thread', 'ir.needaction_mixin', 'utm.mixin']
_track = {
'emp_id': {
'hr_recruitment.mt_applicant_hired': lambda self, cr, uid, obj, ctx=None: obj.emp_id,
},
'stage_id': {
# this is only an heuristics; depending on your particular stage configuration it may not match all 'new' stages
'hr_recruitment.mt_applicant_new': lambda self, cr, uid, obj, ctx=None: obj.stage_id and obj.stage_id.sequence <= 1,
'hr_recruitment.mt_applicant_stage_changed': lambda self, cr, uid, obj, ctx=None: obj.stage_id and obj.stage_id.sequence > 1,
},
}
_mail_mass_mailing = _('Applicants')
def _get_default_department_id(self, cr, uid, context=None):
@@ -428,6 +417,16 @@ class hr_applicant(osv.Model):
action['domain'] = str(['&', ('res_model', '=', self._name), ('res_id', 'in', ids)])
return action
def _track_subtype(self, cr, uid, ids, init_values, context=None):
record = self.browse(cr, uid, ids[0], context=context)
if 'emp_id' in init_values and record.emp_id:
return 'hr_recruitment.mt_applicant_hired'
elif 'stage_id' in init_values and record.stage_id and record.stage_id.sequence <= 1:
return 'hr_recruitment.mt_applicant_new'
elif 'stage_id' in init_values and record.stage_id and record.stage_id.sequence > 1:
return 'hr_recruitment.mt_applicant_stage_changed'
return super(hr_applicant, self)._track_subtype(cr, uid, ids, init_values, context=context)
def message_get_reply_to(self, cr, uid, ids, context=None):
""" Override to get the reply_to of the parent project. """
applicants = self.browse(cr, SUPERUSER_ID, ids, context=context)
@@ -37,13 +37,6 @@ class hr_timesheet_sheet(osv.osv):
_order = "id desc"
_description = "Timesheet"
_track = {
'state': {
'hr_timesheet_sheet.mt_timesheet_confirmed': lambda self, cr, uid, obj, ctx=None: obj.state == 'confirm',
'hr_timesheet_sheet.mt_timesheet_approved': lambda self, cr, uid, obj, ctx=None: obj.state == 'done',
},
}
def _total(self, cr, uid, ids, name, args, context=None):
""" Compute the attendances, analytic lines timesheets and differences between them
for all the days of a timesheet and the current day
@@ -280,6 +273,14 @@ class hr_timesheet_sheet(osv.osv):
# OpenChatter methods and notifications
# ------------------------------------------------
def _track_subtype(self, cr, uid, ids, init_values, context=None):
record = self.browse(cr, uid, ids[0], context=context)
if 'state' in init_values and record.state == 'confirm':
return 'hr_timesheet_sheet.mt_timesheet_confirmed'
elif 'state' in init_values and record.state == 'done':
return 'hr_timesheet_sheet.mt_timesheet_approved'
return super(hr_timesheet_sheet, self)._track_subtype(cr, uid, ids, init_values, context=context)
def _needaction_domain_get(self, cr, uid, context=None):
emp_obj = self.pool.get('hr.employee')
empids = emp_obj.search(cr, uid, [('parent_id.user_id', '=', uid)], context=context)
+30 -34
View File
@@ -86,23 +86,6 @@ class mail_thread(osv.AbstractModel):
_mail_flat_thread = True
_mail_post_access = 'write'
# Automatic logging system if mail installed
# _track = {
# 'field': {
# 'module.subtype_xml': lambda self, cr, uid, obj, context=None: obj[state] == done,
# 'module.subtype_xml2': lambda self, cr, uid, obj, context=None: obj[state] != done,
# },
# 'field2': {
# ...
# },
# }
# where
# :param string field: field name
# :param module.subtype_xml: xml_id of a mail.message.subtype (i.e. mail.mt_comment)
# :param obj: is a browse_record
# :param function lambda: returns whether the tracking should record using this subtype
_track = {}
# Mass mailing feature
_mail_mass_mailing = False
@@ -466,14 +449,26 @@ class mail_thread(osv.AbstractModel):
"""
tracked_fields = []
for name, field in self._fields.items():
visibility = getattr(field, 'track_visibility', False)
if visibility == 'always' or (visibility == 'onchange' and name in updated_fields) or name in self._track:
if getattr(field, 'track_visibility', False):
tracked_fields.append(name)
if tracked_fields:
return self.fields_get(cr, uid, tracked_fields, context=context)
return {}
def _track_subtype(self, cr, uid, ids, init_values, context=None):
""" Give the subtypes triggered by the changes on the record according
to values that have been updated.
:param ids: list of a single ID, the ID of the record being modified
:type ids: singleton list
:param init_values: the original values of the record; only modified fields
are present in the dict
:type init_values: dict
:returns: a subtype xml_id or False if no subtype is trigerred
"""
return False
def message_track(self, cr, uid, ids, tracked_fields, initial_values, context=None):
def convert_for_display(value, col_info):
@@ -530,28 +525,29 @@ class mail_thread(osv.AbstractModel):
continue
# find subtypes and post messages or log if no subtype found
subtypes = []
subtype_xmlid = False
# By passing this key, that allows to let the subtype empty and so don't sent email because partners_to_notify from mail_message._notify will be empty
if not context.get('mail_track_log_only'):
for field, track_info in self._track.items():
if field not in changes:
continue
for subtype, method in track_info.items():
if method(self, cr, uid, browse_record, context):
subtypes.append(subtype)
subtype_xmlid = browse_record._track_subtype(dict((col_name, initial[col_name]) for col_name in changes))
# compatibility: use the deprecated _track dict
if not subtype_xmlid and hasattr(self, '_track'):
for field, track_info in self._track.items():
if field not in changes or subtype_xmlid:
continue
for subtype, method in track_info.items():
if method(self, cr, uid, browse_record, context):
_logger.warning("Model %s still using deprecated _track dict; override _track_subtype method instead" % self._name)
subtype_xmlid = subtype
posted = False
for subtype in subtypes:
subtype_rec = self.pool.get('ir.model.data').xmlid_to_object(cr, uid, subtype, context=context)
if subtype_xmlid:
subtype_rec = self.pool['ir.model.data'].xmlid_to_object(cr, uid, subtype_xmlid, context=context)
if not (subtype_rec and subtype_rec.exists()):
_logger.debug('subtype %s not found' % subtype)
_logger.debug('subtype %s not found' % subtype_xmlid)
continue
message = format_message(subtype_rec.description if subtype_rec.description else subtype_rec.name, tracked_values)
self.message_post(cr, uid, browse_record.id, body=message, subtype=subtype, context=context)
posted = True
if not posted:
else:
message = format_message('', tracked_values)
self.message_post(cr, uid, browse_record.id, body=message, context=context)
self.message_post(cr, uid, browse_record.id, body=message, subtype=subtype_xmlid, context=context)
return True
#------------------------------------------------------
+22 -37
View File
@@ -833,29 +833,26 @@ class test_mail(TestMail):
mt_group_public_id = self.mail_message_subtype.create(cr, uid, {'name': 'group_public', 'description': 'Group changed'})
self.ir_model_data.create(cr, uid, {'name': 'mt_group_public', 'model': 'mail.message.subtype', 'module': 'mail', 'res_id': mt_group_public_id})
# Data: alter mail_group model for testing purposes (test on classic, selection and many2one fields)
cls = type(self.mail_group)
self.assertNotIn('_track', cls.__dict__)
cls._track = {
'public': {
'mail.mt_private': lambda self, cr, uid, obj, ctx=None: obj.public == 'private',
},
'name': {
'mail.mt_name_supername': lambda self, cr, uid, obj, ctx=None: obj.name == 'supername',
},
'group_public_id': {
'mail.mt_group_public_set': lambda self, cr, uid, obj, ctx=None: obj.group_public_id,
'mail.mt_group_public': lambda self, cr, uid, obj, ctx=None: True,
},
}
def _track_subtype(self, cr, uid, ids, init_values, context=None):
record = self.browse(cr, uid, ids[0], context=context)
if 'public' in init_values and record.public == 'private':
return 'mail.mt_private'
elif 'name' in init_values and record.name == 'supername':
return 'mail.mt_name_supername'
elif 'group_public_id' in init_values and record.group_public_id:
return 'mail.mt_group_public_set'
elif 'group_public_id' in init_values and not record.group_public_id:
return 'mail.mt_group_public'
return False
self.registry('mail.group')._patch_method('_track_subtype', _track_subtype)
visibility = {'public': 'onchange', 'name': 'always', 'group_public_id': 'onchange'}
cls = type(self.mail_group)
for key in visibility:
self.assertFalse(hasattr(getattr(cls, key), 'track_visibility'))
getattr(cls, key).track_visibility = visibility[key]
@self.addCleanup
def cleanup():
delattr(cls, '_track')
for key in visibility:
del getattr(cls, key).track_visibility
@@ -863,51 +860,39 @@ class test_mail(TestMail):
self.mail_group.write(cr, self.user_raoul_id, [self.group_pigs_id], {'public': 'public'})
self.group_pigs.refresh()
self.assertEqual(len(self.group_pigs.message_ids), 1, 'tracked: a message should have been produced')
# Test: first produced message: no subtype, name change tracked
# Test: first produced message: group public tracking
last_msg = self.group_pigs.message_ids[-1]
self.assertFalse(last_msg.subtype_id, 'tracked: message should not have been linked to a subtype')
self.assertFalse(last_msg.subtype_id.id, 'tracked: message should not have been linked to a subtype')
self.assertIn(u"Selectedgroupofusers\u2192Everyone", _strip_string_spaces(last_msg.body), 'tracked: message body incorrect')
self.assertIn('Pigs', _strip_string_spaces(last_msg.body), 'tracked: message body does not hold always tracked field')
# Test: change name as supername, public as private -> 2 subtypes
self.mail_group.write(cr, self.user_raoul_id, [self.group_pigs_id], {'name': 'supername', 'public': 'private'})
self.group_pigs.refresh()
self.assertEqual(len(self.group_pigs.message_ids), 3, 'tracked: two messages should have been produced')
self.assertEqual(len(self.group_pigs.message_ids), 2, 'tracked: one new tracking message should have been produced')
# Test: first produced message: mt_name_supername
last_msg = self.group_pigs.message_ids[-2]
self.assertEqual(last_msg.subtype_id.id, mt_private_id, 'tracked: message should be linked to mt_private subtype')
self.assertIn('Private public', last_msg.body, 'tracked: message body does not hold the subtype description')
self.assertIn(u'Pigs\u2192supername', _strip_string_spaces(last_msg.body), 'tracked: message body incorrect')
# Test: second produced message: mt_name_supername
last_msg = self.group_pigs.message_ids[-3]
self.assertEqual(last_msg.subtype_id.id, mt_name_supername_id, 'tracked: message should be linked to mt_name_supername subtype')
self.assertIn('Supername name', last_msg.body, 'tracked: message body does not hold the subtype description')
self.assertIn(u"Everyone\u2192Invitedpeopleonly", _strip_string_spaces(last_msg.body), 'tracked: message body incorrect')
self.assertIn(u'Pigs\u2192supername', _strip_string_spaces(last_msg.body), 'tracked feature: message body does not hold always tracked field')
# Test: change public as public, group_public_id -> 2 subtypes, name always tracked
# Test: change public as public, group_public_id -> 1 subtype, name always tracked
self.mail_group.write(cr, self.user_raoul_id, [self.group_pigs_id], {'public': 'public', 'group_public_id': group_system_id})
self.group_pigs.refresh()
self.assertEqual(len(self.group_pigs.message_ids), 5, 'tracked: one message should have been produced')
self.assertEqual(len(self.group_pigs.message_ids), 3, 'tracked: one message should have been produced')
# Test: first produced message: mt_group_public_set_id, with name always tracked, public tracked on change
last_msg = self.group_pigs.message_ids[-4]
last_msg = self.group_pigs.message_ids[-3]
self.assertEqual(last_msg.subtype_id.id, mt_group_public_set_id, 'tracked: message should be linked to mt_group_public_set_id')
self.assertIn('Group set', last_msg.body, 'tracked: message body does not hold the subtype description')
self.assertIn(u"Invitedpeopleonly\u2192Everyone", _strip_string_spaces(last_msg.body), 'tracked: message body does not hold changed tracked field')
self.assertIn(u'HumanResources/Employee\u2192Administration/Settings', _strip_string_spaces(last_msg.body), 'tracked: message body does not hold always tracked field')
# Test: second produced message: mt_group_public_id, with name always tracked, public tracked on change
last_msg = self.group_pigs.message_ids[-5]
self.assertEqual(last_msg.subtype_id.id, mt_group_public_id, 'tracked: message should be linked to mt_group_public_id')
self.assertIn('Group changed', last_msg.body, 'tracked: message body does not hold the subtype description')
self.assertIn(u"Invitedpeopleonly\u2192Everyone", _strip_string_spaces(last_msg.body), 'tracked: message body does not hold changed tracked field')
self.assertIn(u'HumanResources/Employee\u2192Administration/Settings', _strip_string_spaces(last_msg.body), 'tracked: message body does not hold always tracked field')
# Test: change group_public_id to False -> 1 subtype, name always tracked
self.mail_group.write(cr, self.user_raoul_id, [self.group_pigs_id], {'group_public_id': False})
self.group_pigs.refresh()
self.assertEqual(len(self.group_pigs.message_ids), 6, 'tracked: one message should have been produced')
self.assertEqual(len(self.group_pigs.message_ids), 4, 'tracked: one message should have been produced')
# Test: first produced message: mt_group_public_set_id, with name always tracked, public tracked on change
last_msg = self.group_pigs.message_ids[-6]
last_msg = self.group_pigs.message_ids[-4]
self.assertEqual(last_msg.subtype_id.id, mt_group_public_id, 'tracked: message should be linked to mt_group_public_id')
self.assertIn('Group changed', last_msg.body, 'tracked: message body does not hold the subtype description')
self.assertIn(u'Administration/Settings\u2192', _strip_string_spaces(last_msg.body), 'tracked: message body does not hold always tracked field')
@@ -915,4 +900,4 @@ class test_mail(TestMail):
# Test: change not tracked field, no tracking message
self.mail_group.write(cr, self.user_raoul_id, [self.group_pigs_id], {'description': 'Dummy'})
self.group_pigs.refresh()
self.assertEqual(len(self.group_pigs.message_ids), 6, 'tracked: No message should have been produced')
self.assertEqual(len(self.group_pigs.message_ids), 4, 'tracked: No message should have been produced')
+14 -15
View File
@@ -612,22 +612,7 @@ class task(osv.osv):
_description = "Task"
_date_name = "date_start"
_inherit = ['mail.thread', 'ir.needaction_mixin']
_mail_post_access = 'read'
_track = {
'stage_id': {
# this is only an heuristics; depending on your particular stage configuration it may not match all 'new' stages
'project.mt_task_new': lambda self, cr, uid, obj, ctx=None: obj.stage_id and obj.stage_id.sequence <= 1,
'project.mt_task_stage': lambda self, cr, uid, obj, ctx=None: obj.stage_id.sequence > 1,
},
'user_id': {
'project.mt_task_assigned': lambda self, cr, uid, obj, ctx=None: obj.user_id and obj.user_id.id,
},
'kanban_state': {
'project.mt_task_blocked': lambda self, cr, uid, obj, ctx=None: obj.kanban_state == 'blocked',
'project.mt_task_ready': lambda self, cr, uid, obj, ctx=None: obj.kanban_state == 'done',
},
}
def _get_default_partner(self, cr, uid, context=None):
project_id = self._get_default_project_id(cr, uid, context)
@@ -1137,6 +1122,20 @@ class task(osv.osv):
# Mail gateway
# ---------------------------------------------------
def _track_subtype(self, cr, uid, ids, init_values, context=None):
record = self.browse(cr, uid, ids[0], context=context)
if 'kanban_state' in init_values and record.kanban_state == 'blocked':
return 'project.mt_task_blocked'
elif 'kanban_state' in init_values and record.kanban_state == 'done':
return 'project.mt_task_ready'
elif 'user_id' in init_values and record.user_id: # assigned -> new
return 'project.mt_task_new'
elif 'stage_id' in init_values and record.stage_id and record.stage_id.sequence <= 1: # start stage -> new
return 'project.mt_task_new'
elif 'stage_id' in init_values:
return 'project.mt_task_stage'
return super(task, self)._track_subtype(cr, uid, ids, init_values, context=context)
def message_get_reply_to(self, cr, uid, ids, context=None):
""" Override to get the reply_to of the parent project. """
tasks = self.browse(cr, SUPERUSER_ID, ids, context=context)
+13 -26
View File
@@ -62,17 +62,11 @@
<data>
<!-- Task-related subtypes for messaging / Chatter -->
<record id="mt_task_new" model="mail.message.subtype">
<field name="name">Task Created</field>
<field name="name">Task Opened</field>
<field name="res_model">project.task</field>
<field name="default" eval="False"/>
<field name="hidden" eval="True"/>
<field name="description">Task created</field>
</record>
<record id="mt_task_assigned" model="mail.message.subtype">
<field name="name">Task Assigned</field>
<field name="res_model">project.task</field>
<field name="default" eval="False"/>
<field name="description">Task Assigned</field>
<field name="hidden" eval="False"/>
<field name="description">Task opened</field>
</record>
<record id="mt_task_blocked" model="mail.message.subtype">
<field name="name">Task Blocked</field>
@@ -81,10 +75,10 @@
<field name="description">Task blocked</field>
</record>
<record id="mt_task_ready" model="mail.message.subtype">
<field name="name">Task Ready for Next Stage</field>
<field name="name">Task Ready</field>
<field name="res_model">project.task</field>
<field name="default" eval="False"/>
<field name="description">Task Ready for Next Stage</field>
<field name="description">Task ready for Next Stage</field>
</record>
<record id="mt_task_stage" model="mail.message.subtype">
<field name="name">Stage Changed</field>
@@ -94,43 +88,36 @@
</record>
<!-- Project-related subtypes for messaging / Chatter -->
<record id="mt_project_task_new" model="mail.message.subtype">
<field name="name">Task Created</field>
<field name="name">Task Opened</field>
<field name="sequence">10</field>
<field name="res_model">project.project</field>
<field name="default" eval="False"/>
<field name="parent_id" eval="ref('mt_task_new')"/>
<field name="relation_field">project_id</field>
</record>
<record id="mt_project_task_assigned" model="mail.message.subtype">
<field name="name">Task Assigned</field>
<record id="mt_project_task_blocked" model="mail.message.subtype">
<field name="name">Task Blocked</field>
<field name="sequence">11</field>
<field name="res_model">project.project</field>
<field name="default" eval="False"/>
<field name="parent_id" eval="ref('mt_task_assigned')"/>
<field name="parent_id" eval="ref('mt_task_blocked')"/>
<field name="relation_field">project_id</field>
</record>
<record id="mt_project_task_blocked" model="mail.message.subtype">
<field name="name">Task Blocked</field>
<record id="mt_project_task_ready" model="mail.message.subtype">
<field name="name">Task Ready</field>
<field name="sequence">12</field>
<field name="res_model">project.project</field>
<field name="parent_id" eval="ref('mt_task_blocked')"/>
<field name="parent_id" eval="ref('mt_task_ready')"/>
<field name="relation_field">project_id</field>
</record>
<record id="mt_project_task_stage" model="mail.message.subtype">
<field name="name">Task Stage Changed</field>
<field name="sequence">13</field>
<field name="res_model">project.project</field>
<field name="default" eval="False"/>
<field name="parent_id" eval="ref('mt_task_stage')"/>
<field name="relation_field">project_id</field>
</record>
<record id="mt_project_task_ready" model="mail.message.subtype">
<field name="name">Task Ready</field>
<field name="sequence">14</field>
<field name="res_model">project.project</field>
<field name="parent_id" eval="ref('mt_task_ready')"/>
<field name="relation_field">project_id</field>
<field name="default" eval="False" />
</record>
<!-- notify all employees of module installation -->
<record model="mail.message" id="module_install_notification">
+2 -4
View File
@@ -146,11 +146,9 @@ class TestProjectFlow(TestProjectBase):
self.assertEqual(res[0], uid,
'project: message_process: task should have been created by uid as alias_user_id is False on the alias')
# Test: messages
self.assertEqual(len(task.message_ids), 3,
self.assertEqual(len(task.message_ids), 2,
'project: message_process: newly created task should have 2 messages: creation and email')
self.assertEqual(task.message_ids[2].subtype_id.name, 'Task Created',
'project: message_process: first message of new task should have Task Created subtype')
self.assertEqual(task.message_ids[1].subtype_id.name, 'Task Assigned',
self.assertEqual(task.message_ids[1].subtype_id.name, 'Task Opened',
'project: message_process: first message of new task should have Task Created subtype')
self.assertEqual(task.message_ids[0].author_id.id, self.email_partner_id,
'project: message_process: second message should be the one from Agrolait (partner failed)')
+14 -15
View File
@@ -50,22 +50,7 @@ class project_issue(osv.Model):
_description = "Project Issue"
_order = "priority desc, create_date desc"
_inherit = ['mail.thread', 'ir.needaction_mixin']
_mail_post_access = 'read'
_track = {
'stage_id': {
# this is only an heuristics; depending on your particular stage configuration it may not match all 'new' stages
'project_issue.mt_issue_new': lambda self, cr, uid, obj, ctx=None: obj.stage_id and obj.stage_id.sequence <= 1,
'project_issue.mt_issue_stage': lambda self, cr, uid, obj, ctx=None: obj.stage_id and obj.stage_id.sequence > 1,
},
'user_id': {
'project_issue.mt_issue_assigned': lambda self, cr, uid, obj, ctx=None: obj.user_id and obj.user_id.id,
},
'kanban_state': {
'project_issue.mt_issue_blocked': lambda self, cr, uid, obj, ctx=None: obj.kanban_state == 'blocked',
'project_issue.mt_issue_ready': lambda self, cr, uid, obj, ctx=None: obj.kanban_state == 'done',
},
}
def _get_default_partner(self, cr, uid, context=None):
project_id = self._get_default_project_id(cr, uid, context)
@@ -424,6 +409,20 @@ class project_issue(osv.Model):
# Mail gateway
# -------------------------------------------------------
def _track_subtype(self, cr, uid, ids, init_values, context=None):
record = self.browse(cr, uid, ids[0], context=context)
if 'kanban_state' in init_values and record.kanban_state == 'blocked':
return 'project_issue.mt_issue_blocked'
elif 'kanban_state' in init_values and record.kanban_state == 'done':
return 'project_issue.mt_issue_ready'
elif 'user_id' in init_values and record.user_id: # assigned -> new
return 'project_issue.mt_issue_new'
elif 'stage_id' in init_values and record.stage_id and record.stage_id.sequence <= 1: # start stage -> new
return 'project_issue.mt_issue_new'
elif 'stage_id' in init_values:
return 'project_issue.mt_issue_stage'
return super(project_issue, self)._track_subtype(cr, uid, ids, init_values, context=context)
def message_get_reply_to(self, cr, uid, ids, context=None):
""" Override to get the reply_to of the parent project. """
issues = self.browse(cr, SUPERUSER_ID, ids, context=context)
+12 -25
View File
@@ -30,17 +30,11 @@ Access all issues from the top Project menu, and access the issues of a specific
<!-- Issue-related subtypes for messaging / Chatter -->
<record id="mt_issue_new" model="mail.message.subtype">
<field name="name">Issue Created</field>
<field name="name">Issue Opened</field>
<field name="res_model">project.issue</field>
<field name="default" eval="False"/>
<field name="hidden" eval="True"/>
<field name="description">Issue created</field>
</record>
<record id="mt_issue_assigned" model="mail.message.subtype">
<field name="name">Issue Assigned</field>
<field name="res_model">project.issue</field>
<field name="default" eval="False"/>
<field name="description">Issue assigned</field>
<field name="description">Issue opened</field>
</record>
<record id="mt_issue_blocked" model="mail.message.subtype">
<field name="name">Issue Blocked</field>
@@ -49,10 +43,10 @@ Access all issues from the top Project menu, and access the issues of a specific
<field name="description">Issue blocked</field>
</record>
<record id="mt_issue_ready" model="mail.message.subtype">
<field name="name">Issue Ready for Next Stage</field>
<field name="name">Issue Ready</field>
<field name="res_model">project.issue</field>
<field name="default" eval="False"/>
<field name="description">Issue Ready for Next Stage</field>
<field name="description">Issue ready for next sStage</field>
</record>
<record id="mt_issue_stage" model="mail.message.subtype">
<field name="name">Stage Changed</field>
@@ -62,44 +56,37 @@ Access all issues from the top Project menu, and access the issues of a specific
</record>
<!-- Project-related subtypes for messaging / Chatter -->
<record id="mt_project_issue_new" model="mail.message.subtype">
<field name="name">Issue Created</field>
<field name="name">Issue Opened</field>
<field name="sequence">20</field>
<field name="res_model">project.project</field>
<field name="default" eval="False"/>
<field name="parent_id" eval="ref('mt_issue_new')"/>
<field name="relation_field">project_id</field>
</record>
<record id="mt_project_issue_assigned" model="mail.message.subtype">
<field name="name">Issue Assigned</field>
<record id="mt_project_issue_blocked" model="mail.message.subtype">
<field name="name">Issue Blocked</field>
<field name="sequence">21</field>
<field name="res_model">project.project</field>
<field name="default" eval="False"/>
<field name="parent_id" eval="ref('mt_issue_assigned')"/>
<field name="parent_id" eval="ref('mt_issue_blocked')"/>
<field name="relation_field">project_id</field>
</record>
<record id="mt_project_issue_blocked" model="mail.message.subtype">
<field name="name">Issue Blocked</field>
<record id="mt_project_issue_ready" model="mail.message.subtype">
<field name="name">Issue Ready</field>
<field name="sequence">22</field>
<field name="res_model">project.project</field>
<field name="parent_id" eval="ref('mt_issue_blocked')"/>
<field name="parent_id" eval="ref('mt_issue_ready')"/>
<field name="relation_field">project_id</field>
</record>
<record id="mt_project_issue_stage" model="mail.message.subtype">
<field name="name">Issue Stage Changed</field>
<field name="sequence">23</field>
<field name="res_model">project.project</field>
<field name="default" eval="False"/>
<field name="description">Stage changed</field>
<field name="parent_id" eval="ref('mt_issue_stage')"/>
<field name="relation_field">project_id</field>
</record>
<record id="mt_project_issue_ready" model="mail.message.subtype">
<field name="name">Issue Ready</field>
<field name="sequence">24</field>
<field name="res_model">project.project</field>
<field name="parent_id" eval="ref('mt_issue_ready')"/>
<field name="relation_field">project_id</field>
<field name="default" eval="False" />
</record>
</data>
</openerp>
+11 -7
View File
@@ -197,13 +197,7 @@ class purchase_order(osv.osv):
('done', 'Done'),
('cancel', 'Cancelled')
]
_track = {
'state': {
'purchase.mt_rfq_confirmed': lambda self, cr, uid, obj, ctx=None: obj.state == 'confirmed',
'purchase.mt_rfq_approved': lambda self, cr, uid, obj, ctx=None: obj.state == 'approved',
'purchase.mt_rfq_done': lambda self, cr, uid, obj, ctx=None: obj.state == 'done',
},
}
_columns = {
'name': fields.char('Order Reference', required=True, select=True, copy=False,
help="Unique number of the purchase order, "
@@ -344,6 +338,16 @@ class purchase_order(osv.osv):
return super(purchase_order, self).unlink(cr, uid, unlink_ids, context=context)
def _track_subtype(self, cr, uid, ids, init_values, context=None):
record = self.browse(cr, uid, ids[0], context=context)
if 'state' in init_values and record.state == 'approved':
return 'purchase.mt_rfq_approved'
elif 'state' in init_values and record.state == 'confirmed':
return 'purchase.mt_rfq_confirmed'
elif 'state' in init_values and record.state == 'done':
return 'purchase.mt_rfq_done'
return super(purchase_order, self)._track_subtype(cr, uid, ids, init_values, context=context)
def set_order_line_status(self, cr, uid, ids, status, context=None):
line = self.pool.get('purchase.order.line')
order_line_ids = []
+8 -6
View File
@@ -38,12 +38,6 @@ class sale_order(osv.osv):
_name = "sale.order"
_inherit = ['mail.thread', 'ir.needaction_mixin']
_description = "Sales Order"
_track = {
'state': {
'sale.mt_order_confirmed': lambda self, cr, uid, obj, ctx=None: obj.state in ['manual'],
'sale.mt_order_sent': lambda self, cr, uid, obj, ctx=None: obj.state in ['sent']
},
}
def _amount_line_tax(self, cr, uid, line, context=None):
val = 0.0
@@ -291,6 +285,14 @@ class sale_order(osv.osv):
'nodestroy': True,
}
def _track_subtype(self, cr, uid, ids, init_values, context=None):
record = self.browse(cr, uid, ids[0], context=context)
if 'state' in init_values and record.state == 'manual':
return 'sale.mt_order_confirmed'
elif 'state' in init_values and record.state == 'sent':
return 'sale.mt_order_sent'
return super(sale_order, self)._track_subtype(cr, uid, ids, init_values, context=context)
def onchange_pricelist_id(self, cr, uid, ids, pricelist_id, order_lines, context=None):
context = context or {}
if not pricelist_id:
@@ -31,12 +31,6 @@ class stock_landed_cost(osv.osv):
_description = 'Stock Landed Cost'
_inherit = 'mail.thread'
_track = {
'state': {
'stock_landed_costs.mt_stock_landed_cost_open': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'done',
},
}
def _total_amount(self, cr, uid, ids, name, args, context=None):
result = {}
for cost in self.browse(cr, uid, ids, context=context):
@@ -273,6 +267,12 @@ class stock_landed_cost(osv.osv):
line_obj.write(cr, uid, key, {'additional_landed_cost': value}, context=context)
return True
def _track_subtype(self, cr, uid, ids, init_values, context=None):
record = self.browse(cr, uid, ids[0], context=context)
if 'state' in init_values and record.state == 'done':
return 'stock_landed_costs.mt_stock_landed_cost_open'
return super(stock_landed_cost, self)._track_subtype(cr, uid, ids, init_values, context=context)
class stock_landed_cost_lines(osv.osv):
_name = 'stock.landed.cost.lines'
+10 -6
View File
@@ -10,14 +10,9 @@ from openerp.addons.website.models.website import slug
class event(models.Model):
_name = 'event.event'
_inherit = ['event.event', 'website.seo.metadata', 'website.published.mixin']
_track = {
'website_published': {
'website_event.mt_event_published': lambda self, cr, uid, obj, ctx=None: obj.website_published,
'website_event.mt_event_unpublished': lambda self, cr, uid, obj, ctx=None: not obj.website_published
},
}
twitter_hashtag = fields.Char('Twitter Hashtag', default=lambda self: self._default_hashtag())
website_published = fields.Boolean(track_visibility='onchange')
# TDE TODO FIXME: when website_mail/mail_thread.py inheritance work -> this field won't be necessary
website_message_ids = fields.One2many(
'mail.message', 'res_id',
@@ -89,3 +84,12 @@ class event(models.Model):
if event.address_id:
return self.browse(cr, SUPERUSER_ID, ids[0], context=context).address_id.google_map_link()
return None
@api.multi
def _track_subtype(self, init_values):
self.ensure_one()
if 'website_published' in init_values and self.website_published:
return 'website_event.mt_event_published'
elif 'website_published' in init_values and not self.website_published:
return 'website_event.mt_event_unpublished'
return super(event, self)._track_subtype(init_values)