diff --git a/addons/mail/i18n/mail.pot b/addons/mail/i18n/mail.pot index c593c61adf8..69766609493 100644 --- a/addons/mail/i18n/mail.pot +++ b/addons/mail/i18n/mail.pot @@ -2605,8 +2605,8 @@ msgstr "" #. module: mail #: model:ir.model.fields,help:mail.field_mail_alias_domain__default_from msgid "" -"Default from when it does not match outgoing server filters. Can be either a " -"local-part e.g. 'notifications' either a complete email address e.g. " +"Default from when it does not match outgoing server filters. Can be either a" +" local-part e.g. 'notifications' either a complete email address e.g. " "'notifications@example.com' to override all outgoing emails." msgstr "" @@ -9402,6 +9402,13 @@ msgstr "" msgid "Unit of delay" msgstr "" +#. module: mail +#. odoo-python +#: code:addons/mail/models/mail_tracking_value.py:0 +#, python-format +msgid "Unknown" +msgstr "" + #. module: mail #. odoo-python #: code:addons/mail/models/mail_notification.py:0 diff --git a/addons/mail/models/mail_tracking_value.py b/addons/mail/models/mail_tracking_value.py index 4dd4b822264..ce1dcfc9d72 100644 --- a/addons/mail/models/mail_tracking_value.py +++ b/addons/mail/models/mail_tracking_value.py @@ -3,7 +3,7 @@ from datetime import datetime -from odoo import api, fields, models +from odoo import api, fields, models, _ class MailTracking(models.Model): @@ -112,34 +112,59 @@ class MailTracking(models.Model): to display tracking values. Order it according to asked display, aka ascending sequence (and field name). + :return list: for each tracking value in self, their formatted display + values given as a dict; + """ + model_map = {} + for tracking in self: + model = tracking.field_id.model or tracking.mail_message_id.model + model_map.setdefault(model, self.browse()) + model_map[model] += tracking + formatted = [] + for model, trackings in model_map.items(): + formatted += trackings._tracking_value_format_model(model) + return formatted + + def _tracking_value_format_model(self, model): + """ Return structure and formatted data structure to be used by chatter + to display tracking values. Order it according to asked display, aka + ascending sequence (and field name). + :return list: for each tracking value in self, their formatted display values given as a dict; """ if not self: return [] - field_models = self.field_id.mapped('model') - if len(set(field_models)) != 1: - raise ValueError('All tracking value should belong to the same model.') - TrackedModel = self.env[field_models[0]] - tracked_fields = TrackedModel.fields_get(self.field_id.mapped('name'), attributes={'string', 'type'}) + + # fetch model-based information + if model: + TrackedModel = self.env[model] + tracked_fields = TrackedModel.fields_get(self.field_id.mapped('name'), attributes={'string', 'type'}) + model_sequence_info = dict(TrackedModel._mail_track_order_fields(tracked_fields)) if model else {} + else: + tracked_fields, model_sequence_info = {}, {} + + # generate sequence of trackings + fields_sequence_map = dict( + { + tracking.field_info['name']: tracking.field_info.get('sequence', 100) + for tracking in self.filtered('field_info') + }, + **model_sequence_info, + ) + # generate dict of field information, if available fields_col_info = ( tracked_fields.get(tracking.field_id.name) or { - 'string': tracking.field_info['desc'], - 'type': tracking.field_info['type'], - } - for tracking in self - ) - fields_sequence_map = dict( - {tracking.field_info['name']: tracking.field_info.get('sequence', 100) - for tracking in self.filtered('field_info')}, - **dict(TrackedModel._mail_track_order_fields(tracked_fields)) + 'string': tracking.field_info['desc'] if tracking.field_info else _('Unknown'), + 'type': tracking.field_info['type'] if tracking.field_info else 'char', + } for tracking in self ) formatted = [ { 'changedField': col_info['string'], 'id': tracking.id, - 'fieldName': tracking.field_id.name or tracking.field_info['name'], + 'fieldName': tracking.field_id.name or (tracking.field_info['name'] if tracking.field_info else 'unknown'), 'fieldType': col_info['type'], 'newValue': { 'currencyId': tracking.currency_id.id, @@ -153,7 +178,7 @@ class MailTracking(models.Model): for tracking, col_info in zip(self, fields_col_info) ] formatted.sort( - key=lambda info: (fields_sequence_map[info['fieldName']], info['fieldName']), + key=lambda info: (fields_sequence_map.get(info['fieldName'], 100), info['fieldName']), reverse=False, ) return formatted diff --git a/addons/test_mail/models/test_mail_corner_case_models.py b/addons/test_mail/models/test_mail_corner_case_models.py index 89de852a048..157f7a9aed9 100644 --- a/addons/test_mail/models/test_mail_corner_case_models.py +++ b/addons/test_mail/models/test_mail_corner_case_models.py @@ -152,6 +152,16 @@ class MailTestTrackCompute(models.Model): partner_phone = fields.Char(related='partner_id.phone', tracking=True) +class MailTestTrackGroups(models.Model): + _name = 'mail.test.track.groups' + _description = "Test tracking with groups" + _inherit = ['mail.thread'] + + name = fields.Char(tracking=1) + partner_id = fields.Many2one('res.partner', tracking=2, groups="base.group_user") + secret = fields.Char(tracking=3, groups="base.group_user") + + class MailTestTrackMonetary(models.Model): _name = 'mail.test.track.monetary' _description = 'Test tracking monetary field' diff --git a/addons/test_mail/security/ir.model.access.csv b/addons/test_mail/security/ir.model.access.csv index be96b530ae1..416d141e5fc 100644 --- a/addons/test_mail/security/ir.model.access.csv +++ b/addons/test_mail/security/ir.model.access.csv @@ -50,6 +50,7 @@ access_mail_test_track_all,mail.test.track.all,model_mail_test_track_all,base.gr access_mail_test_track_all_m2m,mail.test.track.all.m2m,model_mail_test_track_all_m2m,base.group_user,1,1,1,1 access_mail_test_track_all_o2m,mail.test.track.all.o2m,model_mail_test_track_all_o2m,base.group_user,1,1,1,1 access_mail_test_track_compute,mail.test.track.compute,model_mail_test_track_compute,base.group_user,1,1,1,1 +access_mail_test_track_groups,mail.test.track.groups,model_mail_test_track_groups,base.group_user,1,1,1,1 access_mail_test_track_monetary,mail.test.track.monetary,model_mail_test_track_monetary,base.group_user,1,1,1,1 access_mail_test_track_selection_portal,mail.test.track.selection.portal,model_mail_test_track_selection,base.group_portal,0,0,0,0 access_mail_test_track_selection_user,mail.test.track.selection.user,model_mail_test_track_selection,base.group_user,1,1,1,1 diff --git a/addons/test_mail/tests/test_message_track.py b/addons/test_mail/tests/test_message_track.py index f59796e04ce..ba4a96aba29 100644 --- a/addons/test_mail/tests/test_message_track.py +++ b/addons/test_mail/tests/test_message_track.py @@ -746,6 +746,84 @@ class TestTrackingInternals(MailCommon): test_record, ) + @users('employee') + def test_track_multi_models(self): + """ Some models track value coming from another model e.g. when having + a sub model (lines) on which some value should be tracked on a parent + model. Test there is no model mismatch. """ + main_track = self.env['mail.test.track.all'].create({ + 'name': 'Multi Models Tracking', + 'char_field': 'char_value', + }) + self.flush_tracking() + self.assertEqual(len(main_track.message_ids), 1) + self.assertFalse(main_track.message_ids.tracking_value_ids) + + sub_track = self.env['mail.test.track.groups'].create({ + 'name': 'Groups', + 'secret': 'secret', + }) + # some custom code generates tracking values on main_track + main_track.message_post( + body='Custom Log with Tracking', + tracking_value_ids=[ + (0, 0, { + 'field_id': self.env['ir.model.fields']._get(sub_track._name, 'secret').id, + 'new_value_char': 'secret', + 'old_value_char': False, + }), + (0, 0, { + 'field_id': False, + 'new_value_integer': self.env.uid, + 'old_value_integer': False, + }), + (0, 0, { + 'field_id': False, + 'field_info': { + 'desc': 'Old integer', + 'name': 'Removed', + 'sequence': 35, + 'type': 'integer', + }, + 'new_value_integer': 35, + 'old_value_integer': 30, + }), + ], + ) + trackings = main_track.message_ids.sudo().tracking_value_ids + self.assertEqual(len(trackings), 3) + + # check formatting, as it fetches info on model + formatted = trackings._tracking_value_format() + self.assertEqual( + formatted, + [ + { + 'changedField': 'Secret', + 'id': trackings[0].id, + 'fieldName': 'secret', + 'fieldType': 'char', + 'newValue': {'currencyId': False, 'value': 'secret'}, + 'oldValue': {'currencyId': False, 'value': False} + }, { + 'changedField': 'Old integer', + 'id': trackings[2].id, + 'fieldName': 'Removed', + 'fieldType': 'integer', + 'newValue': {'currencyId': False, 'value': 35}, + 'oldValue': {'currencyId': False, 'value': 30} + }, { + 'changedField': 'Unknown', + 'id': trackings[1].id, + 'fieldName': 'unknown', + 'fieldType': 'char', + 'newValue': {'currencyId': False, 'value': False}, + 'oldValue': {'currencyId': False, 'value': False} + } + ] + ) + + @users('employee') def test_track_sequence(self): """ Update some tracked fields and check that the mail.tracking.value