From a3eed99161c69a0a81b51398459b2df60d5df55d Mon Sep 17 00:00:00 2001 From: amdi-odoo Date: Wed, 6 Sep 2023 15:27:25 +0200 Subject: [PATCH 1/3] [IMP] google_calendar: add single event synchronization Purpose ======= Add the possibility to only synchronize one specific event of a user calendar instead of syncing the whole calendar. Specification ============= In certain cases, we do not want to synchronize the whole user calendar with all the events and recurrences but only a specific event. For example, in appointment, to retrieve the generated google meet url of a specified event. To perform the single event synchronization a synchronization token is needed, meaning we cannot do a single event synchronization prior to a full calendar synchronization as it is the full sync which generates the temporary sync token. To prevent the need of doing a full sync before a single event sync, the permanent synchronization token is been used instead of the temporary one when performing a single event synchronization. Task-3285601 Part-of: odoo/odoo#133921 --- addons/google_calendar/models/res_users.py | 64 ++++++++++++++----- .../google_calendar/utils/google_calendar.py | 9 ++- 2 files changed, 55 insertions(+), 18 deletions(-) diff --git a/addons/google_calendar/models/res_users.py b/addons/google_calendar/models/res_users.py index c636c3e0bc9..69a4a3d316b 100644 --- a/addons/google_calendar/models/res_users.py +++ b/addons/google_calendar/models/res_users.py @@ -53,24 +53,10 @@ class User(models.Model): def _sync_google_calendar(self, calendar_service: GoogleCalendarService): self.ensure_one() - if self._get_google_sync_status() != "sync_active": + results = self._sync_request(calendar_service) + if not results or not results.get('events'): return False - # don't attempt to sync when another sync is already in progress, as we wouldn't be - # able to commit the transaction anyway (row is locked) - self.env.cr.execute("""SELECT id FROM res_users WHERE id = %s FOR NO KEY UPDATE SKIP LOCKED""", [self.id]) - if not self.env.cr.rowcount: - _logger.info("skipping calendar sync, locked user %s", self.login) - return False - - full_sync = not bool(self.google_calendar_sync_token) - with google_calendar_token(self) as token: - try: - events, next_sync_token, default_reminders = calendar_service.get_events(self.google_calendar_account_id.calendar_sync_token, token=token) - except InvalidSyncToken: - events, next_sync_token, default_reminders = calendar_service.get_events(token=token) - full_sync = True - self.google_calendar_account_id.calendar_sync_token = next_sync_token - + events, default_reminders, full_sync = results.values() # Google -> Odoo send_updates = not full_sync events.clear_type_ambiguity(self.env) @@ -89,6 +75,50 @@ class User(models.Model): return bool(events | synced_events) or bool(recurrences | synced_recurrences) + def _sync_single_event(self, calendar_service: GoogleCalendarService, odoo_event, event_id): + self.ensure_one() + results = self._sync_request(calendar_service, event_id) + if not results or not results.get('events'): + return False + event, default_reminders, full_sync = results.values() + # Google -> Odoo + send_updates = not full_sync + event.clear_type_ambiguity(self.env) + synced_events = self.env['calendar.event']._sync_google2odoo(event, default_reminders=default_reminders) + # Odoo -> Google + odoo_event.with_context(send_updates=send_updates)._sync_odoo2google(calendar_service) + return bool(odoo_event | synced_events) + + def _sync_request(self, calendar_service, event_id=None): + if self._get_google_sync_status() != "sync_active": + return False + # don't attempt to sync when another sync is already in progress, as we wouldn't be + # able to commit the transaction anyway (row is locked) + self.env.cr.execute("""SELECT id FROM res_users WHERE id = %s FOR NO KEY UPDATE SKIP LOCKED""", [self.id]) + if not self.env.cr.rowcount: + _logger.info("skipping calendar sync, locked user %s", self.login) + return False + + full_sync = not bool(self.google_calendar_sync_token) + with google_calendar_token(self) as token: + try: + if not event_id: + events, next_sync_token, default_reminders = calendar_service.get_events(self.google_calendar_account_id.calendar_sync_token, token=token) + else: + # We force the sync_token parameter to avoid doing a full sync. + # Other events are fetched when the calendar view is displayed. + events, next_sync_token, default_reminders = calendar_service.get_events(sync_token=token, token=token, event_id=event_id) + except InvalidSyncToken: + events, next_sync_token, default_reminders = calendar_service.get_events(token=token) + full_sync = True + if next_sync_token: + self.google_calendar_account_id.calendar_sync_token = next_sync_token + return { + 'events': events, + 'default_reminders': default_reminders, + 'full_sync': full_sync, + } + @api.model def _sync_all_google_calendar(self): """ Cron job """ diff --git a/addons/google_calendar/utils/google_calendar.py b/addons/google_calendar/utils/google_calendar.py index a2bf89bd32d..d063de6806a 100644 --- a/addons/google_calendar/utils/google_calendar.py +++ b/addons/google_calendar/utils/google_calendar.py @@ -29,8 +29,10 @@ class GoogleCalendarService(): self.google_service = google_service @requires_auth_token - def get_events(self, sync_token=None, token=None, timeout=TIMEOUT): + def get_events(self, sync_token=None, token=None, event_id=None, timeout=TIMEOUT): url = "/calendar/v3/calendars/primary/events" + if event_id: + url += f"/{event_id}" headers = {'Content-type': 'application/json'} params = {'access_token': token} if sync_token: @@ -51,6 +53,11 @@ class GoogleCalendarService(): raise InvalidSyncToken("Invalid sync token. Full sync required") raise e + if event_id: + next_sync_token = None + default_reminders = () + return GoogleEvent([data]), next_sync_token, default_reminders + events = data.get('items', []) next_page_token = data.get('nextPageToken') while next_page_token: From ec073feeb4a06a0efc2aa6b18fad0c8359f08a3d Mon Sep 17 00:00:00 2001 From: amdi-odoo Date: Fri, 8 Sep 2023 14:48:20 +0200 Subject: [PATCH 2/3] [IMP] google_calendar: add google meet videocall url Add a 'google_meet' value for the event videocall location to make the difference between a custom videocall link and a google meet one. Task-3285601 Part-of: odoo/odoo#133921 --- addons/google_calendar/models/calendar.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/addons/google_calendar/models/calendar.py b/addons/google_calendar/models/calendar.py index 9cbe2f25921..ed591740852 100644 --- a/addons/google_calendar/models/calendar.py +++ b/addons/google_calendar/models/calendar.py @@ -14,10 +14,13 @@ class Meeting(models.Model): _name = 'calendar.event' _inherit = ['calendar.event', 'google.calendar.sync'] + MEET_ROUTE = 'meet.google.com' + google_id = fields.Char( 'Google Calendar Event Id', compute='_compute_google_id', store=True, readonly=False) guests_readonly = fields.Boolean( 'Guests Event Modification Permission', default=False) + videocall_source = fields.Selection(selection_add=[('google_meet', 'Google Meet')], ondelete={'google_meet': 'set discuss'}) @api.depends('recurrence_id.google_id') def _compute_google_id(self): @@ -32,6 +35,12 @@ class Meeting(models.Model): elif not event.google_id: event.google_id = False + @api.depends('videocall_location') + def _compute_videocall_source(self): + events_with_google_url = self.filtered(lambda event: self.MEET_ROUTE in (event.videocall_location or '')) + events_with_google_url.videocall_source = 'google_meet' + super(Meeting, self - events_with_google_url)._compute_videocall_source() + @api.model def _get_google_synced_fields(self): return {'name', 'description', 'allday', 'start', 'date_end', 'stop', From c2f337779b0644cc45264db7889d74d8a64e23e0 Mon Sep 17 00:00:00 2001 From: amdi-odoo Date: Wed, 13 Sep 2023 13:02:53 +0200 Subject: [PATCH 3/3] [IMP] google_calendar: add google sync helper Add a helper method to know when the user calendar is fully synced to Google Calendar, meaning when API calls can correctly be performed. The user calendar is considered fully synced if the Google API is correctly configured in the settings (Client ID / Secret) and the user has synced its calendar with its Google Account. Task-3285601 Part-of: odoo/odoo#133921 --- addons/google_calendar/models/res_users.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/addons/google_calendar/models/res_users.py b/addons/google_calendar/models/res_users.py index 69a4a3d316b..429bbcbb1ef 100644 --- a/addons/google_calendar/models/res_users.py +++ b/addons/google_calendar/models/res_users.py @@ -133,6 +133,12 @@ class User(models.Model): _logger.exception("[%s] Calendar Synchro - Exception : %s!", user, exception_to_unicode(e)) self.env.cr.rollback() + def is_google_calendar_synced(self): + """ True if Google Calendar settings are filled (Client ID / Secret) and user calendar is synced + meaning we can make API calls, false otherwise.""" + self.ensure_one() + return self.google_calendar_token and self._get_google_sync_status() == 'sync_active' + def stop_google_synchronization(self): self.ensure_one() self.google_synchronization_stopped = True