[FIX] calendar: improve calendar recurrency creation performances
Turn off calendar.attendees notification during synced_events creation to improve performances. The calendar notification are only computed once after all the synced events have been created. Add a flag in google_calendar.js to avoid retriggering synchronization while navigating the calendar view if the past synchronization is still pending. opw: 2450259 + fixes/adaptations to 14.3 branch FW of https://github.com/odoo/odoo/pull/69619 closes odoo/odoo#70035 X-original-commit: 28ccb2b88a7793058c81aabffb43071165886f40 Signed-off-by: Yannick Tivisse (yti) <yti@odoo.com> Signed-off-by: Arnaud Joset <arj-odoo@users.noreply.github.com>
This commit is contained in:
@@ -389,11 +389,12 @@ class Meeting(models.Model):
|
||||
events = super().create(other_vals)
|
||||
|
||||
for vals in recurring_vals:
|
||||
|
||||
recurrence_values = {field: vals.pop(field) for field in recurrence_fields if field in vals}
|
||||
vals['follow_recurrence'] = True
|
||||
event = super().create(vals)
|
||||
events |= event
|
||||
recurring_events = super().create(recurring_vals)
|
||||
events += recurring_events
|
||||
|
||||
for event, vals in zip(recurring_events, recurring_vals):
|
||||
recurrence_values = {field: vals.pop(field) for field in recurrence_fields if field in vals}
|
||||
if vals.get('recurrency'):
|
||||
detached_events = event._apply_recurrence_values(recurrence_values)
|
||||
detached_events.active = False
|
||||
@@ -402,8 +403,8 @@ class Meeting(models.Model):
|
||||
self.env.ref('calendar.calendar_template_meeting_invitation', raise_if_not_found=False)
|
||||
)
|
||||
events._sync_activities(fields={f for vals in vals_list for f in vals.keys()})
|
||||
|
||||
events._setup_alarms()
|
||||
if not self.env.context.get('dont_notify'):
|
||||
events._setup_alarms()
|
||||
|
||||
return events
|
||||
|
||||
@@ -488,8 +489,8 @@ class Meeting(models.Model):
|
||||
|
||||
(detached_events & self).active = False
|
||||
(detached_events - self).with_context(archive_on_error=True).unlink()
|
||||
|
||||
self._setup_alarms()
|
||||
if not self.env.context.get('dont_notify'):
|
||||
self._setup_alarms()
|
||||
|
||||
current_attendees = self.filtered('active').attendee_ids
|
||||
if 'partner_ids' in values:
|
||||
@@ -656,7 +657,9 @@ class Meeting(models.Model):
|
||||
# Don't trigger for past alarms, they would be skipped by design
|
||||
cron._trigger(at=at)
|
||||
if any(alarm.alarm_type == 'notification' for alarm in event.alarm_ids):
|
||||
alarm_manager._notify_next_alarm(event.partner_ids.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)
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# RECURRENCY
|
||||
|
||||
@@ -292,7 +292,7 @@ class RecurrenceRule(models.Model):
|
||||
:param dstart: if provided, only write events starting from this point in time
|
||||
"""
|
||||
events = self._get_events_from(dtstart) if dtstart else self.calendar_event_ids
|
||||
return events.with_context(no_mail_to_attendees=True).write(dict(values, recurrence_update='self_only'))
|
||||
return events.with_context(no_mail_to_attendees=True, dont_notify=True).write(dict(values, recurrence_update='self_only'))
|
||||
|
||||
def _rrule_serialize(self):
|
||||
"""
|
||||
|
||||
@@ -41,7 +41,8 @@ class Meeting(models.Model):
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
return super().create([
|
||||
notify_context = self.env.context.get('dont_notify', False)
|
||||
return super(Meeting, self.with_context(dont_notify=notify_context)).create([
|
||||
dict(vals, need_sync=False) if vals.get('recurrence_id') or vals.get('recurrency') else vals
|
||||
for vals in vals_list
|
||||
])
|
||||
@@ -50,7 +51,8 @@ class Meeting(models.Model):
|
||||
recurrence_update_setting = values.get('recurrence_update')
|
||||
if recurrence_update_setting in ('all_events', 'future_events') and len(self) == 1:
|
||||
values = dict(values, need_sync=False)
|
||||
res = super().write(values)
|
||||
notify_context = self.env.context.get('dont_notify', False)
|
||||
res = super(Meeting, self.with_context(dont_notify=notify_context)).write(values)
|
||||
if recurrence_update_setting in ('all_events',) and len(self) == 1 and values.keys() & self._get_google_synced_fields():
|
||||
self.recurrence_id.need_sync = True
|
||||
return res
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import re
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo import api, models
|
||||
|
||||
from odoo.addons.google_calendar.utils.google_calendar import GoogleCalendarService
|
||||
|
||||
@@ -137,12 +136,16 @@ class RecurrenceRule(models.Model):
|
||||
vals['calendar_event_ids'] = [(4, base_event.id)]
|
||||
# event_tz is written on event in Google but on recurrence in Odoo
|
||||
vals['event_tz'] = gevent.start.get('timeZone')
|
||||
recurrence = super()._create_from_google(gevents, vals_list)
|
||||
recurrence._apply_recurrence()
|
||||
recurrence = super(RecurrenceRule, self.with_context(dont_notify=True))._create_from_google(gevents, vals_list)
|
||||
recurrence.with_context(dont_notify=True)._apply_recurrence()
|
||||
return recurrence
|
||||
|
||||
def _get_sync_domain(self):
|
||||
return [('calendar_event_ids.user_id', '=', self.env.user.id)]
|
||||
# Empty rrule may exists in historical data. It is not a desired behavior but it could have been created with
|
||||
# older versions of the module. When synced, these recurrency may come back from Google after database cleaning
|
||||
# and trigger errors as the records are not properly populated.
|
||||
# We also prevent sync of other user recurrent events.
|
||||
return [('calendar_event_ids.user_id', '=', self.env.user.id), ('rrule', '!=', False)]
|
||||
|
||||
@api.model
|
||||
def _odoo_values(self, google_recurrence, default_reminders=()):
|
||||
|
||||
@@ -148,7 +148,7 @@ class GoogleSync(models.AbstractModel):
|
||||
dict(self._odoo_values(e, default_reminders), need_sync=False)
|
||||
for e in new
|
||||
]
|
||||
new_odoo = self._create_from_google(new, odoo_values)
|
||||
new_odoo = self.with_context(dont_notify=True)._create_from_google(new, odoo_values)
|
||||
cancelled = existing.cancelled()
|
||||
cancelled_odoo = self.browse(cancelled.odoo_ids(self.env))
|
||||
cancelled_odoo._cancel()
|
||||
@@ -161,7 +161,7 @@ class GoogleSync(models.AbstractModel):
|
||||
# Migration from 13.4 does not fill write_date. Therefore, we force the update from Google.
|
||||
if not odoo_record.write_date or updated >= pytz.utc.localize(odoo_record.write_date):
|
||||
vals = dict(self._odoo_values(gevent, default_reminders), need_sync=False)
|
||||
odoo_record._write_from_google(gevent, vals)
|
||||
odoo_record.with_context(dont_notify=True)._write_from_google(gevent, vals)
|
||||
synced_records |= odoo_record
|
||||
|
||||
return synced_records
|
||||
|
||||
@@ -21,6 +21,7 @@ const GoogleCalendarModel = CalendarModel.include({
|
||||
init: function () {
|
||||
this._super.apply(this, arguments);
|
||||
this.google_is_sync = true;
|
||||
this.google_pending_sync = false;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -39,6 +40,10 @@ const GoogleCalendarModel = CalendarModel.include({
|
||||
*/
|
||||
async _loadCalendar() {
|
||||
const _super = this._super.bind(this);
|
||||
// When the calendar synchronization takes some time, prevents retriggering the sync while navigating the calendar.
|
||||
if (this.google_pending_sync) {
|
||||
return _super(...arguments);
|
||||
}
|
||||
try {
|
||||
await Promise.race([
|
||||
new Promise(resolve => setTimeout(resolve, 1000)),
|
||||
@@ -49,6 +54,7 @@ const GoogleCalendarModel = CalendarModel.include({
|
||||
error.event.preventDefault();
|
||||
}
|
||||
console.error("Could not synchronize Google events now.", error);
|
||||
this.google_pending_sync = false;
|
||||
}
|
||||
return _super(...arguments);
|
||||
},
|
||||
@@ -56,6 +62,7 @@ const GoogleCalendarModel = CalendarModel.include({
|
||||
_syncGoogleCalendar(shadow = false) {
|
||||
var self = this;
|
||||
var context = this.getSession().user_context;
|
||||
this.google_pending_sync = true;
|
||||
return this._rpc({
|
||||
route: '/google_calendar/sync_data',
|
||||
params: {
|
||||
@@ -69,6 +76,7 @@ const GoogleCalendarModel = CalendarModel.include({
|
||||
} else if (result.status === "no_new_event_from_google" || result.status === "need_refresh") {
|
||||
self.google_is_sync = true;
|
||||
}
|
||||
self.google_pending_sync = false;
|
||||
return result
|
||||
});
|
||||
},
|
||||
|
||||
@@ -681,3 +681,68 @@ class TestSyncGoogle2Odoo(TestSyncGoogle):
|
||||
event = self.env['calendar.event'].search([('google_id', '=', values.get('id'))])
|
||||
self.assertFalse(event.exists(), "The old event should not exits anymore")
|
||||
self.assertGoogleAPINotCalled()
|
||||
|
||||
@patch_api
|
||||
def test_new_google_notifications(self):
|
||||
""" Event from Google should not create notifications and trigger. It ruins the perfs on large databases """
|
||||
cron_id = self.env.ref('calendar.ir_cron_scheduler_alarm').id
|
||||
triggers_before = self.env['ir.cron.trigger'].search([('cron_id', '=', cron_id)])
|
||||
google_id = 'oj44nep1ldf8a3ll02uip0c9aa'
|
||||
start = datetime.today() + relativedelta(months=1, day=1, hours=1)
|
||||
end = datetime.today() + relativedelta(months=1, day=1, hours=2)
|
||||
updated = datetime.today() + relativedelta(minutes=1)
|
||||
values = {
|
||||
'id': google_id,
|
||||
'description': 'Small mini desc',
|
||||
'organizer': {'email': 'odoocalendarref@gmail.com', 'self': True},
|
||||
'summary': 'Pricing new update',
|
||||
'visibility': 'public',
|
||||
'attendees': [{
|
||||
'displayName': 'Mitchell Admin',
|
||||
'email': 'admin@yourcompany.example.com',
|
||||
'responseStatus': 'needsAction'
|
||||
}, ],
|
||||
'reminders': {'overrides': [{"method": "email", "minutes": 10}], 'useDefault': False},
|
||||
'start': {
|
||||
'dateTime': pytz.utc.localize(start).isoformat(),
|
||||
'timeZone': 'Europe/Brussels'
|
||||
},
|
||||
'end': {
|
||||
'dateTime': pytz.utc.localize(end).isoformat(),
|
||||
'timeZone': 'Europe/Brussels'
|
||||
},
|
||||
}
|
||||
self.env['calendar.event']._sync_google2odoo(GoogleEvent([values]))
|
||||
triggers_after = self.env['ir.cron.trigger'].search([('cron_id', '=', cron_id)])
|
||||
new_triggers = triggers_after - triggers_before
|
||||
self.assertFalse(new_triggers, "The event should not be created with triggers.")
|
||||
|
||||
# Event was created from Google and now it will be Updated from Google.
|
||||
# No further notifications should be created.
|
||||
values = {
|
||||
'id': google_id,
|
||||
'updated': pytz.utc.localize(updated).isoformat(),
|
||||
'description': 'New Super description',
|
||||
'organizer': {'email': 'odoocalendarref@gmail.com', 'self': True},
|
||||
'summary': 'Pricing was not good, now it is correct',
|
||||
'visibility': 'public',
|
||||
'attendees': [{
|
||||
'displayName': 'Mitchell Admin',
|
||||
'email': 'admin@yourcompany.example.com',
|
||||
'responseStatus': 'needsAction'
|
||||
}, ],
|
||||
'reminders': {'overrides': [{"method": "email", "minutes": 10}], 'useDefault': False},
|
||||
'start': {
|
||||
'dateTime': pytz.utc.localize(start).isoformat(),
|
||||
'timeZone': 'Europe/Brussels'
|
||||
},
|
||||
'end': {
|
||||
'dateTime': pytz.utc.localize(end).isoformat(),
|
||||
'timeZone': 'Europe/Brussels'
|
||||
},
|
||||
}
|
||||
self.env['calendar.event']._sync_google2odoo(GoogleEvent([values]))
|
||||
triggers_after = self.env['ir.cron.trigger'].search([('cron_id', '=', cron_id)])
|
||||
new_triggers = triggers_after - triggers_before
|
||||
self.assertFalse(new_triggers, "The event should not be created with triggers.")
|
||||
self.assertGoogleAPINotCalled()
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import datetime, date
|
||||
from datetime import datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from unittest.mock import MagicMock, patch
|
||||
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.models.google_sync import GoogleSync
|
||||
from odoo.modules.registry import Registry
|
||||
from odoo.addons.google_account.models.google_service import TIMEOUT
|
||||
from odoo.addons.google_calendar.tests.test_sync_common import TestSyncGoogle, patch_api
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user