diff options
| author | Thibault Delavallée <tde@odoo.com> | 2017-11-17 12:59:00 +0100 |
|---|---|---|
| committer | Thibault Delavallée <tde@odoo.com> | 2018-03-27 15:00:46 +0200 |
| commit | 60b81e00b27edd95d31d668687561e0b2dd7c714 (patch) | |
| tree | c9153842fd664bb343587d8e3fb29d2ac2549f8a /addons | |
| parent | 093dd9b5122bc8a627d758a86839b42d3fdc0bc4 (diff) | |
[IMP] mail: add tool methods to manage activities directly from the mixin
This commit adds tools methods related to activities in the mail.activity
mixin. It gives to models inheriting from the activity mixin an easy-to-use
API to schedule, unlink or mark activities as done. Purpose of those methods
is to avoid having people manually managing activities in the code to hide
the technical details of the activities model, notably access rights
or activity types.
A field is added on activity model to indicate they have been generated
automatically. This way when rescheduling or unlinking based on some
specific activity types we do not change user-created activities.
Those methods include scheduling activities, changing their dates, marking
them as done or unlinking them. Future commits will use those methods
in various addons to automatically generate activities based on workflow
we want to implement.
This commit also adds tests for the newly added code. Future commits should
probably have a look at activity security and add some tests cases to check
it is correctly taken into account. It is considered a bit out of scope for
this task.
Thanks to @jem-odoo for its in-depth review of this commit. Well thanks for
other commits also.
Diffstat (limited to 'addons')
| -rw-r--r-- | addons/mail/models/mail_activity.py | 113 | ||||
| -rw-r--r-- | addons/test_mail/__manifest__.py | 1 | ||||
| -rw-r--r-- | addons/test_mail/data/data.xml | 23 | ||||
| -rw-r--r-- | addons/test_mail/models/test_mail_models.py | 9 | ||||
| -rw-r--r-- | addons/test_mail/tests/test_mail_activity.py | 72 | ||||
| -rw-r--r-- | addons/test_mail/tests/test_performance.py | 20 |
6 files changed, 234 insertions, 4 deletions
diff --git a/addons/mail/models/mail_activity.py b/addons/mail/models/mail_activity.py index 1259e7395f8..0f849526b90 100644 --- a/addons/mail/models/mail_activity.py +++ b/addons/mail/models/mail_activity.py @@ -93,6 +93,9 @@ class MailActivity(models.Model): active = fields.Boolean( 'Open', default=True, help='Done reminders should be archived instead of marked as done.') + automated = fields.Boolean( + 'Automated activity', readonly=True, + help='Indicates this activity has been created automatically and not by any user.') # description user_id = fields.Many2one( 'res.users', 'Assigned to', @@ -345,7 +348,15 @@ class MailActivityMixin(models.AbstractModel): There is also a kanban widget defined. It defines a small widget to integrate in kanban vignettes. It allow to manage activities directly from the kanban view. Use widget="kanban_activity" on activitiy_ids field in kanban view to - use it.""" + use it. + + Some context keys allow to control the mixin behavior. Use those in some + specific cases like import + + * ``mail_activity_automation_skip``: skip activities automation; it means + no automated activities will be generated, updated or unlinked, allowing + to save computation and avoid generating unwanted activities; + """ _name = 'mail.activity.mixin' _description = 'Activity Mixin' @@ -432,3 +443,103 @@ class MailActivityMixin(models.AbstractModel): [('res_model', '=', self._name), ('res_id', 'in', record_ids)] ).unlink() return result + + def activity_schedule(self, act_type_xmlid='', date_deadline=None, summary='', note='', **act_values): + """ Schedule an activity on each record of the current record set. + This method allow to provide as parameter act_type_xmlid. This is an + xml_id of activity type instead of directly giving an activity_type_id. + It is useful to avoid having various "env.ref" in the code and allow + to let the mixin handle access rights. + """ + if self.env.context.get('mail_activity_automation_skip'): + return False + + if not date_deadline: + date_deadline = fields.Date.today() + if act_type_xmlid: + activity_type = self.sudo().env.ref(act_type_xmlid) + else: + activity_type = self.env['mail.activity.type'].sudo().browse(act_values['activity_type_id']) + + model_id = self.env['ir.model']._get(self._name).id + activities = self.env['mail.activity'] + for record in self: + create_vals = { + 'activity_type_id': activity_type.id, + 'summary': summary or activity_type.summary, + 'automated': True, + 'note': note, + 'date_deadline': date_deadline, + 'res_model_id': model_id, + 'res_id': record.id, + } + create_vals.update(act_values) + activities |= self.env['mail.activity'].create(create_vals) + return activities + + def activity_reschedule(self, act_type_xmlids, user_id=None, date_deadline=None): + """ Reschedule some automated activities. Activities to reschedule are + selected based on type xml ids and optionally by user. Purpose is to be + able to change the deadline, not anything else currently. """ + if self.env.context.get('mail_activity_automation_skip'): + return False + + Data = self.env['ir.model.data'].sudo() + activity_types_ids = [Data.xmlid_to_res_id(xmlid) for xmlid in act_type_xmlids] + domain = [ + '&', '&', '&', + ('res_model', '=', self._name), + ('res_id', 'in', self.ids), + ('automated', '=', True), + ('activity_type_id', 'in', activity_types_ids) + ] + if user_id: + domain = ['&'] + domain + [('user_id', '=', user_id)] + activities = self.env['mail.activity'].search(domain) + if activities: + activities.write({ + 'date_deadline': date_deadline, + }) + return activities + + def activity_feedback(self, act_type_xmlids, user_id=None, feedback=None): + """ Set activities as done, limiting to some activity types and + optionally to a given user. """ + if self.env.context.get('mail_activity_automation_skip'): + return False + + Data = self.env['ir.model.data'].sudo() + activity_types_ids = [Data.xmlid_to_res_id(xmlid) for xmlid in act_type_xmlids] + domain = [ + '&', '&', '&', + ('res_model', '=', self._name), + ('res_id', 'in', self.ids), + ('automated', '=', True), + ('activity_type_id', 'in', activity_types_ids) + ] + if user_id: + domain = ['&'] + domain + [('user_id', '=', user_id)] + activities = self.env['mail.activity'].search(domain) + if activities: + activities.action_feedback(feedback=feedback) + return True + + def activity_unlink(self, act_type_xmlids, user_id=None): + """ Unlink activities, limiting to some activity types and optionally + to a given user. """ + if self.env.context.get('mail_activity_automation_skip'): + return False + + Data = self.env['ir.model.data'].sudo() + activity_types_ids = [Data.xmlid_to_res_id(xmlid) for xmlid in act_type_xmlids] + domain = [ + '&', '&', '&', + ('res_model', '=', self._name), + ('res_id', 'in', self.ids), + ('automated', '=', True), + ('activity_type_id', 'in', activity_types_ids) + ] + if user_id: + domain = ['&'] + domain + [('user_id', '=', user_id)] + self.env['mail.activity'].search(domain).unlink() + return True diff --git a/addons/test_mail/__manifest__.py b/addons/test_mail/__manifest__.py index 51d9cc7fe42..40d43883d4b 100644 --- a/addons/test_mail/__manifest__.py +++ b/addons/test_mail/__manifest__.py @@ -12,6 +12,7 @@ tests independently to functional aspects of other models. """, 'depends': ['test_performance', 'mail'], 'data': [ 'security/ir.model.access.csv', + 'data/data.xml', ], 'demo': [ 'data/demo.xml', diff --git a/addons/test_mail/data/data.xml b/addons/test_mail/data/data.xml new file mode 100644 index 00000000000..19be18eb2d6 --- /dev/null +++ b/addons/test_mail/data/data.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + + <record id="mail_act_test_todo" model="mail.activity.type"> + <field name="name">Do Stuff</field> + <field name="summary">Really?! Wow! A superpowers drug you can just rub onto your skin?</field> + <field name="category">default</field> + <field name="res_model_id" ref="test_mail.model_mail_test_activity"/> + </record> + <record id="mail_act_test_meeting" model="mail.activity.type"> + <field name="name">Meet People</field> + <field name="summary">You'd think it would be something you'd have to freebase. Noooooo!</field> + <field name="category">default</field> + <field name="res_model_id" ref="test_mail.model_mail_test_activity"/> + </record> + <record id="mail_act_test_call" model="mail.activity.type"> + <field name="name">Call People</field> + <field name="summary">Then throw her in the laundry room, which will hereafter be referred to as "the brig".</field> + <field name="category">default</field> + <field name="res_model_id" ref="test_mail.model_mail_test_activity"/> + </record> + +</odoo> diff --git a/addons/test_mail/models/test_mail_models.py b/addons/test_mail/models/test_mail_models.py index 4c1fd408ddf..06d84986fe8 100644 --- a/addons/test_mail/models/test_mail_models.py +++ b/addons/test_mail/models/test_mail_models.py @@ -38,6 +38,15 @@ class MailTestActivity(models.Model): name = fields.Char() email_from = fields.Char() + def action_start(self, action_summary): + self.activity_schedule( + 'test_mail.mail_act_test_todo', + summary=action_summary + ) + + def action_close(self, action_feedback): + self.activity_feedback(['test_mail.mail_act_test_todo'], feedback=action_feedback) + class MailTestFull(models.Model): """ This model can be used in tests when complex chatter features are diff --git a/addons/test_mail/tests/test_mail_activity.py b/addons/test_mail/tests/test_mail_activity.py index 3a86d86ed4b..e340358a5ee 100644 --- a/addons/test_mail/tests/test_mail_activity.py +++ b/addons/test_mail/tests/test_mail_activity.py @@ -14,7 +14,13 @@ class TestMailActivity(BaseFunctionalTest): @classmethod def setUpClass(cls): super(TestMailActivity, cls).setUpClass() - cls.test_record = cls.env['mail.test.activity'].create({'name': 'Test'}) + cls.test_record = cls.env['mail.test.activity'].with_context(cls._quick_create_ctx).create({'name': 'Test'}) + # reset ctx + cls.test_record = cls.test_record.with_context( + mail_create_nolog=False, + mail_create_nosubscribe=False, + mail_notrack=False + ) def test_activity_flow_employee(self): with self.sudoAs('ernest'): @@ -106,3 +112,67 @@ class TestMailActivity(BaseFunctionalTest): self.assertEqual(reminder.summary, 'Reminder') reminder.write({'note': 'Holidays', 'summary': 'Summary'}) self.assertEqual(reminder.summary, 'Summary') + + def test_activity_mixin(self): + today = date.today() + with self.sudoAs('ernest'): + self.assertEqual(self.test_record.env.user, self.user_employee) + + # Test various scheduling of activities + act1 = self.test_record.activity_schedule( + 'test_mail.mail_act_test_todo', + today + relativedelta(days=1), + user_id=self.user_admin.id) + self.assertEqual(act1.automated, True) + + act_type = self.env.ref('test_mail.mail_act_test_todo') + self.assertEqual(self.test_record.activity_summary, act_type.summary) + self.assertEqual(self.test_record.activity_state, 'planned') + self.assertEqual(self.test_record.activity_user_id, self.user_admin) + + act2 = self.test_record.activity_schedule( + 'test_mail.mail_act_test_meeting', + today + relativedelta(days=-1)) + self.assertEqual(self.test_record.activity_state, 'overdue') + self.assertEqual(self.test_record.activity_user_id, self.user_employee) + + act3 = self.test_record.activity_schedule( + 'test_mail.mail_act_test_todo', + today + relativedelta(days=3), + user_id=self.user_employee.id) + self.assertEqual(self.test_record.activity_state, 'overdue') + self.assertEqual(self.test_record.activity_user_id, self.user_employee) + + self.assertEqual(self.test_record.activity_ids, act1 | act2 | act3) + + # Perform todo activities for admin + self.test_record.activity_feedback( + ['test_mail.mail_act_test_todo'], + user_id=self.user_admin.id, + feedback='Test feedback',) + self.assertEqual(self.test_record.activity_ids, act2 | act3) + + # Reschedule all activities, should update the record state + self.assertEqual(self.test_record.activity_state, 'overdue') + self.test_record.activity_reschedule( + ['test_mail.mail_act_test_meeting', 'test_mail.mail_act_test_todo'], + date_deadline=today + relativedelta(days=3) + ) + self.assertEqual(self.test_record.activity_state, 'planned') + + # Perform todo activities for remaining people + self.test_record.activity_feedback( + ['test_mail.mail_act_test_todo'], + feedback='Test feedback') + + # Setting activities as done should delete them and post messages + self.assertEqual(self.test_record.activity_ids, act2) + self.assertEqual(len(self.test_record.message_ids), 2) + self.assertEqual(self.test_record.message_ids.mapped('subtype_id'), self.env.ref('mail.mt_activities')) + + # Perform meeting activities + self.test_record.activity_unlink(['test_mail.mail_act_test_meeting']) + + # Canceling activities should simply remove them + self.assertEqual(self.test_record.activity_ids, self.env['mail.activity']) + self.assertEqual(len(self.test_record.message_ids), 2) diff --git a/addons/test_mail/tests/test_performance.py b/addons/test_mail/tests/test_performance.py index 183165f5e41..be887fc0173 100644 --- a/addons/test_mail/tests/test_performance.py +++ b/addons/test_mail/tests/test_performance.py @@ -160,16 +160,32 @@ class TestAdvMailPerformance(TransactionCase): 'default_res_model': 'mail.test.activity', }) - with self.assertQueryCount(admin=26, emp=32): # test_mail only: 26 - 32 + with self.assertQueryCount(admin=26, emp=33): # test_mail only: 26 - 32 activity = MailActivity.create({ 'summary': 'Test Activity', 'res_id': record.id, 'activity_type_id': self.env.ref('mail.mail_activity_data_todo').id, }) - with self.assertQueryCount(admin=59, emp=88): # test_mail only: 58 - 86 + with self.assertQueryCount(admin=59, emp=88): # test_mail only: 58 - 87 activity.action_feedback(feedback='Zizisse Done !') + @users('admin', 'emp') + @warmup + @mute_logger('odoo.models.unlink') + def test_adv_activity_mixin(self): + record = self.env['mail.test.activity'].create({'name': 'Test'}) + + with self.assertQueryCount(admin=26, emp=32): # test_mail only: 26 - 32 + record.action_start('Test Start') + + record.write({'name': 'Dupe write'}) + + with self.assertQueryCount(admin=58, emp=88): # test_mail only: 58 - 88 + record.action_close('Dupe feedback') + + self.assertEqual(record.activity_ids, self.env['mail.activity']) + @mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.models.unlink') @users('admin', 'emp') @warmup |
