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 00000000000..46eacd17924 Binary files /dev/null and b/addons/calendar/static/src/img/google_calendar_40.png differ diff --git a/addons/calendar/static/src/img/microsoft_calendar_40.png b/addons/calendar/static/src/img/microsoft_calendar_40.png new file mode 100644 index 00000000000..baddba785c7 Binary files /dev/null and b/addons/calendar/static/src/img/microsoft_calendar_40.png differ 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 + +
+ +
+ Google Calendar icon + Google Calendar + +
+ Synchronize your calendar with Google Calendar +
+ + + + +
+
+ Microsoft Outlook icon + Outlook Calendar + +
+ Synchronize your calendar with Outlook +
+ + + + +
+ + +
+
+
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 @@ - - -