[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
This commit is contained in:
@@ -4,3 +4,4 @@
|
||||
from . import controllers
|
||||
from . import models
|
||||
from . import populate
|
||||
from . import wizard
|
||||
|
||||
@@ -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/**/*',
|
||||
|
||||
@@ -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
|
||||
|
||||
|
Binary file not shown.
|
After Width: | Height: | Size: 605 B |
Binary file not shown.
|
After Width: | Height: | Size: 3.9 KiB |
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -3,7 +3,21 @@
|
||||
|
||||
<t t-name="calendar.CalendarView" t-extend="CalendarView">
|
||||
<t t-jquery="div.o_calendar_sidebar" t-operation="append">
|
||||
<div id="calendar_sync" class="o_calendar_sync container inline btn-group"/>
|
||||
<div class="o_calendar_sync text-center" groups="base.group_system">
|
||||
<span class="text-primary fs-6">Synchronize with:</span>
|
||||
</div>
|
||||
<div id="calendar_sync" class="container inline btn-group justify-content-evenly align-items-center" groups="base.group_system">
|
||||
<div id="google_calendar_sync" class="o_calendar_sync">
|
||||
<button type="button" id="google_sync_activate" class="btn btn-muted">
|
||||
<b><i class='fa fa-plug'/> Google</b>
|
||||
</button>
|
||||
</div>
|
||||
<div id="microsoft_calendar_sync" class="o_calendar_sync">
|
||||
<button type="button" id="microsoft_sync_activate" class="btn btn-muted">
|
||||
<b><i class='fa fa-plug'/> Outlook</b>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
|
||||
from . import calendar_provider_config
|
||||
@@ -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)
|
||||
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
<record id="calendar_provider_config_view_form" model="ir.ui.view">
|
||||
<field name="name">calendar.provider.config.view.form</field>
|
||||
<field name="model">calendar.provider.config</field>
|
||||
<field name="arch" type="xml">
|
||||
<form js_class="calendar_provider_config_form">
|
||||
<field name="external_calendar_provider" widget="radio" options="{'horizontal': true}"/>
|
||||
<div attrs="{'invisible': [('external_calendar_provider', '!=', 'google')]}">
|
||||
<img alt="Google Calendar icon" src="/calendar/static/src/img/google_calendar_40.png" style="height: 40px; margin-right: 5px"/>
|
||||
<span class="me-1 o_form_label">Google Calendar</span>
|
||||
<a href="https://www.odoo.com/documentation/master/applications/general/calendars/google/google_calendar_credentials.html" title="Read More" class="o_doc_link" target="_blank"></a>
|
||||
<div class="text-muted mt-2">
|
||||
Synchronize your calendar with Google Calendar
|
||||
</div>
|
||||
<group>
|
||||
<field name="cal_client_id" attrs="{'required': [('external_calendar_provider', '=', 'google')]}"/>
|
||||
<field name="cal_client_secret" password="True" attrs="{'required': [('external_calendar_provider', '=', 'google')]}"/>
|
||||
</group>
|
||||
</div>
|
||||
<div attrs="{'invisible': [('external_calendar_provider', '!=', 'microsoft')]}">
|
||||
<img alt="Microsoft Outlook icon" src="/calendar/static/src/img/microsoft_calendar_40.png" style="height: 40px; margin-right: 5px"/>
|
||||
<span class="me-1 o_form_label">Outlook Calendar</span>
|
||||
<a href="https://www.odoo.com/documentation/master/applications/general/calendars/outlook/outlook_calendar.html" title="Read More" class="o_doc_link" target="_blank"></a>
|
||||
<div class="text-muted mt-2">
|
||||
Synchronize your calendar with Outlook
|
||||
</div>
|
||||
<group>
|
||||
<field name="microsoft_outlook_client_identifier" attrs="{'required': [('external_calendar_provider', '=', 'microsoft')]}"/>
|
||||
<field name="microsoft_outlook_client_secret" password="True" attrs="{'required': [('external_calendar_provider', '=', 'microsoft')]}"/>
|
||||
</group>
|
||||
</div>
|
||||
<footer>
|
||||
<a role="button" title="Connect" class="o_calendar_activate_external_cal btn btn-primary" t-on-click="onConnect">Connect</a>
|
||||
<button string="Cancel" class="btn btn-secondary" special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -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?"), {
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates>
|
||||
<t t-inherit="calendar.CalendarView" t-inherit-mode="extension">
|
||||
<xpath expr="//div[@id='calendar_sync']" position="inside">
|
||||
<button type="button" id="google_sync_pending" class="o_google_sync_button o_google_sync_pending btn btn-secondary btn">
|
||||
<b><i class='fa fa-refresh'/> Google</b>
|
||||
</button>
|
||||
<button type="button" id="google_sync_configured" class="me-1 o_google_sync_button o_google_sync_button_configured btn text-bg-primary">
|
||||
<b>
|
||||
<i id="google_check" class='fa fa-check'/>
|
||||
<i id="google_stop" class='fa fa-times' style="display: none"/>&nbsp;Google
|
||||
</b>
|
||||
</button>
|
||||
<xpath expr="//div[@id='google_calendar_sync']" position="replace">
|
||||
<div id="google_calendar_sync" class="o_calendar_sync">
|
||||
<button type="button" id="google_sync_pending" class="o_google_sync_button o_google_sync_pending btn btn-secondary btn">
|
||||
<b><i class='fa fa-refresh'/> Google</b>
|
||||
</button>
|
||||
<button type="button" id="google_sync_configured" class="me-1 o_google_sync_button o_google_sync_button_configured btn text-bg-primary">
|
||||
<b>
|
||||
<i id="google_check" class='fa fa-check'/>
|
||||
<i id="google_stop" class='fa fa-times' style="display: none"/>&nbsp;Google
|
||||
</b>
|
||||
</button>
|
||||
</div>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
|
||||
@@ -3,7 +3,6 @@ odoo.define('microsoft_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")];
|
||||
@@ -111,12 +110,8 @@ const MicrosoftCalendarController = CalendarController.include({
|
||||
|
||||
return this._restartMicrosoftSynchronization().then(() => {return this.model._syncMicrosoftCalendar();}).then(function (o) {
|
||||
if (o.status === "need_auth") {
|
||||
Dialog.alert(self, _t("You will be redirected to Outlook to authorize the 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('microsoft');
|
||||
} else if (o.status === "need_config_from_admin") {
|
||||
if (!_.isUndefined(o.action) && parseInt(o.action)) {
|
||||
Dialog.confirm(self, _t("The Outlook Synchronization needs to be configured before you can use it, do you want to do it now?"), {
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates>
|
||||
<t t-inherit="calendar.CalendarView" t-inherit-mode="extension">
|
||||
<xpath expr="//div[@id='calendar_sync']" position="inside">
|
||||
<button type="button" id="microsoft_sync_pending" class="o_microsoft_sync_button o_microsoft_sync_pending btn btn-secondary btn">
|
||||
<b><i class='fa fa-refresh'/> Outlook</b>
|
||||
</button>
|
||||
<button type="button" id="microsoft_sync_configured" class="me-1 o_microsoft_sync_button o_microsoft_sync_button_configured btn text-bg-primary">
|
||||
<b>
|
||||
<i id="microsoft_check" class='fa fa-check'/>
|
||||
<i id="microsoft_stop" class='fa fa-times' style="display: none"/>&nbsp;Outlook
|
||||
</b>
|
||||
</button>
|
||||
<xpath expr="//div[@id='microsoft_calendar_sync']" position="replace">
|
||||
<div id="microsoft_calendar_sync" class="o_calendar_sync">
|
||||
<button type="button" id="microsoft_sync_pending" class="o_microsoft_sync_button o_microsoft_sync_pending btn btn-secondary btn">
|
||||
<b><i class='fa fa-refresh'/> Outlook</b>
|
||||
</button>
|
||||
<button type="button" id="microsoft_sync_configured" class="me-1 o_microsoft_sync_button o_microsoft_sync_button_configured btn text-bg-primary">
|
||||
<b>
|
||||
<i id="microsoft_check" class='fa fa-check'/>
|
||||
<i id="microsoft_stop" class='fa fa-times' style="display: none"/>&nbsp;Outlook
|
||||
</b>
|
||||
</button>
|
||||
</div>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
|
||||
Reference in New Issue
Block a user