From e9f712278d5c6ab68d86be2ed7727aefd4625fff Mon Sep 17 00:00:00 2001 From: std-odoo Date: Tue, 10 May 2022 07:39:44 +0000 Subject: [PATCH] [IMP] microsoft_outlook, fetchmail_outlook *: improve usability * fetchmail, google_gmail, fetchmail_gmail Purpose ======= Improve the usability of the outlook modules. 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 does not fail). Add an option in the mail module to install Outlook. Show a message for the outgoing mail servers to explain each authentication methods. Task-2811567 Part-of: odoo/odoo#88215 --- addons/fetchmail/models/fetchmail.py | 9 +++ addons/fetchmail/views/fetchmail_views.xml | 23 ++++++-- .../models/fetchmail_server.py | 10 +++- .../views/fetchmail_server_views.xml | 20 +++---- .../models/fetchmail_server.py | 52 +++++++++++------- .../tests/test_fetchmail_outlook.py | 14 +---- .../views/fetchmail_server_views.xml | 30 ++++------ .../google_account/models/google_service.py | 2 +- addons/google_gmail/__manifest__.py | 7 ++- addons/google_gmail/models/ir_mail_server.py | 38 ++++++++++++- .../google_gmail/static/description/icon.png | Bin 0 -> 3627 bytes .../google_gmail/static/description/icon.svg | 1 + .../static/src/scss/google_gmail.scss | 4 ++ .../views/ir_mail_server_views.xml | 25 +++++---- .../views/res_config_settings_views.xml | 10 ++-- addons/mail/models/res_config_settings.py | 1 + .../mail/views/res_config_settings_views.xml | 48 ++++++++++------ addons/microsoft_outlook/__manifest__.py | 2 +- .../models/ir_mail_server.py | 51 +++++++++++++---- .../models/microsoft_outlook_mixin.py | 10 ++-- .../static/description/icon.png | Bin 0 -> 2928 bytes .../static/description/icon.svg | 51 +++++++++++++++++ .../views/ir_mail_server_views.xml | 32 ++++++----- .../views/res_config_settings_views.xml | 33 ++++------- odoo/addons/base/models/ir_mail_server.py | 16 ++++++ .../base/views/ir_mail_server_views.xml | 36 ++++++++---- 26 files changed, 355 insertions(+), 170 deletions(-) create mode 100644 addons/google_gmail/static/description/icon.png create mode 100644 addons/google_gmail/static/description/icon.svg create mode 100644 addons/google_gmail/static/src/scss/google_gmail.scss create mode 100644 addons/microsoft_outlook/static/description/icon.png create mode 100644 addons/microsoft_outlook/static/description/icon.svg diff --git a/addons/fetchmail/models/fetchmail.py b/addons/fetchmail/models/fetchmail.py index f4de274195e..ea9c649dea3 100644 --- a/addons/fetchmail/models/fetchmail.py +++ b/addons/fetchmail/models/fetchmail.py @@ -47,6 +47,7 @@ class FetchmailServer(models.Model): ('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 diff --git a/addons/fetchmail/views/fetchmail_views.xml b/addons/fetchmail/views/fetchmail_views.xml index 880c79ff652..359faf5d06b 100644 --- a/addons/fetchmail/views/fetchmail_views.xml +++ b/addons/fetchmail/views/fetchmail_views.xml @@ -29,10 +29,20 @@ - - - - + + + + + + + + + + @@ -47,7 +57,8 @@ - + @@ -63,7 +74,7 @@ - + diff --git a/addons/fetchmail_gmail/models/fetchmail_server.py b/addons/fetchmail_gmail/models/fetchmail_server.py index 09196892015..1669d636944 100644 --- a/addons/fetchmail_gmail/models/fetchmail_server.py +++ b/addons/fetchmail_gmail/models/fetchmail_server.py @@ -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.""" diff --git a/addons/fetchmail_gmail/views/fetchmail_server_views.xml b/addons/fetchmail_gmail/views/fetchmail_server_views.xml index 259ebe57ee4..aae7e7a9202 100644 --- a/addons/fetchmail_gmail/views/fetchmail_server_views.xml +++ b/addons/fetchmail_gmail/views/fetchmail_server_views.xml @@ -9,8 +9,8 @@ -
-
+
Gmail Token Valid @@ -22,22 +22,18 @@ Connect your Gmail account - +
- - - {'required' : [('server_type', '!=', 'local'), ('server_type', '!=', 'gmail'), ('password', '!=', False)], 'invisible' : [('server_type', '=', 'gmail')]} - - diff --git a/addons/fetchmail_outlook/models/fetchmail_server.py b/addons/fetchmail_outlook/models/fetchmail_server.py index 1e36ebe9fe0..52df4eaf582 100644 --- a/addons/fetchmail_outlook/models/fetchmail_server.py +++ b/addons/fetchmail_outlook/models/fetchmail_server.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo import _, api, models +from odoo import _, api, fields, models from odoo.exceptions import UserError @@ -13,35 +13,40 @@ class FetchmailServer(models.Model): _OUTLOOK_SCOPE = 'https://outlook.office.com/IMAP.AccessAsUser.All' - @api.constrains('use_microsoft_outlook_service', 'server_type', 'password', 'is_ssl') + 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 not server.use_microsoft_outlook_service: - continue + if server.server_type == 'outlook' and not server.is_ssl: + raise UserError(_('SSL is required for the server %r.', server.name)) - 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): + @api.onchange('server_type') + def onchange_server_type(self): """Set the default configuration for a IMAP Outlook server.""" - if self.use_microsoft_outlook_service: + if self.server_type == 'outlook': 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 + super(FetchmailServer, self).onchange_server_type() def _imap_login(self, connection): """Authenticate the IMAP connection. @@ -49,9 +54,16 @@ class FetchmailServer(models.Model): If the mail server is Outlook, we use the OAuth2 authentication protocol. """ self.ensure_one() - if self.use_microsoft_outlook_service: + 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() diff --git a/addons/fetchmail_outlook/tests/test_fetchmail_outlook.py b/addons/fetchmail_outlook/tests/test_fetchmail_outlook.py index 8cc07d1764d..143f379a598 100644 --- a/addons/fetchmail_outlook/tests/test_fetchmail_outlook.py +++ b/addons/fetchmail_outlook/tests/test_fetchmail_outlook.py @@ -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', }) diff --git a/addons/fetchmail_outlook/views/fetchmail_server_views.xml b/addons/fetchmail_outlook/views/fetchmail_server_views.xml index 30d432d74e0..a572fd3e448 100644 --- a/addons/fetchmail_outlook/views/fetchmail_server_views.xml +++ b/addons/fetchmail_outlook/views/fetchmail_server_views.xml @@ -6,42 +6,36 @@ 1000 - - - -
-
- + Outlook Token Valid - +
- - {} -
diff --git a/addons/google_account/models/google_service.py b/addons/google_account/models/google_service.py index ec644bd131b..09bcd919091 100644 --- a/addons/google_account/models/google_service.py +++ b/addons/google_account/models/google_service.py @@ -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( diff --git a/addons/google_gmail/__manifest__.py b/addons/google_gmail/__manifest__.py index 76a8727f44e..a91d4fd0ed8 100644 --- a/addons/google_gmail/__manifest__.py +++ b/addons/google_gmail/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Google Gmail", - "version": "1.1", + "version": "1.2", "category": "Hidden", "description": "Gmail support for incoming / outgoing mail servers", "depends": [ @@ -16,4 +16,9 @@ ], "auto_install": True, "license": "LGPL-3", + "assets": { + "web.assets_backend": [ + "google_gmail/static/src/scss/google_gmail.scss", + ] + }, } diff --git a/addons/google_gmail/models/ir_mail_server.py b/addons/google_gmail/models/ir_mail_server.py index eb338bc1c55..57835f6ebb6 100644 --- a/addons/google_gmail/models/ir_mail_server.py +++ b/addons/google_gmail/models/ir_mail_server.py @@ -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) diff --git a/addons/google_gmail/static/description/icon.png b/addons/google_gmail/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f28d0ab573f781ec0e4fbcf6a0c9554e99f109dd GIT binary patch literal 3627 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 literal 0 HcmV?d00001 diff --git a/addons/google_gmail/static/description/icon.svg b/addons/google_gmail/static/description/icon.svg new file mode 100644 index 00000000000..7965971e95e --- /dev/null +++ b/addons/google_gmail/static/description/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addons/google_gmail/static/src/scss/google_gmail.scss b/addons/google_gmail/static/src/scss/google_gmail.scss new file mode 100644 index 00000000000..1bc54662b5c --- /dev/null +++ b/addons/google_gmail/static/src/scss/google_gmail.scss @@ -0,0 +1,4 @@ +span.o_field_char[name="google_gmail_authorization_code"] { + max-width: 150px !important; + overflow: hidden; +} diff --git a/addons/google_gmail/views/ir_mail_server_views.xml b/addons/google_gmail/views/ir_mail_server_views.xml index 910a77efa46..b8a25334623 100644 --- a/addons/google_gmail/views/ir_mail_server_views.xml +++ b/addons/google_gmail/views/ir_mail_server_views.xml @@ -8,8 +8,8 @@ -
-
+
Gmail Token Valid @@ -21,22 +21,23 @@ Connect your Gmail account - +
- - {'invisible' : [('smtp_authentication', '!=', 'login'), ('smtp_authentication', '!=', 'gmail')]} - - - {'invisible' : [('smtp_authentication', '=', 'gmail')]} + + + Read More + diff --git a/addons/google_gmail/views/res_config_settings_views.xml b/addons/google_gmail/views/res_config_settings_views.xml index 3a7a1bfcdac..d0d83760d49 100644 --- a/addons/google_gmail/views/res_config_settings_views.xml +++ b/addons/google_gmail/views/res_config_settings_views.xml @@ -8,12 +8,14 @@
-
-
diff --git a/addons/mail/models/res_config_settings.py b/addons/mail/models/res_config_settings.py index 04e6769c969..bfc67e9252e 100644 --- a/addons/mail/models/res_config_settings.py +++ b/addons/mail/models/res_config_settings.py @@ -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', diff --git a/addons/mail/views/res_config_settings_views.xml b/addons/mail/views/res_config_settings_views.xml index 513be01ce90..ebe37cbe8fb 100644 --- a/addons/mail/views/res_config_settings_views.xml +++ b/addons/mail/views/res_config_settings_views.xml @@ -36,13 +36,43 @@
Configure your own email servers
-
@@ -66,22 +96,6 @@
-
Custom ICE server list diff --git a/addons/microsoft_outlook/__manifest__.py b/addons/microsoft_outlook/__manifest__.py index b1070571605..0af1221b549 100644 --- a/addons/microsoft_outlook/__manifest__.py +++ b/addons/microsoft_outlook/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Microsoft Outlook", - "version": "1.0", + "version": "1.1", "category": "Hidden", "description": "Outlook support for outgoing mail servers", "depends": [ diff --git a/addons/microsoft_outlook/models/ir_mail_server.py b/addons/microsoft_outlook/models/ir_mail_server.py index c01ecf9ab44..f28f7fbd08a 100644 --- a/addons/microsoft_outlook/models/ir_mail_server.py +++ b/addons/microsoft_outlook/models/ir_mail_server.py @@ -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) diff --git a/addons/microsoft_outlook/models/microsoft_outlook_mixin.py b/addons/microsoft_outlook/models/microsoft_outlook_mixin.py index a534cf475e5..b74afa76e77 100644 --- a/addons/microsoft_outlook/models/microsoft_outlook_mixin.py +++ b/addons/microsoft_outlook/models/microsoft_outlook_mixin.py @@ -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,14 +34,13 @@ 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() @@ -50,7 +48,7 @@ class MicrosoftOutlookMixin(models.AbstractModel): 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 @@ -80,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 { @@ -161,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, diff --git a/addons/microsoft_outlook/static/description/icon.png b/addons/microsoft_outlook/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..6c454d3f896c152fba379a71cf3995c0933c4e54 GIT binary patch literal 2928 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 O8t7>oY87bWL;eSF-db`1 literal 0 HcmV?d00001 diff --git a/addons/microsoft_outlook/static/description/icon.svg b/addons/microsoft_outlook/static/description/icon.svg new file mode 100644 index 00000000000..9408317cf0e --- /dev/null +++ b/addons/microsoft_outlook/static/description/icon.svg @@ -0,0 +1,51 @@ + \ No newline at end of file diff --git a/addons/microsoft_outlook/views/ir_mail_server_views.xml b/addons/microsoft_outlook/views/ir_mail_server_views.xml index 8870b9419e1..5fd7c5db638 100644 --- a/addons/microsoft_outlook/views/ir_mail_server_views.xml +++ b/addons/microsoft_outlook/views/ir_mail_server_views.xml @@ -5,38 +5,42 @@ ir.mail_server - - - -
-
- + Outlook Token Valid - +
+ +
+ Read More + + diff --git a/addons/microsoft_outlook/views/res_config_settings_views.xml b/addons/microsoft_outlook/views/res_config_settings_views.xml index e1adf2dc6d8..b2ad3e606e3 100644 --- a/addons/microsoft_outlook/views/res_config_settings_views.xml +++ b/addons/microsoft_outlook/views/res_config_settings_views.xml @@ -6,27 +6,18 @@ res.config.settings -
-
-
- Outlook Credentials -
- Send and receive email with your Outlook account. -
-
-
-
-
-
-
-
+
+
+
+
+
diff --git a/odoo/addons/base/models/ir_mail_server.py b/odoo/addons/base/models/ir_mail_server.py index 39fddc7f733..de7ea18eebd 100644 --- a/odoo/addons/base/models/ir_mail_server.py +++ b/odoo/addons/base/models/ir_mail_server.py @@ -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.""" diff --git a/odoo/addons/base/views/ir_mail_server_views.xml b/odoo/addons/base/views/ir_mail_server_views.xml index 8fe837d721a..553486cdd20 100644 --- a/odoo/addons/base/views/ir_mail_server_views.xml +++ b/odoo/addons/base/views/ir_mail_server_views.xml @@ -10,6 +10,8 @@ name="test_smtp_connection" class="btn-primary"/> + @@ -17,24 +19,38 @@ - + - - - - - - - - - + + + + + + + + + + + + + + + + + + + +