From ba2b0020dfc2a6484fd4a9f2ed72eece414a823a Mon Sep 17 00:00:00 2001 From: Florian Charlier Date: Fri, 5 Aug 2022 13:04:11 +0000 Subject: [PATCH] [IMP] calendar, *: configure provider * = google_calendar, microsoft_calendar, web Purpose: Allow to install appropriate module and configure calendar provider credentials in fewer steps, without needing to go to general settings, etc. This requires that both buttons are visible from the start. In this commit, we also prepare the ability to extend this configuration wizard for the appointment onboarding (see ENT PR). Task-2852375 Part of Task-2900763 Part-of: odoo/odoo#97105 --- addons/calendar/__init__.py | 1 + addons/calendar/__manifest__.py | 2 + addons/calendar/security/ir.model.access.csv | 2 + .../static/src/img/google_calendar_40.png | Bin 0 -> 605 bytes .../static/src/img/microsoft_calendar_40.png | Bin 0 -> 3969 bytes .../static/src/js/calendar_renderer.js | 29 +++++++ .../calendar_provider_config_form_renderer.js | 71 ++++++++++++++++++ .../calendar_provider_config_form_view.js | 13 ++++ .../calendar/static/src/xml/base_calendar.xml | 16 +++- addons/calendar/wizard/__init__.py | 5 ++ .../wizard/calendar_provider_config.py | 50 ++++++++++++ .../wizard/calendar_provider_config.xml | 40 ++++++++++ .../static/src/js/google_calendar.js | 9 +-- .../static/src/xml/base_calendar.xml | 22 +++--- .../static/src/js/microsoft_calendar.js | 9 +-- .../static/src/xml/base_calendar.xml | 22 +++--- 16 files changed, 256 insertions(+), 35 deletions(-) create mode 100644 addons/calendar/static/src/img/google_calendar_40.png create mode 100644 addons/calendar/static/src/img/microsoft_calendar_40.png create mode 100644 addons/calendar/static/src/views/calendar_provider_config_form_renderer.js create mode 100644 addons/calendar/static/src/views/calendar_provider_config_form_view.js create mode 100644 addons/calendar/wizard/__init__.py create mode 100644 addons/calendar/wizard/calendar_provider_config.py create mode 100644 addons/calendar/wizard/calendar_provider_config.xml diff --git a/addons/calendar/__init__.py b/addons/calendar/__init__.py index ca9d16e5409..48dadfaf2b2 100644 --- a/addons/calendar/__init__.py +++ b/addons/calendar/__init__.py @@ -4,3 +4,4 @@ from . import controllers from . import models from . import populate +from . import wizard diff --git a/addons/calendar/__manifest__.py b/addons/calendar/__manifest__.py index f8aec351693..636b7f9e4fa 100644 --- a/addons/calendar/__manifest__.py +++ b/addons/calendar/__manifest__.py @@ -34,6 +34,7 @@ If you need to manage your meetings, you should install the CRM module. 'views/calendar_templates.xml', 'views/calendar_views.xml', 'views/res_partner_views.xml', + 'wizard/calendar_provider_config.xml' ], 'installable': True, 'application': True, @@ -49,6 +50,7 @@ If you need to manage your meetings, you should install the CRM module. 'calendar/static/src/js/calendar_model.js', 'calendar/static/src/js/calendar_view.js', 'calendar/static/src/js/services/calendar_notification_service.js', + 'calendar/static/src/views/*', ], 'web.qunit_suite_tests': [ 'calendar/static/tests/**/*', diff --git a/addons/calendar/security/ir.model.access.csv b/addons/calendar/security/ir.model.access.csv index 5a3b8812f1d..da97d846e0a 100644 --- a/addons/calendar/security/ir.model.access.csv +++ b/addons/calendar/security/ir.model.access.csv @@ -11,3 +11,5 @@ access_calendar_alarm_manager,access_calendar_alarm_manager,model_calendar_alarm access_calendar_filters_all,access_calendar_contacts_all,model_calendar_filters,base.group_user,1,1,1,1 access_calendar_filters,access_calendar_contacts,model_calendar_filters,base.group_system,1,1,1,1 access_calendar_recurrence,access_calendar_recurrence,model_calendar_recurrence,base.group_user,1,1,1,1 +access_calendar_provider_config_all,calendar.provider.config.all,model_calendar_provider_config,,0,0,0,0 +access_calendar_provider_config_manager,calendar.provider.config.manager,model_calendar_provider_config,base.group_system,1,1,1,1 diff --git a/addons/calendar/static/src/img/google_calendar_40.png b/addons/calendar/static/src/img/google_calendar_40.png new file mode 100644 index 0000000000000000000000000000000000000000..46eacd17924881eb34ef9ff5330dff7c47ce32c6 GIT binary patch literal 605 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 literal 0 HcmV?d00001 diff --git a/addons/calendar/static/src/js/calendar_renderer.js b/addons/calendar/static/src/js/calendar_renderer.js index 516720d33ad..a71de015dc8 100644 --- a/addons/calendar/static/src/js/calendar_renderer.js +++ b/addons/calendar/static/src/js/calendar_renderer.js @@ -1,5 +1,6 @@ /** @odoo-module alias=calendar.CalendarRenderer **/ +import {_t} from 'web.core'; import CalendarRenderer from 'web.CalendarRenderer'; import CalendarPopover from 'web.CalendarPopover'; import session from 'web.session'; @@ -110,6 +111,20 @@ const AttendeeCalendarRenderer = CalendarRenderer.extend({ CalendarPopover: AttendeeCalendarPopover, eventTemplate: 'Calendar.calendar-box', }), + + events: _.extend({}, CalendarRenderer.prototype.events, { + 'click #google_sync_activate': '_onConfigureExternalCalendarGoogle', + 'click #microsoft_sync_activate': '_onConfigureExternalCalendarMicrosoft', + }), + + _onConfigureExternalCalendarGoogle: function (e) { + e.preventDefault(); + this._configureCalendarProviderSync('google'); + }, + _onConfigureExternalCalendarMicrosoft: function (e) { + e.preventDefault(); + this._configureCalendarProviderSync('microsoft'); + }, /** * Add the attendee-id attribute in order to distinct the events when there are * several attendees in the event. @@ -131,6 +146,20 @@ const AttendeeCalendarRenderer = CalendarRenderer.extend({ } return selector; }, + _configureCalendarProviderSync: function (ProviderName) { + this.do_action({ + name: _t('Connect your Calendar'), + type: 'ir.actions.act_window', + res_model: 'calendar.provider.config', + views: [[false, "form"]], + view_mode: "form", + target: 'new', + context: { + 'default_external_calendar_provider': ProviderName, + 'dialog_size': 'medium', + } + }); + }, }); export default { diff --git a/addons/calendar/static/src/views/calendar_provider_config_form_renderer.js b/addons/calendar/static/src/views/calendar_provider_config_form_renderer.js new file mode 100644 index 00000000000..97e25a03231 --- /dev/null +++ b/addons/calendar/static/src/views/calendar_provider_config_form_renderer.js @@ -0,0 +1,71 @@ +/** @odoo-module **/ + +import { FormRenderer } from "@web/views/form/form_renderer"; +import { session } from "@web/session"; +import { useService } from "@web/core/utils/hooks"; + +const providerData = { + 'google': { + 'restart_sync_method': 'restart_google_synchronization', + 'sync_route': '/google_calendar/sync_data' + }, + 'microsoft': { + 'restart_sync_method': 'restart_microsoft_synchronization', + 'sync_route': '/microsoft_calendar/sync_data' + } +} + + +export class CalendarProviderConfigFormRenderer extends FormRenderer { + setup() { + super.setup(); + this.orm = useService('orm'); + this.rpc = useService('rpc'); + } + + /** + * Activate the external sync for the first time, after installing the + * relevant submodule if necessary. + * + * @private + */ + async onConnect(ev) { + ev.preventDefault(); + ev.stopImmediatePropagation(); + if (!await this.props.record.save({stayInEdition: true})) { + return; // handled by view + } + await this.orm.call( + this.props.record.resModel, + 'action_calendar_prepare_external_provider_sync', + [this.props.record.resId] + ) + // See google/microsoft_calendar for the origin of this shortened version + const { restart_sync_method, sync_route } = providerData[this.props.record.data.external_calendar_provider]; + await this.orm.call( + 'res.users', + restart_sync_method, + [[session.uid]] + ); + const response = await this.rpc( + sync_route, { + model: 'calendar.event', + fromurl: window.location.href, + } + ); + await this._beforeLeaveContext(); + if (response.status === "need_auth") { + window.location.assign(response.url); + } else if (response.status === "need_refresh") { + window.location.reload(); + } + } + /** + * Hook to perform additional work before redirecting to external url or reloading. + * + * @private + */ + async _beforeLeaveContext() { + return Promise.resolve(); + } +} diff --git a/addons/calendar/static/src/views/calendar_provider_config_form_view.js b/addons/calendar/static/src/views/calendar_provider_config_form_view.js new file mode 100644 index 00000000000..bca8bfbf241 --- /dev/null +++ b/addons/calendar/static/src/views/calendar_provider_config_form_view.js @@ -0,0 +1,13 @@ +/** @odoo-module **/ + +import { registry } from "@web/core/registry"; +import { formView } from "@web/views/form/form_view"; +import { CalendarProviderConfigFormRenderer } from "./calendar_provider_config_form_renderer"; + + +export const CalendarProviderConfigFormView = { + ...formView, + Renderer: CalendarProviderConfigFormRenderer, +}; + +registry.category("views").add("calendar_provider_config_form", CalendarProviderConfigFormView); diff --git a/addons/calendar/static/src/xml/base_calendar.xml b/addons/calendar/static/src/xml/base_calendar.xml index 8919bb7fc70..9ca5ec3ef8b 100644 --- a/addons/calendar/static/src/xml/base_calendar.xml +++ b/addons/calendar/static/src/xml/base_calendar.xml @@ -3,7 +3,21 @@ -
+
+ Synchronize with: +
+
+
+ +
+
+ +
+
diff --git a/addons/calendar/wizard/__init__.py b/addons/calendar/wizard/__init__.py new file mode 100644 index 00000000000..0d1fe747cc7 --- /dev/null +++ b/addons/calendar/wizard/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + + +from . import calendar_provider_config diff --git a/addons/calendar/wizard/calendar_provider_config.py b/addons/calendar/wizard/calendar_provider_config.py new file mode 100644 index 00000000000..8755303da5a --- /dev/null +++ b/addons/calendar/wizard/calendar_provider_config.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import fields, models +from odoo.addons.base.models.ir_module import assert_log_admin_access + + +class CalendarProviderConfig(models.TransientModel): + _name = 'calendar.provider.config' + _description = 'Calendar Provider Configuration Wizard' + + external_calendar_provider = fields.Selection([ + ('google', 'Google'), ('microsoft', 'Outlook')], + "Choose an external calendar to configure", default='google') + + # Allow to sync with eventually existing ICP keys without creating them if respective module is not installed + # Using same field names and strings as their respective res.config.settings + cal_client_id = fields.Char( + "Google Client_id", + default=lambda self: self.env['ir.config_parameter'].get_param('google_calendar_client_id')) + cal_client_secret = fields.Char( + "Google Client_key", + default=lambda self: self.env['ir.config_parameter'].get_param('google_calendar_client_secret')) + microsoft_outlook_client_identifier = fields.Char( + "Outlook Client Id", + default=lambda self: self.env['ir.config_parameter'].get_param('microsoft_calendar_client_id')) + microsoft_outlook_client_secret = fields.Char( + "Outlook Client Secret", + default=lambda self: self.env['ir.config_parameter'].get_param('microsoft_calendar_client_secret')) + + @assert_log_admin_access + def action_calendar_prepare_external_provider_sync(self): + """ Called by the wizard to configure an external calendar provider without requiring users + to access the general settings page. + Make sure that the provider calendar module is installed or install it. Then, set + the API keys into the applicable config parameters. + """ + self.ensure_one() + calendar_module = self.env['ir.module.module'].search([ + ('name', '=', f'{self.external_calendar_provider}_calendar')]) + + if calendar_module.state != 'installed': + calendar_module.button_immediate_install() + + if self.external_calendar_provider == 'google': + self.env['ir.config_parameter'].set_param('google_calendar_client_id', self.cal_client_id) + self.env['ir.config_parameter'].set_param('google_calendar_client_secret', self.cal_client_secret) + elif self.external_calendar_provider == 'microsoft': + self.env['ir.config_parameter'].set_param('microsoft_calendar_client_id', self.microsoft_outlook_client_identifier) + self.env['ir.config_parameter'].set_param('microsoft_calendar_client_secret', self.microsoft_outlook_client_secret) diff --git a/addons/calendar/wizard/calendar_provider_config.xml b/addons/calendar/wizard/calendar_provider_config.xml new file mode 100644 index 00000000000..be4bf019cde --- /dev/null +++ b/addons/calendar/wizard/calendar_provider_config.xml @@ -0,0 +1,40 @@ + + + + calendar.provider.config.view.form + calendar.provider.config + + + + + diff --git a/addons/google_calendar/static/src/js/google_calendar.js b/addons/google_calendar/static/src/js/google_calendar.js index ccd47ba3a29..312c9485cbc 100644 --- a/addons/google_calendar/static/src/js/google_calendar.js +++ b/addons/google_calendar/static/src/js/google_calendar.js @@ -3,7 +3,6 @@ odoo.define('google_calendar.CalendarView', function (require) { var core = require('web.core'); var Dialog = require('web.Dialog'); -var framework = require('web.framework'); const CalendarRenderer = require('@calendar/js/calendar_renderer')[Symbol.for("default")].AttendeeCalendarRenderer; const CalendarController = require('@calendar/js/calendar_controller')[Symbol.for("default")]; const CalendarModel = require('@calendar/js/calendar_model')[Symbol.for("default")]; @@ -112,12 +111,8 @@ const GoogleCalendarController = CalendarController.include({ 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() { - framework.redirect(o.url); - }, - title: _t('Redirection'), - }); + // allows to configure API keys in modal and will then redirect. + self.renderer._configureCalendarProviderSync('google'); } else if (o.status === "need_config_from_admin") { if (!_.isUndefined(o.action) && parseInt(o.action)) { Dialog.confirm(self, _t("The Google Synchronization needs to be configured before you can use it, do you want to do it now?"), { diff --git a/addons/google_calendar/static/src/xml/base_calendar.xml b/addons/google_calendar/static/src/xml/base_calendar.xml index 667d8c5188d..40265369aca 100644 --- a/addons/google_calendar/static/src/xml/base_calendar.xml +++ b/addons/google_calendar/static/src/xml/base_calendar.xml @@ -1,16 +1,18 @@ - - -
+ +
+ Google Calendar icon + Google Calendar + +
+ Synchronize your calendar with Google Calendar +
+ + + + +
+
+ Microsoft Outlook icon + Outlook Calendar + +
+ Synchronize your calendar with Outlook +
+ + + + +
+ +