From 36bca6eb52188c37229b0a188ebbf14ef3bdad20 Mon Sep 17 00:00:00 2001 From: fw-bot Date: Wed, 9 Jun 2021 11:32:02 +0000 Subject: [PATCH] [IMP] calendar: Forward port of Saas 14.3 calendar perf yti Forward port of https://github.com/odoo/odoo/pull/71808 closes odoo/odoo#72043 X-original-commit: 4f13a2214aed380c8aea984d3414caca506401bf Signed-off-by: Yannick Tivisse (yti) Signed-off-by: Arnaud Joset --- addons/calendar/models/calendar_attendee.py | 6 +- addons/calendar/models/calendar_event.py | 58 +++++++++------ .../tests/test_sync_odoo2google.py | 71 ++++++++++++++++++- .../hr_holidays/tests/test_company_leave.py | 2 +- .../tests/test_performance.py | 2 +- 5 files changed, 112 insertions(+), 27 deletions(-) diff --git a/addons/calendar/models/calendar_attendee.py b/addons/calendar/models/calendar_attendee.py index 503b7d5363f..04b1706b29e 100644 --- a/addons/calendar/models/calendar_attendee.py +++ b/addons/calendar/models/calendar_attendee.py @@ -4,6 +4,7 @@ import uuid import base64 import logging +from collections import defaultdict from odoo import api, fields, models, _ from odoo.addons.base.models.res_partner import _tz_get from odoo.exceptions import UserError @@ -76,11 +77,14 @@ class Attendee(models.Model): raise UserError(_('You cannot duplicate a calendar attendee.')) def _subscribe_partner(self): + mapped_followers = defaultdict(lambda: self.env['calendar.event']) for event in self.event_id: partners = (event.attendee_ids & self).partner_id - event.message_partner_ids # current user is automatically added as followers, don't add it twice. partners -= self.env.user.partner_id - event.message_subscribe(partner_ids=partners.ids) + mapped_followers[partners] |= event + for partners, events in mapped_followers.items(): + events.message_subscribe(partner_ids=partners.ids) def _unsubscribe_partner(self): for event in self.event_id: diff --git a/addons/calendar/models/calendar_event.py b/addons/calendar/models/calendar_event.py index cff68d85215..fc1bca028a1 100644 --- a/addons/calendar/models/calendar_event.py +++ b/addons/calendar/models/calendar_event.py @@ -68,6 +68,7 @@ class Meeting(models.Model): if 'res_model_id' not in defaults and 'res_model_id' in fields and \ self.env.context.get('active_model') and self.env.context['active_model'] != 'calendar.event': defaults['res_model_id'] = self.env['ir.model'].sudo().search([('model', '=', self.env.context['active_model'])], limit=1).id + defaults['res_model'] = self.env.context.get('active_model') if 'res_id' not in defaults and 'res_id' in fields and \ defaults.get('res_model_id') and self.env.context.get('active_id'): defaults['res_id'] = self.env.context['active_id'] @@ -354,25 +355,32 @@ class Meeting(models.Model): for vals in vals_list ] - for values in vals_list: - # created from calendar: try to create an activity on the related record - if not values.get('activity_ids'): - defaults = self.default_get(['activity_ids', 'res_model_id', 'res_id', 'user_id']) + defaults = self.default_get(['activity_ids', 'res_model_id', 'res_id', 'user_id', 'res_model']) + meeting_activity_type = self.env['mail.activity.type'].search([('category', '=', 'meeting')], limit=1) + # get list of models ids and filter out None values directly + model_ids = list(filter(bool, {values.get('res_model_id', defaults.get('res_model_id')) for values in vals_list})) + model_name = defaults.get('res_model') + valid_activity_model_ids = model_name and self.env[model_name].sudo().browse(model_ids).filtered(lambda m: 'activity_ids' in m).ids or [] + if meeting_activity_type and not defaults.get('activity_ids'): + for values in vals_list: + # created from calendar: try to create an activity on the related record + if values.get('activity_ids'): + continue res_model_id = values.get('res_model_id', defaults.get('res_model_id')) res_id = values.get('res_id', defaults.get('res_id')) user_id = values.get('user_id', defaults.get('user_id')) - if not defaults.get('activity_ids') and res_model_id and res_id: - if hasattr(self.env[self.env['ir.model'].sudo().browse(res_model_id).model], 'activity_ids'): - meeting_activity_type = self.env['mail.activity.type'].search([('category', '=', 'meeting')], limit=1) - if meeting_activity_type: - activity_vals = { - 'res_model_id': res_model_id, - 'res_id': res_id, - 'activity_type_id': meeting_activity_type.id, - } - if user_id: - activity_vals['user_id'] = user_id - values['activity_ids'] = [(0, 0, activity_vals)] + if not res_model_id or not res_id: + continue + if res_model_id not in valid_activity_model_ids: + continue + activity_vals = { + 'res_model_id': res_model_id, + 'res_id': res_id, + 'activity_type_id': meeting_activity_type.id, + } + if user_id: + activity_vals['user_id'] = user_id + values['activity_ids'] = [(0, 0, activity_vals)] # Automatically add the current partner when creating an event if there is none (happens when we quickcreate an event) self_partner_id = [(4, self.env.user.partner_id.id)] @@ -556,10 +564,13 @@ class Meeting(models.Model): added_partner_ids += [command[1]] if command[1] not in self.partner_ids.ids else [] # commands 0 and 1 not supported - attendees_to_unlink = self.env['calendar.attendee'].search([ - ('event_id', 'in', self.ids), - ('partner_id', 'in', removed_partner_ids), - ]) + if not self: + attendees_to_unlink = self.env['calendar.attendee'] + else: + attendees_to_unlink = self.env['calendar.attendee'].search([ + ('event_id', 'in', self.ids), + ('partner_id', 'in', removed_partner_ids), + ]) attendee_commands += [[2, attendee.id] for attendee in attendees_to_unlink] # Removes and delete attendee_commands += [ @@ -650,8 +661,8 @@ class Meeting(models.Model): def _setup_alarms(self): """ Schedule cron triggers for future events """ cron = self.env.ref('calendar.ir_cron_scheduler_alarm').sudo() - alarm_manager = self.env['calendar.alarm_manager'] alarm_types = self._get_trigger_alarm_types() + events_to_notify = self.env['calendar.event'] for event in self: for alarm in (alarm for alarm in event.alarm_ids if alarm.alarm_type in alarm_types): @@ -661,8 +672,9 @@ class Meeting(models.Model): cron._trigger(at=at) if any(alarm.alarm_type == 'notification' for alarm in event.alarm_ids): # filter events before notifying attendees through calendar_alarm_manager - need_notifs = event.filtered(lambda ev: ev.alarm_ids and ev.stop >= fields.Datetime.now()) - alarm_manager._notify_next_alarm(need_notifs.partner_ids.ids) + events_to_notify |= event.filtered(lambda ev: ev.alarm_ids and ev.stop >= fields.Datetime.now()) + if events_to_notify: + self.env['calendar.alarm_manager']._notify_next_alarm(events_to_notify.partner_ids.ids) # ------------------------------------------------------------ # RECURRENCY diff --git a/addons/google_calendar/tests/test_sync_odoo2google.py b/addons/google_calendar/tests/test_sync_odoo2google.py index 58cad26ab40..90e979a4601 100644 --- a/addons/google_calendar/tests/test_sync_odoo2google.py +++ b/addons/google_calendar/tests/test_sync_odoo2google.py @@ -8,8 +8,10 @@ from unittest.mock import patch from odoo.addons.google_calendar.utils.google_calendar import GoogleCalendarService from odoo.addons.google_calendar.models.res_users import User from odoo.addons.google_calendar.tests.test_sync_common import TestSyncGoogle, patch_api +from odoo.tests.common import users, warmup +from odoo.tests import tagged - +@tagged('odoo2google') @patch.object(User, '_get_google_calendar_token', lambda user: 'dummy-token') class TestSyncOdoo2Google(TestSyncGoogle): @@ -53,6 +55,73 @@ class TestSyncOdoo2Google(TestSyncGoogle): 'extendedProperties': {'shared': {'%s_odoo_id' % self.env.cr.dbname: event.id}} }) + @patch_api + @users('__system__') + @warmup + def test_event_creation_perf(self): + EVENT_COUNT = 100 + partners = self.env['res.partner'].create([ + {'name': 'Jean-Luc %s' % (i), 'email': 'jean-luc-%s@opoo.com' % (i)} for i in range(EVENT_COUNT)]) + alarm = self.env['calendar.alarm'].create({ + 'name': 'Notif', + 'alarm_type': 'notification', + 'interval': 'minutes', + 'duration': 18, + }) + partner_model = self.env.ref('base.model_res_partner') + partner = self.env['res.partner'].search([], limit=1) + with self.assertQueryCount(__system__=1211): + events = self.env['calendar.event'].create([{ + 'name': "Event %s" % (i), + 'start': datetime(2020, 1, 15, 8, 0), + 'stop': datetime(2020, 1, 15, 18, 0), + 'partner_ids': [(4, partners[i].id), (4, self.env.user.partner_id.id)], + 'alarm_ids': [(4, alarm.id)], + 'privacy': 'private', + 'need_sync': False, + 'res_model_id': partner_model.id, + 'res_id': partner.id, + } for i in range(EVENT_COUNT)]) + + events._sync_odoo2google(self.google_service) + + with self.assertQueryCount(__system__=129): + events.unlink() + + + @patch_api + @users('__system__') + @warmup + def test_recurring_event_creation_perf(self): + partner = self.env['res.partner'].create({'name': 'Jean-Luc', 'email': 'jean-luc@opoo.com'}) + alarm = self.env['calendar.alarm'].create({ + 'name': 'Notif', + 'alarm_type': 'notification', + 'interval': 'minutes', + 'duration': 18, + }) + partner_model = self.env.ref('base.model_res_partner') + partner = self.env['res.partner'].search([], limit=1) + with self.assertQueryCount(__system__=3637): + event = self.env['calendar.event'].create({ + 'name': "Event", + 'start': datetime(2020, 1, 15, 8, 0), + 'stop': datetime(2020, 1, 15, 18, 0), + 'partner_ids': [(4, partner.id)], + 'alarm_ids': [(4, alarm.id)], + 'privacy': 'private', + 'need_sync': False, + 'interval': 1, + 'recurrency': True, + 'rrule_type': 'daily', + 'end_type': 'forever', + 'res_model_id': partner_model.id, + 'res_id': partner.id, + }) + + with self.assertQueryCount(__system__=2191): + event.unlink() + def test_event_without_user(self): event = self.env['calendar.event'].create({ 'name': "Event", diff --git a/addons/hr_holidays/tests/test_company_leave.py b/addons/hr_holidays/tests/test_company_leave.py index da2b444617f..b5979dc19ab 100644 --- a/addons/hr_holidays/tests/test_company_leave.py +++ b/addons/hr_holidays/tests/test_company_leave.py @@ -313,7 +313,7 @@ class TestCompanyLeave(TransactionCase): }) company_leave._compute_date_from_to() - with self.assertQueryCount(__system__=843, admin=865): + with self.assertQueryCount(__system__=845, admin=865): # Original query count: 1987 # Without tracking/activity context keys: 5154 company_leave.action_validate() diff --git a/addons/hr_work_entry_holidays/tests/test_performance.py b/addons/hr_work_entry_holidays/tests/test_performance.py index b633da312d9..b3f72f8ed64 100644 --- a/addons/hr_work_entry_holidays/tests/test_performance.py +++ b/addons/hr_work_entry_holidays/tests/test_performance.py @@ -31,7 +31,7 @@ class TestWorkEntryHolidaysPerformance(TestWorkEntryHolidaysBase): self.richard_emp.generate_work_entries(date(2018, 1, 1), date(2018, 1, 2)) leave = self.create_leave(datetime(2018, 1, 1, 7, 0), datetime(2018, 1, 1, 18, 0)) - with self.assertQueryCount(__system__=94, admin=95): + with self.assertQueryCount(__system__=96, admin=97): leave.action_validate() leave.action_refuse()