From 6acdcb6fd88bbcc650bf025bd50e93debf8f724d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20=28avd=29?= Date: Mon, 26 Oct 2020 17:41:38 +0100 Subject: [PATCH] [IMP] google_calendar: add stop synchronization button Add the possibility to stop/restart the synchronization with Google Calendar. Change the calendar event popover of the calendar view to display an 'Archive' button if the event was synced at least one. Add some tests for the new stop/restart features. --- addons/google_calendar/__manifest__.py | 1 + addons/google_calendar/controllers/main.py | 9 +- addons/google_calendar/models/calendar.py | 6 + .../models/calendar_recurrence_rule.py | 6 + addons/google_calendar/models/google_sync.py | 12 +- addons/google_calendar/models/res_users.py | 17 ++- .../static/src/js/google_calendar.js | 137 ++++++++++++++++-- .../static/src/js/google_calendar_popover.js | 26 ++++ .../src/xml/google_calendar_popover.xml | 8 + .../tests/test_sync_odoo2google.py | 50 +++++++ .../views/google_calendar_templates.xml | 1 + .../views/google_calendar_views.xml | 13 ++ 12 files changed, 267 insertions(+), 19 deletions(-) create mode 100644 addons/google_calendar/static/src/js/google_calendar_popover.js create mode 100644 addons/google_calendar/static/src/xml/google_calendar_popover.xml create mode 100644 addons/google_calendar/views/google_calendar_views.xml diff --git a/addons/google_calendar/__manifest__.py b/addons/google_calendar/__manifest__.py index a743f7528a2..8e90d83ac8c 100644 --- a/addons/google_calendar/__manifest__.py +++ b/addons/google_calendar/__manifest__.py @@ -14,6 +14,7 @@ 'wizard/reset_account_views.xml', 'views/res_config_settings_views.xml', 'views/res_users_views.xml', + 'views/google_calendar_views.xml', 'views/google_calendar_templates.xml', ], 'demo': [], diff --git a/addons/google_calendar/controllers/main.py b/addons/google_calendar/controllers/main.py index 99ed0b0b2a1..bd76d4dd9c1 100644 --- a/addons/google_calendar/controllers/main.py +++ b/addons/google_calendar/controllers/main.py @@ -13,7 +13,7 @@ class GoogleCalendarController(http.Controller): """ This route/function is called when we want to synchronize Odoo calendar with Google Calendar. Function return a dictionary with the status : need_config_from_admin, need_auth, - need_refresh, success if not calendar_event + need_refresh, sync_stopped, success if not calendar_event The dictionary may contains an url, to allow Odoo Client to redirect user on this URL for authorization for example """ @@ -42,6 +42,13 @@ class GoogleCalendarController(http.Controller): } # If App authorized, and user access accepted, We launch the synchronization need_refresh = request.env.user.sudo()._sync_google_calendar(GoogleCal) + + # If synchronization has been stopped + if not need_refresh and request.env.user.google_synchronization_stopped: + return { + "status": "sync_stopped", + "url": '' + } return { "status": "need_refresh" if need_refresh else "no_new_event_from_google", "url": '' diff --git a/addons/google_calendar/models/calendar.py b/addons/google_calendar/models/calendar.py index 765473509ba..b2aeae22984 100644 --- a/addons/google_calendar/models/calendar.py +++ b/addons/google_calendar/models/calendar.py @@ -33,6 +33,12 @@ class Meeting(models.Model): return {'name', 'description', 'allday', 'start', 'date_end', 'stop', 'attendee_ids', 'alarm_ids', 'location', 'privacy', 'active'} + @api.model + def _restart_google_sync(self): + self.env['calendar.event'].search(self._get_sync_domain()).write({ + 'need_sync': True, + }) + @api.model_create_multi def create(self, vals_list): return super().create([ diff --git a/addons/google_calendar/models/calendar_recurrence_rule.py b/addons/google_calendar/models/calendar_recurrence_rule.py index b3cd06db329..1bc94084faa 100644 --- a/addons/google_calendar/models/calendar_recurrence_rule.py +++ b/addons/google_calendar/models/calendar_recurrence_rule.py @@ -79,6 +79,12 @@ class RecurrenceRule(models.Model): def _get_google_synced_fields(self): return {'rrule'} + @api.model + def _restart_google_sync(self): + self.env['calendar.recurrence'].search(self._get_sync_domain()).write({ + 'need_sync': True, + }) + @api.model def _sync_google2odoo(self, *args, **kwargs): synced_recurrences = super()._sync_google2odoo(*args, **kwargs) diff --git a/addons/google_calendar/models/google_sync.py b/addons/google_calendar/models/google_sync.py index a16cce527f4..06c09bc48b5 100644 --- a/addons/google_calendar/models/google_sync.py +++ b/addons/google_calendar/models/google_sync.py @@ -72,7 +72,7 @@ class GoogleSync(models.AbstractModel): if 'google_id' in vals: self._from_google_ids.clear_cache(self) synced_fields = self._get_google_synced_fields() - if 'need_sync' not in vals and vals.keys() & synced_fields: + if 'need_sync' not in vals and vals.keys() & synced_fields and not self.env.user.google_synchronization_stopped: vals['need_sync'] = True result = super().write(vals) @@ -86,6 +86,9 @@ class GoogleSync(models.AbstractModel): def create(self, vals_list): if any(vals.get('google_id') for vals in vals_list): self._from_google_ids.clear_cache(self) + if self.env.user.google_synchronization_stopped: + for vals in vals_list: + vals.update({'need_sync': False}) records = super().create(vals_list) google_service = GoogleCalendarService(self.env['google.service']) @@ -244,3 +247,10 @@ class GoogleSync(models.AbstractModel): marks the record to be re-synchronized. """ raise NotImplementedError() + + @api.model + def _restart_google_sync(self): + """ Turns on the google synchronization for all the events of + a given user. + """ + raise NotImplementedError() diff --git a/addons/google_calendar/models/res_users.py b/addons/google_calendar/models/res_users.py index 416cc9f8667..1530c19f20e 100644 --- a/addons/google_calendar/models/res_users.py +++ b/addons/google_calendar/models/res_users.py @@ -23,7 +23,8 @@ class User(models.Model): google_calendar_token_validity = fields.Datetime('Token Validity', copy=False) google_calendar_sync_token = fields.Char('Next Sync Token', copy=False) google_calendar_cal_id = fields.Char('Calendar ID', copy=False, help='Last Calendar ID who has been synchronized. If it is changed, we remove all links between GoogleID and Odoo Google Internal ID') - + google_synchronization_stopped = fields.Boolean('Google Synchronization stopped', copy=False) + def _set_auth_tokens(self, access_token, refresh_token, ttl): self.write({ 'google_calendar_rtoken': refresh_token, @@ -79,6 +80,8 @@ class User(models.Model): def _sync_google_calendar(self, calendar_service: GoogleCalendarService): self.ensure_one() + if self.google_synchronization_stopped: + return False full_sync = not bool(self.google_calendar_sync_token) with google_calendar_token(self) as token: try: @@ -106,7 +109,7 @@ class User(models.Model): @api.model def _sync_all_google_calendar(self): """ Cron job """ - users = self.env['res.users'].search([('google_calendar_rtoken', '!=', False)]) + users = self.env['res.users'].search([('google_calendar_rtoken', '!=', False), ('google_synchronization_stopped', '=', False)]) google = GoogleCalendarService(self.env['google.service']) for user in users: _logger.info("Calendar Synchro - Starting synchronization for %s", user) @@ -114,3 +117,13 @@ class User(models.Model): user.with_user(user).sudo()._sync_google_calendar(google) except Exception as e: _logger.exception("[%s] Calendar Synchro - Exception : %s !", user, exception_to_unicode(e)) + + def stop_google_synchronization(self): + self.ensure_one() + self.sudo().google_synchronization_stopped = True + + def restart_google_synchronization(self): + self.ensure_one() + self.sudo().google_synchronization_stopped = False + self.env['calendar.recurrence']._restart_google_sync() + self.env['calendar.event']._restart_google_sync() diff --git a/addons/google_calendar/static/src/js/google_calendar.js b/addons/google_calendar/static/src/js/google_calendar.js index cfa2437bb51..00f75118822 100644 --- a/addons/google_calendar/static/src/js/google_calendar.js +++ b/addons/google_calendar/static/src/js/google_calendar.js @@ -9,6 +9,7 @@ const CalendarRenderer = require('calendar.CalendarRenderer'); const CalendarController = require('calendar.CalendarController'); const CalendarModel = require('calendar.CalendarModel'); const viewRegistry = require('web.view_registry'); +const session = require('web.session'); var _t = core._t; @@ -63,7 +64,7 @@ const GoogleCalendarModel = CalendarModel.include({ local_context: context, // LUL TODO remove this local_context } }, {shadow}).then(function (result) { - if (result.status === "need_config_from_admin" || result.status === "need_auth") { + if (["need_config_from_admin", "need_auth", "sync_stopped"].includes(result.status)) { self.google_is_sync = false; } else if (result.status === "no_new_event_from_google" || result.status === "need_refresh") { self.google_is_sync = true; @@ -71,11 +72,22 @@ const GoogleCalendarModel = CalendarModel.include({ return result }); }, + + archiveRecords: function (ids, model) { + return this._rpc({ + model: model, + method: 'action_archive', + args: [ids], + context: session.user_context, + }); + }, }) const GoogleCalendarController = CalendarController.include({ custom_events: _.extend({}, CalendarController.prototype.custom_events, { syncGoogleCalendar: '_onGoogleSyncCalendar', + stopGoogleSynchronization: '_onStopGoogleSynchronization', + archiveRecord: '_onArchiveRecord', }), @@ -94,7 +106,7 @@ const GoogleCalendarController = CalendarController.include({ _onGoogleSyncCalendar: function (event) { var self = this; - return this.model._syncGoogleCalendar().then(function (o) { + return this._restartGoogleSynchronization().then(() => {return this.model._syncGoogleCalendar();}).then(function (o) { if (o.status === "need_auth") { Dialog.alert(self, _t("You will be redirected to Google to authorize access to your calendar!"), { confirm_callback: function() { @@ -117,20 +129,95 @@ const GoogleCalendarController = CalendarController.include({ } } else if (o.status === "need_refresh") { self.reload(); + return event.data.on_refresh(); } }).then(event.data.on_always, event.data.on_always); - } + }, + + _onStopGoogleSynchronization: function (event) { + var self = this; + Dialog.confirm(this, _t("You are about to stop the synchronization of your calendar with Google. Are you sure you want to continue?"), { + confirm_callback: function() { + return self._rpc({ + model: 'res.users', + method: 'stop_google_synchronization', + args: [[self.context.uid]], + }).then(() => { + self.displayNotification({ + title: _t("Success"), + message: _t("The synchronization with Google calendar was successfully stopped."), + type: 'success', + }); + }).then(event.data.on_confirm); + }, + title: _t('Confirmation'), + }); + + return event.data.on_always(); + }, + + _restartGoogleSynchronization: function () { + return this._rpc({ + model: 'res.users', + method: 'restart_google_synchronization', + args: [[this.context.uid]], + }); + }, + + _onArchiveRecord: function (event) { + var self = this; + Dialog.confirm(this, _t("Are you sure you want to archive this record ?"), { + confirm_callback: function () { + self.model.archiveRecords([event.data.id], self.modelName).then(function () { + self.reload(); + }); + } + }); + }, }); const GoogleCalendarRenderer = CalendarRenderer.include({ + custom_events: _.extend({}, CalendarRenderer.prototype.custom_events, { + archive_event: '_onArchiveEvent', + }), + events: _.extend({}, CalendarRenderer.prototype.events, { 'click .o_google_sync_button': '_onGoogleSyncCalendar', + 'click .o_stop_google_sync_button': '_onStopGoogleSynchronization', }), //-------------------------------------------------------------------------- // Private //-------------------------------------------------------------------------- + _initGooglePillButton: function() { + this.$googleStopButton.css({"cursor":"pointer", "font-size":"0.9em"}); + var switchBadgeClass = (elem) => {elem.toggleClass('badge-success'); elem.toggleClass('badge-danger');}; + this.$('.o_stop_google_sync_button').hover(() => { + switchBadgeClass(this.$googleStopButton); + this.$googleStopButton.html("".concat(_t("Stop the Synchronization"))); + }, () => { + switchBadgeClass(this.$googleStopButton); + this.$googleStopButton.html("".concat(_t("Synched with Google"))); + }); + }, + + _getGoogleButton: function () { + return $('