[MERGE][IMP] microsoft_outlook, fetchmail_outlook: improve usability, lessen module complexity

Purpose
=======

Improve the usability of the outlook modules. Lessen module complexity.

Specifications
==============

Remove the checkbox "Use Outlook" and instead use the
smtp_authentication and the server_type, to be consistent with Gmail.

Hide the password field for Outlook / Gmail mail servers.

Add constraints on the outgoing mail server to force the user to use
the right configuration (e.g. the from_filter, so the sending do not
fail).

Merge fetchmail_{google/microsoft} into google_gmail / microsoft_outlook?
Most of the time, both module will be installed at the same time, or
none. For that reason, we want to merge both modules, for both
providers, to lessen complexity.

Task-2811567

closes odoo/odoo#88215

Related: odoo/upgrade#3419
Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
Odoo's Mergebot
2022-07-12 15:36:51 +02:00
committed by GitHub
38 changed files with 420 additions and 472 deletions
+10 -1
View File
@@ -43,10 +43,11 @@ class FetchmailServer(models.Model):
server = fields.Char(string='Server Name', readonly=True, help="Hostname or IP of the mail server", states={'draft': [('readonly', False)]})
port = fields.Integer(readonly=True, states={'draft': [('readonly', False)]})
server_type = fields.Selection([
('pop', 'POP Server'),
('imap', 'IMAP Server'),
('pop', 'POP Server'),
('local', 'Local Server'),
], string='Server Type', index=True, required=True, default='pop')
server_type_info = fields.Text('Server Type Info', compute='_compute_server_type_info')
is_ssl = fields.Boolean('SSL/TLS', help="Connections are encrypted with SSL/TLS through a dedicated port (default: IMAPS=993, POP3S=995)")
attach = fields.Boolean('Keep Attachments', help="Whether attachments should be downloaded. "
"If not enabled, incoming emails will be stripped of any attachments before being processed", default=True)
@@ -64,6 +65,14 @@ class FetchmailServer(models.Model):
configuration = fields.Text('Configuration', readonly=True)
script = fields.Char(readonly=True, default='/mail/static/scripts/odoo-mailgate.py')
@api.depends('server_type')
def _compute_server_type_info(self):
for server in self:
if server.server_type == 'local':
server.server_type_info = _('Use a local script to fetch your emails and create new records.')
else:
server.server_type_info = False
@api.onchange('server_type', 'is_ssl', 'object_id')
def onchange_server_type(self):
self.port = 0
+23 -11
View File
@@ -5,14 +5,14 @@
<field name="name">fetchmail.server.list</field>
<field name="model">fetchmail.server</field>
<field name="arch" type="xml">
<tree decoration-info="state == 'draft'" string="POP/IMAP Servers">
<tree string="POP/IMAP Servers">
<field name="name"/>
<field name="server_type"/>
<field name="is_ssl"/>
<field name="object_id"/>
<field name="user"/>
<field name="date"/>
<field name="message_ids" string="Email Count"/>
<field name="state"/>
<field name="state" widget="badge"
decoration-info="state == 'draft'"
decoration-success="state == 'done'"/>
</tree>
</field>
</record>
@@ -29,10 +29,20 @@
<field name="state" widget="statusbar"/>
</header>
<sheet>
<group col="4">
<field name="name"/>
<field name="date"/>
<field name="server_type" widget="radio"/>
<widget name="web_ribbon" title="Archived" bg_color="bg-danger"
attrs="{'invisible': [('active', '=', True)]}"/>
<group>
<group>
<field name="name"/>
<field name="server_type" widget="radio" attrs="{'readonly': [('state', '=', 'done')]}"/>
</group>
<group>
<field name="date" attrs="{'invisible': [('date', '=', False)]}"/>
<div class="text-muted font-italic" colspan="8" role="alert"
attrs="{'invisible': [('server_type_info', '=', False)]}">
<field name="server_type_info"/>
</div>
</group>
</group>
<notebook>
<page string="Server &amp; Login" name="server_login_details">
@@ -47,7 +57,8 @@
</group>
<group attrs="{'invisible' : [('server_type', '=', 'local')]}" string="Login Information">
<field name="user" attrs="{'required' : [('server_type', '!=', 'local')]}"/>
<field name="password" password="True" attrs="{'required' : [('server_type', '!=', 'local')]}"/>
<field name="password" password="True"
attrs="{'required' : [('server_type', 'in', ('imap', 'pop'))], 'invisible' : [('server_type', 'not in', ('imap', 'pop'))]}"/>
</group>
<group string="Actions to Perform on Incoming Mails">
<field name="object_id"/>
@@ -63,7 +74,7 @@
<field name="priority"/>
<field name="attach"/>
<field name="original"/>
<field name="active" widget="boolean_toggle"/>
<field name="active" invisible="1"/>
</group>
</page>
</notebook>
@@ -78,6 +89,7 @@
<field name="arch" type="xml">
<search string="Search Incoming Mail Servers">
<field name="name" string="Incoming Mail Server"/>
<field name="user"/>
<filter string="IMAP" name="imap" domain="[('server_type', '=', 'imap')]" help="Server type IMAP."/>
<filter string="POP" name="pop" domain="[('server_type', '=', 'pop')]" help="Server type POP."/>
<separator/>
-4
View File
@@ -1,4 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import models
-16
View File
@@ -1,16 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
"name": "Fetchmail Gmail",
"version": "1.1",
"category": "Hidden",
"description": "Google authentication for incoming mail server",
"depends": [
"google_gmail",
"fetchmail",
],
"data": ["views/fetchmail_server_views.xml"],
"auto_install": True,
"license": "LGPL-3",
}
@@ -1,90 +0,0 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * fetchmail_gmail
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server saas~15.2\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-02-11 14:34+0000\n"
"PO-Revision-Date: 2022-02-11 14:34+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: fetchmail_gmail
#: model_terms:ir.ui.view,arch_db:fetchmail_gmail.fetchmail_server_view_form
msgid ""
"<i class=\"fa fa-arrow-right\"/>\n"
" Connect your Gmail account"
msgstr ""
#. module: fetchmail_gmail
#: model_terms:ir.ui.view,arch_db:fetchmail_gmail.fetchmail_server_view_form
msgid ""
"<i class=\"fa fa-cog\"/>\n"
" Edit Settings"
msgstr ""
#. module: fetchmail_gmail
#: model_terms:ir.ui.view,arch_db:fetchmail_gmail.fetchmail_server_view_form
msgid ""
"<span attrs=\"{'invisible': ['|', ('server_type', '!=', 'gmail'), ('google_gmail_refresh_token', '=', False)]}\" class=\"badge badge-success\">\n"
" Gmail Token Valid\n"
" </span>"
msgstr ""
#. module: fetchmail_gmail
#: model:ir.model.fields,field_description:fetchmail_gmail.field_fetchmail_server__google_gmail_access_token
msgid "Access Token"
msgstr ""
#. module: fetchmail_gmail
#: model:ir.model.fields,field_description:fetchmail_gmail.field_fetchmail_server__google_gmail_access_token_expiration
msgid "Access Token Expiration Timestamp"
msgstr ""
#. module: fetchmail_gmail
#: model:ir.model.fields,field_description:fetchmail_gmail.field_fetchmail_server__google_gmail_authorization_code
msgid "Authorization Code"
msgstr ""
#. module: fetchmail_gmail
#: model:ir.model.fields.selection,name:fetchmail_gmail.selection__fetchmail_server__server_type__gmail
msgid "Gmail OAuth Authentication"
msgstr ""
#. module: fetchmail_gmail
#: model:ir.model,name:fetchmail_gmail.model_fetchmail_server
msgid "Incoming Mail Server"
msgstr ""
#. module: fetchmail_gmail
#: model:ir.model.fields,field_description:fetchmail_gmail.field_fetchmail_server__google_gmail_refresh_token
msgid "Refresh Token"
msgstr ""
#. module: fetchmail_gmail
#: model:ir.model.fields,field_description:fetchmail_gmail.field_fetchmail_server__server_type
msgid "Server Type"
msgstr ""
#. module: fetchmail_gmail
#: model_terms:ir.ui.view,arch_db:fetchmail_gmail.fetchmail_server_view_form
msgid ""
"Setup your Gmail API credentials in the general settings to link a Gmail "
"account."
msgstr ""
#. module: fetchmail_gmail
#: model:ir.model.fields,help:fetchmail_gmail.field_fetchmail_server__google_gmail_uri
msgid "The URL to generate the authorization code from Google"
msgstr ""
#. module: fetchmail_gmail
#: model:ir.model.fields,field_description:fetchmail_gmail.field_fetchmail_server__google_gmail_uri
msgid "URI"
msgstr ""
@@ -1,4 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import fetchmail_server
-4
View File
@@ -1,4 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import models
-18
View File
@@ -1,18 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
"name": "Fetchmail Outlook",
"version": "1.0",
"category": "Hidden",
"description": "OAuth authentication for incoming Outlook mail server",
"depends": [
"microsoft_outlook",
"fetchmail",
],
"data": [
"views/fetchmail_server_views.xml",
],
"auto_install": True,
"license": "LGPL-3",
}
@@ -1,110 +0,0 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * fetchmail_outlook
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server saas~15.4\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-06-09 14:05+0000\n"
"PO-Revision-Date: 2022-06-09 14:05+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: fetchmail_outlook
#: model_terms:ir.ui.view,arch_db:fetchmail_outlook.fetchmail_server_view_form
msgid ""
"<i class=\"fa fa-arrow-right\"/>\n"
" Connect your Outlook account"
msgstr ""
#. module: fetchmail_outlook
#: model_terms:ir.ui.view,arch_db:fetchmail_outlook.fetchmail_server_view_form
msgid ""
"<i class=\"fa fa-cog\"/>\n"
" Edit Settings"
msgstr ""
#. module: fetchmail_outlook
#: model_terms:ir.ui.view,arch_db:fetchmail_outlook.fetchmail_server_view_form
msgid ""
"<span attrs=\"{'invisible': ['|', ('use_microsoft_outlook_service', '=', False), ('microsoft_outlook_refresh_token', '=', False)]}\" class=\"badge badge-success\">\n"
" Outlook Token Valid\n"
" </span>"
msgstr ""
#. module: fetchmail_outlook
#: model:ir.model.fields,field_description:fetchmail_outlook.field_fetchmail_server__microsoft_outlook_uri
msgid "Authentication URI"
msgstr ""
#. module: fetchmail_outlook
#: model:ir.model,name:fetchmail_outlook.model_fetchmail_server
msgid "Incoming Mail Server"
msgstr ""
#. module: fetchmail_outlook
#: model:ir.model.fields,field_description:fetchmail_outlook.field_fetchmail_server__is_microsoft_outlook_configured
msgid "Is Outlook Credential Configured"
msgstr ""
#. module: fetchmail_outlook
#: model_terms:ir.ui.view,arch_db:fetchmail_outlook.fetchmail_server_view_form
msgid "Outlook"
msgstr ""
#. module: fetchmail_outlook
#: model:ir.model.fields,field_description:fetchmail_outlook.field_fetchmail_server__microsoft_outlook_access_token
msgid "Outlook Access Token"
msgstr ""
#. module: fetchmail_outlook
#: model:ir.model.fields,field_description:fetchmail_outlook.field_fetchmail_server__microsoft_outlook_access_token_expiration
msgid "Outlook Access Token Expiration Timestamp"
msgstr ""
#. module: fetchmail_outlook
#: model:ir.model.fields,field_description:fetchmail_outlook.field_fetchmail_server__use_microsoft_outlook_service
msgid "Outlook Authentication"
msgstr ""
#. module: fetchmail_outlook
#: model:ir.model.fields,field_description:fetchmail_outlook.field_fetchmail_server__microsoft_outlook_refresh_token
msgid "Outlook Refresh Token"
msgstr ""
#. module: fetchmail_outlook
#: code:addons/fetchmail_outlook/models/fetchmail_server.py:0
#, python-format
msgid "Outlook mail server %r only supports IMAP server type."
msgstr ""
#. module: fetchmail_outlook
#: code:addons/fetchmail_outlook/models/fetchmail_server.py:0
#, python-format
msgid ""
"Please leave the password field empty for Outlook mail server %r. The OAuth "
"process does not require it"
msgstr ""
#. module: fetchmail_outlook
#: code:addons/fetchmail_outlook/models/fetchmail_server.py:0
#, python-format
msgid "SSL is required ."
msgstr ""
#. module: fetchmail_outlook
#: model_terms:ir.ui.view,arch_db:fetchmail_outlook.fetchmail_server_view_form
msgid ""
"Setup your Outlook API credentials in the general settings to link a Outlook"
" account."
msgstr ""
#. module: fetchmail_outlook
#: model:ir.model.fields,help:fetchmail_outlook.field_fetchmail_server__microsoft_outlook_uri
msgid "The URL to generate the authorization code from Outlook"
msgstr ""
@@ -1,4 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import fetchmail_server
@@ -1,57 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import _, api, models
from odoo.exceptions import UserError
class FetchmailServer(models.Model):
"""Add the Outlook OAuth authentication on the incoming mail servers."""
_name = 'fetchmail.server'
_inherit = ['fetchmail.server', 'microsoft.outlook.mixin']
_OUTLOOK_SCOPE = 'https://outlook.office.com/IMAP.AccessAsUser.All'
@api.constrains('use_microsoft_outlook_service', 'server_type', 'password', 'is_ssl')
def _check_use_microsoft_outlook_service(self):
for server in self:
if not server.use_microsoft_outlook_service:
continue
if server.server_type != 'imap':
raise UserError(_('Outlook mail server %r only supports IMAP server type.', server.name))
if server.password:
raise UserError(_(
'Please leave the password field empty for Outlook mail server %r. '
'The OAuth process does not require it', server.name))
if not server.is_ssl:
raise UserError(_('SSL is required .', server.name))
@api.onchange('use_microsoft_outlook_service')
def _onchange_use_microsoft_outlook_service(self):
"""Set the default configuration for a IMAP Outlook server."""
if self.use_microsoft_outlook_service:
self.server = 'imap.outlook.com'
self.server_type = 'imap'
self.is_ssl = True
self.port = 993
else:
self.microsoft_outlook_refresh_token = False
self.microsoft_outlook_access_token = False
self.microsoft_outlook_access_token_expiration = False
def _imap_login(self, connection):
"""Authenticate the IMAP connection.
If the mail server is Outlook, we use the OAuth2 authentication protocol.
"""
self.ensure_one()
if self.use_microsoft_outlook_service:
auth_string = self._generate_outlook_oauth2_string(self.user)
connection.authenticate('XOAUTH2', lambda x: auth_string)
connection.select('INBOX')
else:
super()._imap_login(connection)
@@ -131,7 +131,7 @@ class GoogleService(models.AbstractModel):
raise UserError(_('Google %s is not yet configured.', service.title()))
if not refresh_token:
raise UserError(_('The refresh token for authentication is not set.'))
raise UserError(_('Your Google account is not connected.'))
try:
result = requests.post(
+8 -2
View File
@@ -3,17 +3,23 @@
{
"name": "Google Gmail",
"version": "1.1",
"version": "1.2",
"category": "Hidden",
"description": "Gmail support for incoming / outgoing mail servers",
"depends": [
"fetchmail",
"mail",
"google_account",
],
"data": [
"views/fetchmail_server_views.xml",
"views/ir_mail_server_views.xml",
"views/res_config_settings_views.xml",
],
"auto_install": True,
"license": "LGPL-3",
"assets": {
"web.assets_backend": [
"google_gmail/static/src/scss/google_gmail.scss",
]
},
}
+2
View File
@@ -2,5 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import google_gmail_mixin
from . import fetchmail_server
from . import ir_mail_server
from . import res_config_settings
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
from odoo import _, api, fields, models
class FetchmailServer(models.Model):
@@ -10,6 +10,14 @@ class FetchmailServer(models.Model):
server_type = fields.Selection(selection_add=[('gmail', 'Gmail OAuth Authentication')], ondelete={'gmail': 'set default'})
def _compute_server_type_info(self):
gmail_servers = self.filtered(lambda server: server.server_type == 'gmail')
gmail_servers.server_type_info = _(
'Connect your Gmail account with the OAuth Authentication process. \n'
'You will be redirected to the Gmail login page where you will '
'need to accept the permission.')
super(FetchmailServer, self - gmail_servers)._compute_server_type_info()
@api.onchange('server_type', 'is_ssl', 'object_id')
def onchange_server_type(self):
"""Set the default configuration for a IMAP Gmail server."""
+36 -2
View File
@@ -3,7 +3,8 @@
import base64
from odoo import fields, models, api
from odoo import _, fields, models, api
from odoo.exceptions import UserError
class IrMailServer(models.Model):
@@ -16,6 +17,14 @@ class IrMailServer(models.Model):
selection_add=[('gmail', 'Gmail OAuth Authentication')],
ondelete={'gmail': 'set default'})
def _compute_smtp_authentication_info(self):
gmail_servers = self.filtered(lambda server: server.smtp_authentication == 'gmail')
gmail_servers.smtp_authentication_info = _(
'Connect your Gmail account with the OAuth Authentication process. \n'
'By default, only a user with a matching email address will be able to use this server. '
'To extend its use, you should set a "mail.default.from" system parameter.')
super(IrMailServer, self - gmail_servers)._compute_smtp_authentication_info()
@api.onchange('smtp_encryption')
def _onchange_encryption(self):
"""Do not change the SMTP configuration if it's a Gmail server
@@ -24,7 +33,7 @@ class IrMailServer(models.Model):
super(IrMailServer, self)._onchange_encryption()
@api.onchange('smtp_authentication')
def _onchange_smtp_authentication(self):
def _onchange_smtp_authentication_gmail(self):
if self.smtp_authentication == 'gmail':
self.smtp_host = 'smtp.gmail.com'
self.smtp_encryption = 'starttls'
@@ -35,6 +44,31 @@ class IrMailServer(models.Model):
self.google_gmail_access_token = False
self.google_gmail_access_token_expiration = False
@api.onchange('smtp_user', 'smtp_authentication')
def _on_change_smtp_user_gmail(self):
"""The Gmail mail servers can only be used for the user personal email address."""
if self.smtp_authentication == 'gmail':
self.from_filter = self.smtp_user
@api.constrains('smtp_authentication', 'smtp_pass', 'smtp_encryption', 'from_filter', 'smtp_user')
def _check_use_google_gmail_service(self):
gmail_servers = self.filtered(lambda server: server.smtp_authentication == 'gmail')
for server in gmail_servers:
if server.smtp_pass:
raise UserError(_(
'Please leave the password field empty for Gmail mail server %r. '
'The OAuth process does not require it', server.name))
if server.smtp_encryption != 'starttls':
raise UserError(_(
'Incorrect Connection Security for Gmail mail server %r. '
'Please set it to "TLS (STARTTLS)".', server.name))
if server.from_filter != server.smtp_user:
raise UserError(_(
'This server %r can only be used for your personal email address. '
'Please fill the "from_filter" field with %r.', server.name, server.smtp_user))
def _smtp_login(self, connection, smtp_user, smtp_password):
if len(self) == 1 and self.smtp_authentication == 'gmail':
auth_string = self._generate_oauth2_string(smtp_user, self.google_gmail_refresh_token)
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

@@ -0,0 +1 @@
<?xml version="1.0" ?><svg height="48px" viewBox="0 0 48 48" width="48px" xmlns="http://www.w3.org/2000/svg"><path d="M45,16.2l-5,2.75l-5,4.75L35,40h7c1.657,0,3-1.343,3-3V16.2z" fill="#4caf50"/><path d="M3,16.2l3.614,1.71L13,23.7V40H6c-1.657,0-3-1.343-3-3V16.2z" fill="#1e88e5"/><polygon fill="#e53935" points="35,11.2 24,19.45 13,11.2 12,17 13,23.7 24,31.95 35,23.7 36,17"/><path d="M3,12.298V16.2l10,7.5V11.2L9.876,8.859C9.132,8.301,8.228,8,7.298,8h0C4.924,8,3,9.924,3,12.298z" fill="#c62828"/><path d="M45,12.298V16.2l-10,7.5V11.2l3.124-2.341C38.868,8.301,39.772,8,40.702,8h0 C43.076,8,45,9.924,45,12.298z" fill="#fbc02d"/></svg>

After

Width:  |  Height:  |  Size: 632 B

@@ -0,0 +1,4 @@
span.o_field_char[name="google_gmail_authorization_code"] {
max-width: 150px !important;
overflow: hidden;
}
@@ -9,8 +9,8 @@
<field name="user" position="after">
<field name="google_gmail_uri" invisible="1"/>
<field name="google_gmail_refresh_token" invisible="1"/>
<div></div>
<div attrs="{'invisible': [('server_type', '!=', 'gmail')]}">
<div attrs="{'invisible': [('server_type', '!=', 'gmail')]}"
class="d-flex flex-row align-items-center" colspan="8">
<span attrs="{'invisible': ['|', ('server_type', '!=', 'gmail'), ('google_gmail_refresh_token', '=', False)]}"
class="badge text-bg-success">
Gmail Token Valid
@@ -22,22 +22,18 @@
Connect your Gmail account
</button>
<button type="object"
name="open_google_gmail_uri" class="btn-link px-0"
name="open_google_gmail_uri" class="btn-link px-0 ml-2"
attrs="{'invisible': ['|', '|', ('google_gmail_uri', '=', False), ('server_type', '!=', 'gmail'), ('google_gmail_refresh_token', '=', False)]}">
<i class="fa fa-cog"/>
Edit Settings
<i class="fa fa-cog" title="Edit Settings"/>
</button>
<div class="alert alert-warning" role="alert"
<button class="alert alert-warning d-block mt-2 text-left"
icon="fa-arrow-right" type="action" role="alert"
name="%(base.res_config_setting_act_window)d"
attrs="{'invisible': ['|', ('server_type', '!=', 'gmail'), ('google_gmail_uri', '!=', False)]}">
Setup your Gmail API credentials in the general settings to link a Gmail account.
</div>
</button>
</div>
</field>
<field name="password" position="attributes">
<attribute name="attrs">
{'required' : [('server_type', '!=', 'local'), ('server_type', '!=', 'gmail'), ('password', '!=', False)], 'invisible' : [('server_type', '=', 'gmail')]}
</attribute>
</field>
</field>
</record>
</odoo>
@@ -8,8 +8,8 @@
<field name="smtp_user" position="after">
<field name="google_gmail_uri" invisible="1"/>
<field name="google_gmail_refresh_token" invisible="1"/>
<div></div>
<div attrs="{'invisible': [('smtp_authentication', '!=', 'gmail')]}">
<div attrs="{'invisible': [('smtp_authentication', '!=', 'gmail')]}"
class="d-flex flex-row align-items-center" colspan="8">
<span attrs="{'invisible': ['|', ('smtp_authentication', '!=', 'gmail'), ('google_gmail_refresh_token', '=', False)]}"
class="badge text-bg-success">
Gmail Token Valid
@@ -21,22 +21,23 @@
Connect your Gmail account
</button>
<button type="object"
name="open_google_gmail_uri" class="btn-link px-0"
name="open_google_gmail_uri" class="btn-link px-0 ml-2"
attrs="{'invisible': ['|', '|', ('google_gmail_uri', '=', False), ('smtp_authentication', '!=', 'gmail'), ('google_gmail_refresh_token', '=', False)]}">
<i class="fa fa-cog"/>
Edit Settings
<i class="fa fa-cog" title="Edit Settings"/>
</button>
<div class="alert alert-warning" role="alert"
<button class="alert alert-warning d-block mt-2 text-left"
icon="fa-arrow-right" type="action" role="alert"
name="%(base.res_config_setting_act_window)d"
attrs="{'invisible': ['|', ('smtp_authentication', '!=', 'gmail'), ('google_gmail_uri', '!=', False)]}">
Setup your Gmail API credentials in the general settings to link a Gmail account.
</div>
</button>
</div>
</field>
<field name="smtp_user" position="attributes">
<attribute name="attrs">{'invisible' : [('smtp_authentication', '!=', 'login'), ('smtp_authentication', '!=', 'gmail')]}</attribute>
</field>
<field name="smtp_pass" position="attributes">
<attribute name="attrs">{'invisible' : [('smtp_authentication', '=', 'gmail')]}</attribute>
<field name="smtp_authentication_info" position="after">
<a href="https://www.odoo.com/documentation/15.0/applications/general/email_communication/email_servers.html?highlight=outgoing email server#use-a-default-from-email-address"
attrs="{'invisible': [('smtp_authentication', '!=', 'gmail')]}" target="_blank">
Read More
</a>
</field>
</field>
</record>
@@ -8,12 +8,14 @@
<field name="arch" type="xml">
<div id="msg_module_google_gmail" position="replace">
<div class="row mt16" id="gmail_client_identifier">
<label string="Client ID" for="google_gmail_client_identifier" class="col-lg-3 o_light_label"/>
<field name="google_gmail_client_identifier" class="ms-2"/>
<label string="ID" for="google_gmail_client_identifier" class="col-lg-3 o_light_label"/>
<field name="google_gmail_client_identifier" class="ms-2"
placeholder="ID of your Google app"/>
</div>
<div class="row mt16" id="gmail_client_secret">
<label string="Client Secret" for="google_gmail_client_secret" class="col-lg-3 o_light_label"/>
<field name="google_gmail_client_secret" class="ms-2"/>
<label string="Secret" for="google_gmail_client_secret" class="col-lg-3 o_light_label"/>
<field name="google_gmail_client_secret" class="ms-2"
placeholder="Secret of your Google app"/>
</div>
</div>
</field>
@@ -16,6 +16,7 @@ class ResConfigSettings(models.TransientModel):
'Alias Domain', config_parameter='mail.catchall.domain',
help="If you have setup a catch-all email domain redirected to the Odoo server, enter the domain name here.")
module_google_gmail = fields.Boolean('Support Gmail Authentication')
module_microsoft_outlook = fields.Boolean('Support Outlook Authentication')
restrict_template_rendering = fields.Boolean(
'Restrict Template Rendering',
config_parameter='mail.restrict.template.rendering',
+31 -17
View File
@@ -36,13 +36,43 @@
<div class="text-muted" id="external_email_server_default">
Configure your own email servers
</div>
<div class="content-group" attrs="{'invisible': [('external_email_server_default', '=', False)]}">
<div class="content-group mb-3" attrs="{'invisible': [('external_email_server_default', '=', False)]}">
<div class="mt16" id="mail_alias_domain">
<label for="alias_domain" class="o_light_label"/>
<span>@</span>
<field name="alias_domain" placeholder='e.g. "mycompany.com"'/>
</div>
</div>
<div class="mt-3 d-flex" attrs="{'invisible': [('external_email_server_default', '=', False)]}">
<field name="module_google_gmail"/>
<div>
<label string="Gmail Credentials" for="module_google_gmail"/>
<a href="https://console.developers.google.com/" title="Get Gmail API credentials" class="o_doc_link" target="_blank"/>
<div class="text-muted">
Send and receive emails through your Gmail account.
</div>
<div class="content-group" attrs="{'invisible': [('module_google_gmail','=',False)]}" id="msg_module_google_gmail">
<div class="mt16 text-warning"><strong>Save</strong> this page and come back here to set up the feature.</div>
</div>
</div>
</div>
<div class="mt-3 d-flex" attrs="{'invisible': [('external_email_server_default', '=', False)]}">
<field name="module_microsoft_outlook"/>
<div>
<label string="Outlook Credentials" for="module_microsoft_outlook"/>
<a href="https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app"
title="Get Outlook API credentials" class="o_doc_link" target="_blank"/>
<div class="text-muted">
Send and receive emails through your Outlook account.
</div>
<div class="content-group" id="msg_module_microsoft_outlook"
attrs="{'invisible': [('module_microsoft_outlook','=',False)]}">
<div class="mt16 text-warning">
<strong>Save</strong> this page and come back here to set up the feature.
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-12 col-lg-6 o_setting_box">
@@ -66,22 +96,6 @@
</div>
</div>
</div>
<div class="col-12 col-lg-6 o_setting_box" id="google_gmail_setting"
attrs="{'invisible': [('external_email_server_default', '=', False)]}">
<div class="o_setting_left_pane">
<field name="module_google_gmail"/>
</div>
<div class="o_setting_right_pane">
<label string="Gmail Credentials" for="module_google_gmail"/>
<a href="https://console.developers.google.com/" title="Get Gmail API credentials" class="o_doc_link" target="_blank"/>
<div class="text-muted">
Send and receive emails with your Gmail account.
</div>
<div class="content-group" attrs="{'invisible': [('module_google_gmail','=',False)]}" id="msg_module_google_gmail">
<div class="mt16 text-warning"><strong>Save</strong> this page and come back here to set up the feature.</div>
</div>
</div>
</div>
<div class="col-12 col-lg-6 o_setting_box">
<div class="o_setting_right_pane">
<span class="o_form_label">Custom ICE server list</span>
+4 -2
View File
@@ -3,13 +3,15 @@
{
"name": "Microsoft Outlook",
"version": "1.0",
"version": "1.1",
"category": "Hidden",
"description": "Outlook support for outgoing mail servers",
"description": "Outlook support for incoming / outgoing mail servers",
"depends": [
"fetchmail",
"mail",
],
"data": [
"views/fetchmail_server_views.xml",
"views/ir_mail_server_views.xml",
"views/res_config_settings_views.xml",
"views/templates.xml",
@@ -3,5 +3,6 @@
from . import microsoft_outlook_mixin
from . import fetchmail_server
from . import ir_mail_server
from . import res_config_settings
@@ -0,0 +1,69 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import _, api, fields, models
from odoo.exceptions import UserError
class FetchmailServer(models.Model):
"""Add the Outlook OAuth authentication on the incoming mail servers."""
_name = 'fetchmail.server'
_inherit = ['fetchmail.server', 'microsoft.outlook.mixin']
_OUTLOOK_SCOPE = 'https://outlook.office.com/IMAP.AccessAsUser.All'
server_type = fields.Selection(selection_add=[('outlook', 'Outlook OAuth Authentication')], ondelete={'outlook': 'set default'})
def _compute_server_type_info(self):
outlook_servers = self.filtered(lambda server: server.server_type == 'outlook')
outlook_servers.server_type_info = _(
'Connect your personal Outlook account using OAuth. \n'
'You will be redirected to the Outlook login page to accept '
'the permissions.')
super(FetchmailServer, self - outlook_servers)._compute_server_type_info()
@api.depends('server_type')
def _compute_is_microsoft_outlook_configured(self):
outlook_servers = self.filtered(lambda server: server.server_type == 'outlook')
(self - outlook_servers).is_microsoft_outlook_configured = False
super(FetchmailServer, outlook_servers)._compute_is_microsoft_outlook_configured()
@api.constrains('server_type', 'is_ssl')
def _check_use_microsoft_outlook_service(self):
for server in self:
if server.server_type == 'outlook' and not server.is_ssl:
raise UserError(_('SSL is required for the server %r.', server.name))
@api.onchange('server_type')
def onchange_server_type(self):
"""Set the default configuration for a IMAP Outlook server."""
if self.server_type == 'outlook':
self.server = 'imap.outlook.com'
self.is_ssl = True
self.port = 993
else:
self.microsoft_outlook_refresh_token = False
self.microsoft_outlook_access_token = False
self.microsoft_outlook_access_token_expiration = False
super(FetchmailServer, self).onchange_server_type()
def _imap_login(self, connection):
"""Authenticate the IMAP connection.
If the mail server is Outlook, we use the OAuth2 authentication protocol.
"""
self.ensure_one()
if self.server_type == 'outlook':
auth_string = self._generate_outlook_oauth2_string(self.user)
connection.authenticate('XOAUTH2', lambda x: auth_string)
connection.select('INBOX')
else:
super()._imap_login(connection)
def _get_connection_type(self):
"""Return which connection must be used for this mail server (IMAP or POP).
The Outlook mail server used an IMAP connection.
"""
self.ensure_one()
return 'imap' if self.server_type == 'outlook' else super()._get_connection_type()
@@ -3,7 +3,7 @@
import base64
from odoo import _, api, models
from odoo import _, api, fields, models
from odoo.exceptions import UserError
@@ -15,12 +15,28 @@ class IrMailServer(models.Model):
_OUTLOOK_SCOPE = 'https://outlook.office.com/SMTP.Send'
@api.constrains('use_microsoft_outlook_service', 'smtp_pass', 'smtp_encryption')
def _check_use_microsoft_outlook_service(self):
for server in self:
if not server.use_microsoft_outlook_service:
continue
smtp_authentication = fields.Selection(
selection_add=[('outlook', 'Outlook OAuth Authentication')],
ondelete={'outlook': 'set default'})
@api.depends('smtp_authentication')
def _compute_is_microsoft_outlook_configured(self):
outlook_servers = self.filtered(lambda server: server.smtp_authentication == 'outlook')
(self - outlook_servers).is_microsoft_outlook_configured = False
super(IrMailServer, outlook_servers)._compute_is_microsoft_outlook_configured()
def _compute_smtp_authentication_info(self):
outlook_servers = self.filtered(lambda server: server.smtp_authentication == 'outlook')
outlook_servers.smtp_authentication_info = _(
'Connect your Outlook account with the OAuth Authentication process. \n'
'By default, only a user with a matching email address will be able to use this server. '
'To extend its use, you should set a "mail.default.from" system parameter.')
super(IrMailServer, self - outlook_servers)._compute_smtp_authentication_info()
@api.constrains('smtp_authentication', 'smtp_pass', 'smtp_encryption', 'from_filter', 'smtp_user')
def _check_use_microsoft_outlook_service(self):
outlook_servers = self.filtered(lambda server: server.smtp_authentication == 'outlook')
for server in outlook_servers:
if server.smtp_pass:
raise UserError(_(
'Please leave the password field empty for Outlook mail server %r. '
@@ -31,17 +47,22 @@ class IrMailServer(models.Model):
'Incorrect Connection Security for Outlook mail server %r. '
'Please set it to "TLS (STARTTLS)".', server.name))
if server.from_filter != server.smtp_user:
raise UserError(_(
'This server %r can only be used for your personal email address. '
'Please fill the "from_filter" field with %r.', server.name, server.smtp_user))
@api.onchange('smtp_encryption')
def _onchange_encryption(self):
"""Do not change the SMTP configuration if it's a Outlook server
(e.g. the port which is already set)"""
if not self.use_microsoft_outlook_service:
if self.smtp_authentication != 'outlook':
super()._onchange_encryption()
@api.onchange('use_microsoft_outlook_service')
def _onchange_use_microsoft_outlook_service(self):
if self.use_microsoft_outlook_service:
@api.onchange('smtp_authentication')
def _onchange_smtp_authentication_outlook(self):
if self.smtp_authentication == 'outlook':
self.smtp_host = 'smtp.outlook.com'
self.smtp_encryption = 'starttls'
self.smtp_port = 587
@@ -50,11 +71,17 @@ class IrMailServer(models.Model):
self.microsoft_outlook_access_token = False
self.microsoft_outlook_access_token_expiration = False
@api.onchange('smtp_user', 'smtp_authentication')
def _on_change_smtp_user_outlook(self):
"""The Outlook mail servers can only be used for the user personal email address."""
if self.smtp_authentication == 'outlook':
self.from_filter = self.smtp_user
def _smtp_login(self, connection, smtp_user, smtp_password):
if len(self) == 1 and self.use_microsoft_outlook_service:
if len(self) == 1 and self.smtp_authentication == 'outlook':
auth_string = self._generate_outlook_oauth2_string(smtp_user)
oauth_param = base64.b64encode(auth_string.encode()).decode()
connection.ehlo()
connection.docmd('AUTH', 'XOAUTH2 %s' % oauth_param)
connection.docmd('AUTH', f'XOAUTH2 {oauth_param}')
else:
super()._smtp_login(connection, smtp_user, smtp_password)
@@ -23,7 +23,6 @@ class MicrosoftOutlookMixin(models.AbstractModel):
_OUTLOOK_SCOPE = None
_OUTLOOK_ENDPOINT = 'https://login.microsoftonline.com/common/oauth2/v2.0/'
use_microsoft_outlook_service = fields.Boolean('Outlook Authentication')
is_microsoft_outlook_configured = fields.Boolean('Is Outlook Credential Configured',
compute='_compute_is_microsoft_outlook_configured')
microsoft_outlook_refresh_token = fields.Char(string='Outlook Refresh Token',
@@ -35,25 +34,25 @@ class MicrosoftOutlookMixin(models.AbstractModel):
microsoft_outlook_uri = fields.Char(compute='_compute_outlook_uri', string='Authentication URI',
help='The URL to generate the authorization code from Outlook', groups='base.group_system')
@api.depends('use_microsoft_outlook_service')
def _compute_is_microsoft_outlook_configured(self):
Config = self.env['ir.config_parameter'].sudo()
microsoft_outlook_client_id = Config.get_param('microsoft_outlook_client_id')
microsoft_outlook_client_secret = Config.get_param('microsoft_outlook_client_secret')
self.is_microsoft_outlook_configured = microsoft_outlook_client_id and microsoft_outlook_client_secret
@api.depends('use_microsoft_outlook_service')
@api.depends('is_microsoft_outlook_configured')
def _compute_outlook_uri(self):
Config = self.env['ir.config_parameter'].sudo()
base_url = self.get_base_url()
microsoft_outlook_client_id = Config.get_param('microsoft_outlook_client_id')
OUTLOOK_ENDPOINT = Config.get_param('microsoft.outlook.endpoint', self._OUTLOOK_ENDPOINT)
for record in self:
if not record.id or not record.use_microsoft_outlook_service or not record.is_microsoft_outlook_configured:
if not record.id or not record.is_microsoft_outlook_configured:
record.microsoft_outlook_uri = False
continue
record.microsoft_outlook_uri = url_join(self._OUTLOOK_ENDPOINT, 'authorize?%s' % url_encode({
record.microsoft_outlook_uri = url_join(OUTLOOK_ENDPOINT, 'authorize?%s' % url_encode({
'client_id': microsoft_outlook_client_id,
'response_type': 'code',
'redirect_uri': url_join(base_url, '/microsoft_outlook/confirm'),
@@ -79,7 +78,7 @@ class MicrosoftOutlookMixin(models.AbstractModel):
if not self.env.user.has_group('base.group_system'):
raise AccessError(_('Only the administrator can link an Outlook mail server.'))
if not self.use_microsoft_outlook_service or not self.is_microsoft_outlook_configured:
if not self.is_microsoft_outlook_configured:
raise UserError(_('Please configure your Outlook credentials.'))
return {
@@ -124,9 +123,10 @@ class MicrosoftOutlookMixin(models.AbstractModel):
base_url = self.get_base_url()
microsoft_outlook_client_id = Config.get_param('microsoft_outlook_client_id')
microsoft_outlook_client_secret = Config.get_param('microsoft_outlook_client_secret')
OUTLOOK_ENDPOINT = Config.get_param('microsoft.outlook.endpoint', self._OUTLOOK_ENDPOINT)
response = requests.post(
url_join(self._OUTLOOK_ENDPOINT, 'token'),
url_join(OUTLOOK_ENDPOINT, 'token'),
data={
'client_id': microsoft_outlook_client_id,
'client_secret': microsoft_outlook_client_secret,
@@ -159,7 +159,7 @@ class MicrosoftOutlookMixin(models.AbstractModel):
or not self.microsoft_outlook_access_token_expiration \
or self.microsoft_outlook_access_token_expiration < now_timestamp:
if not self.microsoft_outlook_refresh_token:
raise UserError(_('Please login your Outlook mail server before using it.'))
raise UserError(_('Please connect with your Outlook account before using it.'))
(
self.microsoft_outlook_access_token,
self.microsoft_outlook_access_token_expiration,
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

@@ -0,0 +1,51 @@
<?xml version="1.0" ?><svg id="Capa_1" style="enable-background:new 0 0 128 128;" version="1.1" viewBox="0 0 128 128" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><style type="text/css">
.st0{fill:#21A365;}
.st1{fill:#107C41;}
.st2{fill:#185B37;}
.st3{fill:#33C481;}
.st4{fill:#17864C;}
.st5{fill:#FFFFFF;}
.st6{fill:#036C70;}
.st7{fill:#1A9BA1;}
.st8{fill:#37C6D0;}
.st9{fill:#04878B;}
.st10{fill:#4F59CA;}
.st11{fill:#7B82EA;}
.st12{fill:#4C53BB;}
.st13{fill:#0F78D5;}
.st14{fill:#29A7EB;}
.st15{fill:#0358A8;}
.st16{fill:#0F79D6;}
.st17{fill:#038387;}
.st18{fill:#048A8E;}
.st19{fill:#C8421D;}
.st20{fill:#FF8F6A;}
.st21{fill:#ED6B47;}
.st22{fill:#891323;}
.st23{fill:#AF2131;}
.st24{fill:#C94E60;}
.st25{fill:#E08195;}
.st26{fill:#B42839;}
.st27{fill:#0464B8;}
.st28{fill:#0377D4;}
.st29{fill:#4FD8FF;}
.st30{fill:#1681D7;}
.st31{fill:#0178D4;}
.st32{fill:#042071;}
.st33{fill:#168FDE;}
.st34{fill:#CA64EA;}
.st35{fill:#7E1FAF;}
.st36{fill:#AE4BD5;}
.st37{fill:#9332BF;}
.st38{fill:#7719AA;}
.st39{fill:#0078D4;}
.st40{fill:#1490DF;}
.st41{fill:#0364B8;}
.st42{fill:#28A8EA;}
.st43{fill:#41A5ED;}
.st44{fill:#2C7BD5;}
.st45{fill:#195ABE;}
.st46{fill:#103E91;}
.st47{fill:#2166C3;}
.st48{opacity:0.2;}
</style><g><path class="st27" d="M120.2,22.8H34.2V9.1C34.2,6.8,36,5,38.2,5h77.9c2.3,0,4.1,1.8,4.1,4.1V22.8z"/><rect class="st28" height="26.6" width="28.7" x="34.2" y="22.8"/><rect class="st14" height="26.6" width="28.7" x="62.9" y="22.8"/><rect class="st29" height="26.6" width="28.7" x="91.6" y="22.8"/><rect class="st30" height="26.6" width="28.7" x="34.2" y="49.4"/><rect class="st31" height="26.6" width="28.7" x="62.9" y="49.4"/><rect class="st14" height="26.6" width="28.7" x="91.6" y="49.4"/><rect class="st27" height="26.6" width="28.7" x="62.9" y="75.9"/><rect class="st30" height="26.6" width="28.7" x="91.6" y="75.9"/></g><path class="st32" d="M126.9,69.1l-6.6,3.9V61.8l6.5,3.6C128.3,66.1,128.3,68.2,126.9,69.1z"/><path class="st33" d="M126.9,69.1l-0.6,0.4l0,0l-0.7,0.4l-5.3,3.1v-0.1l-88.4,50.6h89.1c3.8,0,6.8-3.1,6.8-6.8l0.1-49.4 C128,67.9,127.6,68.7,126.9,69.1z"/><g><path class="st14" d="M122,123.5H32.8c-3.8,0-6.8-3.1-6.8-6.8V68.5L122,123.5z"/></g><path class="st30" d="M59,96.5h-53c-3.5,0-6.4-2.9-6.4-6.4V37.9c0-3.5,2.9-6.4,6.4-6.4h53c3.5,0,6.4,2.9,6.4,6.4v52.2 C65.4,93.6,62.6,96.5,59,96.5z"/><g><path class="st5" d="M32.5,82.9c-10.3,0-16.8-7.8-16.8-18.2c0-11,7.1-18.8,17.3-18.8c10.6,0,16.8,7.9,16.8,18.1 C49.9,76.1,42.5,82.9,32.5,82.9L32.5,82.9z M32.9,77.7c6.4,0,10-5.9,10-13.4c0-6.8-3.4-13.1-10-13.1s-10.1,6.2-10.1,13.4 S26.4,77.7,32.9,77.7L32.9,77.7z"/></g><path class="st48" d="M65.5,37.3c0,0.2,0,0.4,0,0.6v52.2c0,3.5-2.9,6.4-6.4,6.4H26v5.7h38.4c3.5,0,6.4-2.9,6.4-6.4V43.6 C70.8,40.4,68.5,37.7,65.5,37.3z"/></svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

@@ -21,12 +21,11 @@ class TestFetchmailOutlook(TransactionCase):
mail_server = self.env['fetchmail.server'].create({
'name': 'Test server',
'use_microsoft_outlook_service': True,
'server_type': 'outlook',
'user': 'test@example.com',
'microsoft_outlook_access_token': 'test_access_token',
'microsoft_outlook_access_token_expiration': time.time() + 1000000,
'password': '',
'server_type': 'imap',
'is_ssl': True,
})
@@ -45,15 +44,6 @@ class TestFetchmailOutlook(TransactionCase):
with self.assertRaises(UserError, msg='Should ensure that the password is empty'):
self.env['fetchmail.server'].create({
'name': 'Test server',
'use_microsoft_outlook_service': True,
'server_type': 'outlook',
'password': 'test',
'server_type': 'imap',
})
with self.assertRaises(UserError, msg='Should ensure that the server type is IMAP'):
self.env['fetchmail.server'].create({
'name': 'Test server',
'use_microsoft_outlook_service': True,
'password': '',
'server_type': 'pop',
})
@@ -6,42 +6,36 @@
<field name="priority">1000</field>
<field name="inherit_id" ref="fetchmail.view_email_server_form"/>
<field name="arch" type="xml">
<field name="server" position="before">
<field name="use_microsoft_outlook_service" string="Outlook"
attrs="{'readonly': [('state', '=', 'done')]}"/>
</field>
<field name="user" position="after">
<field name="is_microsoft_outlook_configured" invisible="1"/>
<field name="microsoft_outlook_refresh_token" invisible="1"/>
<field name="microsoft_outlook_access_token" invisible="1"/>
<field name="microsoft_outlook_access_token_expiration" invisible="1"/>
<div></div>
<div attrs="{'invisible': [('use_microsoft_outlook_service', '=', False)]}">
<span attrs="{'invisible': ['|', ('use_microsoft_outlook_service', '=', False), ('microsoft_outlook_refresh_token', '=', False)]}"
<div attrs="{'invisible': [('server_type', '!=', 'outlook')]}"
class="d-flex flex-row align-items-center" colspan="8">
<span attrs="{'invisible': ['|', ('server_type', '!=', 'outlook'), ('microsoft_outlook_refresh_token', '=', False)]}"
class="badge text-bg-success">
Outlook Token Valid
</span>
<button type="object"
name="open_microsoft_outlook_uri" class="btn-link px-0"
attrs="{'invisible': ['|', '|', ('is_microsoft_outlook_configured', '=', False), ('use_microsoft_outlook_service', '=', False), ('microsoft_outlook_refresh_token', '!=', False)]}">
attrs="{'invisible': ['|', '|', ('is_microsoft_outlook_configured', '=', False), ('server_type', '!=', 'outlook'), ('microsoft_outlook_refresh_token', '!=', False)]}">
<i class="fa fa-arrow-right"/>
Connect your Outlook account
</button>
<button type="object"
name="open_microsoft_outlook_uri" class="btn-link px-0"
attrs="{'invisible': ['|', '|', ('is_microsoft_outlook_configured', '=', False), ('use_microsoft_outlook_service', '=', False), ('microsoft_outlook_refresh_token', '=', False)]}">
<i class="fa fa-cog"/>
Edit Settings
name="open_microsoft_outlook_uri" class="btn-link px-0 ml-2"
attrs="{'invisible': ['|', '|', ('is_microsoft_outlook_configured', '=', False), ('server_type', '!=', 'outlook'), ('microsoft_outlook_refresh_token', '=', False)]}">
<i class="fa fa-cog" title="Edit Settings"/>
</button>
<div class="alert alert-warning" role="alert"
attrs="{'invisible': ['|', ('is_microsoft_outlook_configured', '=', True), ('use_microsoft_outlook_service', '=', False)]}">
<button class="alert alert-warning d-block mt-2 text-left"
icon="fa-arrow-right" type="action" role="alert"
name="%(base.res_config_setting_act_window)d"
attrs="{'invisible': ['|', ('is_microsoft_outlook_configured', '=', True), ('server_type', '!=', 'outlook')]}">
Setup your Outlook API credentials in the general settings to link a Outlook account.
</div>
</button>
</div>
</field>
<field name="password" position="attributes">
<attribute name="attrs">{}</attribute>
</field>
</field>
</record>
</odoo>
@@ -5,38 +5,42 @@
<field name="model">ir.mail_server</field>
<field name="inherit_id" ref="base.ir_mail_server_form"/>
<field name="arch" type="xml">
<field name="smtp_host" position="before">
<field name="use_microsoft_outlook_service" string="Outlook"/>
</field>
<field name="smtp_user" position="after">
<field name="is_microsoft_outlook_configured" invisible="1"/>
<field name="microsoft_outlook_refresh_token" invisible="1"/>
<field name="microsoft_outlook_access_token" invisible="1"/>
<field name="microsoft_outlook_access_token_expiration" invisible="1"/>
<div></div>
<div attrs="{'invisible': [('use_microsoft_outlook_service', '=', False)]}">
<span attrs="{'invisible': ['|', ('use_microsoft_outlook_service', '=', False), ('microsoft_outlook_refresh_token', '=', False)]}"
<div attrs="{'invisible': [('smtp_authentication', '!=', 'outlook')]}"
class="d-flex flex-row align-items-center" colspan="8">
<span attrs="{'invisible': ['|', ('smtp_authentication', '!=', 'outlook'), ('microsoft_outlook_refresh_token', '=', False)]}"
class="badge text-bg-success">
Outlook Token Valid
</span>
<button type="object"
name="open_microsoft_outlook_uri" class="btn-link px-0"
attrs="{'invisible': ['|', '|', ('is_microsoft_outlook_configured', '=', False), ('use_microsoft_outlook_service', '=', False), ('microsoft_outlook_refresh_token', '!=', False)]}">
attrs="{'invisible': ['|', '|', ('is_microsoft_outlook_configured', '=', False), ('smtp_authentication', '!=', 'outlook'), ('microsoft_outlook_refresh_token', '!=', False)]}">
<i class="fa fa-arrow-right"/>
Connect your Outlook account
</button>
<button type="object"
name="open_microsoft_outlook_uri" class="btn-link px-0"
attrs="{'invisible': ['|', '|', ('is_microsoft_outlook_configured', '=', False), ('use_microsoft_outlook_service', '=', False), ('microsoft_outlook_refresh_token', '=', False)]}">
<i class="fa fa-cog"/>
Edit Settings
name="open_microsoft_outlook_uri" class="btn-link px-0 ml-2"
attrs="{'invisible': ['|', '|', ('is_microsoft_outlook_configured', '=', False), ('smtp_authentication', '!=', 'outlook'), ('microsoft_outlook_refresh_token', '=', False)]}">
<i class="fa fa-cog" title="Edit Settings"/>
</button>
<div class="alert alert-warning" role="alert"
attrs="{'invisible': ['|', ('is_microsoft_outlook_configured', '=', True), ('use_microsoft_outlook_service', '=', False)]}">
<button class="alert alert-warning d-block mt-2 text-left"
icon="fa-arrow-right" type="action" role="alert"
name="%(base.res_config_setting_act_window)d"
attrs="{'invisible': ['|', ('is_microsoft_outlook_configured', '=', True), ('smtp_authentication', '!=', 'outlook')]}">
Setup your Outlook API credentials in the general settings to link a Outlook account.
</div>
</button>
</div>
</field>
<field name="smtp_authentication_info" position="after">
<a href="https://www.odoo.com/documentation/15.0/applications/general/email_communication/email_servers.html?highlight=outgoing email server#use-a-default-from-email-address"
attrs="{'invisible': [('smtp_authentication', '!=', 'outlook')]}" target="_blank">
Read More
</a>
</field>
</field>
</record>
</odoo>
@@ -6,27 +6,18 @@
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="base_setup.res_config_settings_view_form"/>
<field name="arch" type="xml">
<div id="emails" position="inside">
<div class="col-12 col-lg-6 o_setting_box" id="microsoft_outlook_setting"
attrs="{'invisible': [('external_email_server_default', '=', False)]}">
<div class="o_setting_right_pane">
<span class="o_form_label">Outlook Credentials</span>
<div class="text-muted">
Send and receive email with your Outlook account.
</div>
<div class="content-group">
<div class="row mt16" id="outlook_client_identifier">
<label string="Client ID" for="microsoft_outlook_client_identifier"
class="col-lg-3 o_light_label"/>
<field name="microsoft_outlook_client_identifier" class="ms-2"/>
</div>
<div class="row mt16" id="outlook_client_secret">
<label string="Client Secret" for="microsoft_outlook_client_secret"
class="col-lg-3 o_light_label"/>
<field name="microsoft_outlook_client_secret" class="ms-2"/>
</div>
</div>
</div>
<div id="msg_module_microsoft_outlook" position="replace">
<div class="row mt16" id="outlook_client_identifier">
<label string="ID" for="microsoft_outlook_client_identifier"
class="col-lg-3 o_light_label"/>
<field name="microsoft_outlook_client_identifier" class="ms-2"
placeholder="ID of your Outlook app"/>
</div>
<div class="row mt16" id="outlook_client_secret">
<label string="Secret" for="microsoft_outlook_client_secret"
class="col-lg-3 o_light_label"/>
<field name="microsoft_outlook_client_secret" class="ms-2"
placeholder="Secret of your Outlook app"/>
</div>
</div>
</field>
+16
View File
@@ -95,6 +95,7 @@ class IrMailServer(models.Model):
smtp_host = fields.Char(string='SMTP Server', required=True, help="Hostname or IP of SMTP server")
smtp_port = fields.Integer(string='SMTP Port', required=True, default=25, help="SMTP Port. Usually 465 for SSL, and 25 or 587 for other cases.")
smtp_authentication = fields.Selection([('login', 'Username'), ('certificate', 'SSL Certificate')], string='Authenticate with', required=True, default='login')
smtp_authentication_info = fields.Text('Authentication Info', compute='_compute_smtp_authentication_info')
smtp_user = fields.Char(string='Username', help="Optional username for SMTP authentication", groups='base.group_system')
smtp_pass = fields.Char(string='Password', help="Optional password for SMTP authentication", groups='base.group_system')
smtp_encryption = fields.Selection([('none', 'None'),
@@ -118,6 +119,21 @@ class IrMailServer(models.Model):
"is used. Default priority is 10 (smaller number = higher priority)")
active = fields.Boolean(default=True)
@api.depends('smtp_authentication')
def _compute_smtp_authentication_info(self):
for server in self:
if server.smtp_authentication == 'login':
server.smtp_authentication_info = _(
'Connect to your server through your usual username and password. \n'
'This is the most basic SMTP authentication process and '
'may not be accepted by all providers. \n')
elif server.smtp_authentication == 'certificate':
server.smtp_authentication_info = _(
'Authenticate by using SSL certificates, belonging to your domain name. \n'
'SSL certificates allow you to authenticate your mail server for the entire domain name.')
else:
server.smtp_authentication = False
@api.constrains('smtp_ssl_certificate', 'smtp_ssl_private_key')
def _check_smtp_ssl_files(self):
"""We must provided both files or none."""
+35 -15
View File
@@ -10,27 +10,47 @@
name="test_smtp_connection" class="btn-primary"/>
</header>
<sheet>
<group col="4">
<field name="name" placeholder="e.g. My Outgoing Server"/>
<field name="from_filter"/>
<field name="sequence"/>
<field name="active" widget="boolean_toggle"/>
<widget name="web_ribbon" title="Archived" bg_color="bg-danger"
attrs="{'invisible': [('active', '=', True)]}"/>
<group>
<group>
<field name="name" placeholder="e.g. My Outgoing Server"/>
<field name="from_filter"/>
</group>
<group>
<field name="sequence"/>
<field name="active" invisible="1"/>
</group>
</group>
<group>
<group string="Authentication">
<group>
<field name="smtp_authentication" widget="radio"/>
<field name="smtp_user" attrs="{'invisible': [('smtp_authentication', '!=', 'login')]}" force_save="1"/>
<field name="smtp_pass" attrs="{'invisible': [('smtp_authentication', '!=', 'login')]}" password="True" force_save="1"/>
<field name="smtp_ssl_certificate" attrs="{'invisible': [('smtp_authentication', '!=', 'certificate')]}" force_save="1"/>
<field name="smtp_ssl_private_key" attrs="{'invisible': [('smtp_authentication', '!=', 'certificate')]}" force_save="1"/>
</group>
<group string="Connection">
<field name="smtp_encryption" widget="radio"/>
<field name="smtp_host"/>
<field name="smtp_port" options="{'format': false}"/>
<field name="smtp_debug" groups="base.group_no_one"/>
<group>
<div class="text-muted font-italic" role="alert"
attrs="{'invisible': [('smtp_authentication_info', '=', False)]}">
<field name="smtp_authentication_info"/>
</div>
</group>
</group>
<notebook colspan="4">
<page name="connection" string="Connection">
<group>
<group>
<field name="smtp_encryption" widget="radio"/>
<field name="smtp_host"/>
<field name="smtp_port" options="{'format': false}"/>
<field name="smtp_debug" groups="base.group_no_one"/>
</group>
<group>
<field name="smtp_user" attrs="{'invisible': [('smtp_authentication', '=', 'certificate')]}" force_save="1"/>
<field name="smtp_pass" attrs="{'invisible': [('smtp_authentication', '!=', 'login')]}" password="True" force_save="1"/>
<field name="smtp_ssl_certificate" attrs="{'invisible': [('smtp_authentication', '!=', 'certificate')]}" force_save="1"/>
<field name="smtp_ssl_private_key" attrs="{'invisible': [('smtp_authentication', '!=', 'certificate')]}" force_save="1"/>
</group>
</group>
</page>
</notebook>
</sheet>
</form>
</field>