[FIX] google_calendar: do not spam while updating recurrent events
Before this task, lots of mails were sent after updating or deleting recurrent event in 'All events' or 'This and future events' update type. This was happening because updating these recurrent events was triggering patch calls event by event, when they should be handled in batch. After this commit, updating or deleting recurrent events should trigger at most two mails for Google users. closes odoo/odoo#137607 Task-id: 3163695 X-original-commit: ad2106babda61446bce13283d570dc723418b630 Signed-off-by: Arnaud Joset (arj) <arj@odoo.com> Signed-off-by: Gabriel de Paula Felix (gdpf) <gdpf@odoo.com>
This commit is contained in:
@@ -9,6 +9,7 @@ from uuid import uuid4
|
||||
from odoo import api, fields, models, tools, _
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
from odoo.addons.google_calendar.utils.google_calendar import GoogleCalendarService
|
||||
|
||||
class Meeting(models.Model):
|
||||
_name = 'calendar.event'
|
||||
@@ -60,6 +61,31 @@ class Meeting(models.Model):
|
||||
for vals in vals_list
|
||||
])
|
||||
|
||||
@api.model
|
||||
def _check_values_to_sync(self, values):
|
||||
""" Return True if values being updated intersects with Google synced values and False otherwise. """
|
||||
synced_fields = self._get_google_synced_fields()
|
||||
values_to_sync = any(key in synced_fields for key in values)
|
||||
return values_to_sync
|
||||
|
||||
@api.model
|
||||
def _get_update_future_events_values(self):
|
||||
""" Add parameters for updating events within the _update_future_events function scope. """
|
||||
update_future_events_values = super()._get_update_future_events_values()
|
||||
return {**update_future_events_values, 'need_sync': False}
|
||||
|
||||
@api.model
|
||||
def _get_remove_sync_id_values(self):
|
||||
""" Add parameters for removing event synchronization while updating the events in super class. """
|
||||
remove_sync_id_values = super()._get_remove_sync_id_values()
|
||||
return {**remove_sync_id_values, 'google_id': False}
|
||||
|
||||
@api.model
|
||||
def _get_archive_values(self):
|
||||
""" Return the parameters for archiving events. Do not synchronize events after archiving. """
|
||||
archive_values = super()._get_archive_values()
|
||||
return {**archive_values, 'need_sync': False}
|
||||
|
||||
def write(self, values):
|
||||
recurrence_update_setting = values.get('recurrence_update')
|
||||
if recurrence_update_setting in ('all_events', 'future_events') and len(self) == 1:
|
||||
@@ -233,6 +259,18 @@ class Meeting(models.Model):
|
||||
commands += [(0, 0, {'duration': duration, 'interval': interval, 'name': name, 'alarm_type': alarm_type})]
|
||||
return commands
|
||||
|
||||
def action_mass_archive(self, recurrence_update_setting):
|
||||
""" Delete recurrence in Odoo if in 'all_events' or in 'future_events' edge case, triggering one mail. """
|
||||
self.ensure_one()
|
||||
google_service = GoogleCalendarService(self.env['google.service'])
|
||||
archive_future_events = recurrence_update_setting == 'future_events' and self == self.recurrence_id.base_event_id
|
||||
if recurrence_update_setting == 'all_events' or archive_future_events:
|
||||
self.recurrence_id.with_context(is_recurrence=True)._google_delete(google_service, self.recurrence_id.google_id)
|
||||
# Increase performance handling 'future_events' edge case as it was an 'all_events' update.
|
||||
if archive_future_events:
|
||||
recurrence_update_setting = 'all_events'
|
||||
super(Meeting, self).action_mass_archive(recurrence_update_setting)
|
||||
|
||||
def _google_values(self):
|
||||
if self.allday:
|
||||
start = {'date': self.start_date.isoformat()}
|
||||
|
||||
@@ -64,8 +64,8 @@ class RecurrenceRule(models.Model):
|
||||
|
||||
def _write_events(self, values, dtstart=None):
|
||||
values.pop('google_id', False)
|
||||
# If only some events are updated, sync those events.
|
||||
values['need_sync'] = bool(dtstart)
|
||||
# Events will be updated by patch requests, do not sync events for avoiding spam.
|
||||
values['need_sync'] = False
|
||||
return super()._write_events(values, dtstart=dtstart)
|
||||
|
||||
def _cancel(self):
|
||||
|
||||
@@ -242,6 +242,8 @@ class GoogleSync(models.AbstractModel):
|
||||
def _google_delete(self, google_service: GoogleCalendarService, google_id, timeout=TIMEOUT):
|
||||
with google_calendar_token(self.env.user.sudo()) as token:
|
||||
if token:
|
||||
is_recurrence = self._context.get('is_recurrence', False)
|
||||
google_service.google_service = google_service.google_service.with_context(is_recurrence=is_recurrence)
|
||||
google_service.delete(google_id, token=token, timeout=timeout)
|
||||
# When the record has been deleted on our side, we need to delete it on google but we don't want
|
||||
# to raise an error because the record don't exists anymore.
|
||||
@@ -256,7 +258,8 @@ class GoogleSync(models.AbstractModel):
|
||||
except HTTPError as e:
|
||||
if e.response.status_code in (400, 403):
|
||||
self._google_error_handling(e)
|
||||
self.exists().with_context(dont_notify=True).need_sync = False
|
||||
if values:
|
||||
self.exists().with_context(dont_notify=True).need_sync = False
|
||||
|
||||
@after_commit
|
||||
def _google_insert(self, google_service: GoogleCalendarService, values, timeout=TIMEOUT):
|
||||
|
||||
@@ -106,7 +106,7 @@ class TestSyncOdoo2Google(TestSyncGoogle):
|
||||
})
|
||||
partner_model = self.env.ref('base.model_res_partner')
|
||||
partner = self.env['res.partner'].search([], limit=1)
|
||||
with self.assertQueryCount(__system__=84):
|
||||
with self.assertQueryCount(__system__=86):
|
||||
event = self.env['calendar.event'].create({
|
||||
'name': "Event",
|
||||
'start': datetime(2020, 1, 15, 8, 0),
|
||||
@@ -326,8 +326,8 @@ class TestSyncOdoo2Google(TestSyncGoogle):
|
||||
'name': 'New name',
|
||||
'recurrence_update': 'future_events',
|
||||
})
|
||||
self.assertGoogleEventPatched(event.google_id, {
|
||||
'id': event.google_id,
|
||||
self.assertGoogleEventInserted({
|
||||
'id': False,
|
||||
'start': {'date': str(event.start_date)},
|
||||
'end': {'date': str(event.stop_date + relativedelta(days=1))},
|
||||
'summary': 'New name',
|
||||
@@ -336,9 +336,10 @@ class TestSyncOdoo2Google(TestSyncGoogle):
|
||||
'guestsCanModify': True,
|
||||
'organizer': {'email': 'odoobot@example.com', 'self': True},
|
||||
'attendees': [{'email': 'odoobot@example.com', 'responseStatus': 'accepted'}],
|
||||
'extendedProperties': {'shared': {'%s_odoo_id' % self.env.cr.dbname: event.id}},
|
||||
'extendedProperties': {'shared': {'%s_odoo_id' % self.env.cr.dbname: event.recurrence_id.id}},
|
||||
'reminders': {'overrides': [], 'useDefault': False},
|
||||
'visibility': 'public',
|
||||
'recurrence': ['RRULE:FREQ=WEEKLY;WKST=SU;COUNT=1;BYDAY=WE']
|
||||
}, timeout=3)
|
||||
|
||||
@patch_api
|
||||
@@ -412,8 +413,9 @@ class TestSyncOdoo2Google(TestSyncGoogle):
|
||||
'name': 'New name',
|
||||
'recurrence_update': 'all_events',
|
||||
})
|
||||
self.assertGoogleEventPatched(recurrence.google_id, {
|
||||
'id': recurrence.google_id,
|
||||
new_recurrence = self.env['calendar.recurrence'].search([('id', '>', recurrence.id)])
|
||||
self.assertGoogleEventInserted({
|
||||
'id': False,
|
||||
'start': {'date': str(event.start_date)},
|
||||
'end': {'date': str(event.stop_date + relativedelta(days=1))},
|
||||
'summary': 'New name',
|
||||
@@ -422,8 +424,8 @@ class TestSyncOdoo2Google(TestSyncGoogle):
|
||||
'guestsCanModify': True,
|
||||
'organizer': {'email': 'odoobot@example.com', 'self': True},
|
||||
'attendees': [{'email': 'odoobot@example.com', 'responseStatus': 'accepted'}],
|
||||
'recurrence': ['RRULE:FREQ=WEEKLY;COUNT=2;BYDAY=WE'],
|
||||
'extendedProperties': {'shared': {'%s_odoo_id' % self.env.cr.dbname: recurrence.id}},
|
||||
'recurrence': ['RRULE:FREQ=WEEKLY;WKST=SU;COUNT=2;BYDAY=WE'],
|
||||
'extendedProperties': {'shared': {'%s_odoo_id' % self.env.cr.dbname: new_recurrence.id}},
|
||||
'reminders': {'overrides': [], 'useDefault': False},
|
||||
'visibility': 'public',
|
||||
}, timeout=3)
|
||||
@@ -578,8 +580,9 @@ class TestSyncOdoo2Google(TestSyncGoogle):
|
||||
'name': 'New name',
|
||||
'recurrence_update': 'all_events',
|
||||
})
|
||||
self.assertGoogleEventPatched(recurrence.google_id, {
|
||||
'id': recurrence.google_id,
|
||||
new_recurrence = self.env['calendar.recurrence'].search([('id', '>', recurrence.id)])
|
||||
self.assertGoogleEventInserted({
|
||||
'id': False,
|
||||
'start': {'dateTime': "2020-01-15T08:00:00+00:00", 'timeZone': 'Europe/Brussels'},
|
||||
'end': {'dateTime': "2020-01-15T09:00:00+00:00", 'timeZone': 'Europe/Brussels'},
|
||||
'summary': 'New name',
|
||||
@@ -588,8 +591,8 @@ class TestSyncOdoo2Google(TestSyncGoogle):
|
||||
'guestsCanModify': True,
|
||||
'organizer': {'email': 'odoobot@example.com', 'self': True},
|
||||
'attendees': [{'email': 'odoobot@example.com', 'responseStatus': 'accepted'}],
|
||||
'recurrence': ['RRULE:FREQ=WEEKLY;COUNT=2;BYDAY=WE'],
|
||||
'extendedProperties': {'shared': {'%s_odoo_id' % self.env.cr.dbname: recurrence.id}},
|
||||
'recurrence': ['RRULE:FREQ=WEEKLY;WKST=SU;COUNT=2;BYDAY=WE'],
|
||||
'extendedProperties': {'shared': {'%s_odoo_id' % self.env.cr.dbname: new_recurrence.id}},
|
||||
'reminders': {'overrides': [], 'useDefault': False},
|
||||
'visibility': 'public',
|
||||
}, timeout=3)
|
||||
|
||||
@@ -92,6 +92,12 @@ class GoogleCalendarService():
|
||||
url = "/calendar/v3/calendars/primary/events/%s?sendUpdates=all" % event_id
|
||||
headers = {'Content-type': 'application/json'}
|
||||
params = {'access_token': token}
|
||||
# Delete all events from recurrence in a single request to Google and triggering a single mail.
|
||||
# The 'singleEvents' parameter is a trick that tells Google API to delete all recurrent events individually,
|
||||
# making the deletion be handled entirely on their side, and then we archive the events in Odoo.
|
||||
is_recurrence = self.google_service._context.get('is_recurrence', True)
|
||||
if is_recurrence:
|
||||
params['singleEvents'] = 'true'
|
||||
try:
|
||||
self.google_service._do_request(url, params, headers=headers, method='DELETE', timeout=timeout)
|
||||
except requests.HTTPError as e:
|
||||
|
||||
Reference in New Issue
Block a user