[MERGE][IMP] portal: avoid error modal on wizard modal superposition

BEFORE THIS COMMIT :

When using the action "grant portal access" on contact, it opens a wizard.
When trying to granting access, an error is raised if email is not properly
formatted or already used by another user, in a modal. The modal of error
message is superimposed on the one of the wizard. This looks very unpleasant
and not very readable.

AFTER THIS COMMIT :

Therefore, the error messages due to an invalid email (bad format or already
used) are simplified and displayed inline in portal wizard user tree view
instead, not displaying the different action buttons for those users as long
as the error remains.

TESTS :

Tests are updated accordingly. The last assert of test_portal_wizard_error
is repaired. The previous one raised an error as expected but not the good
one. As the email had a wrong format, it was detected and error was raised,
and not because the user was internal, as supposed to. The portal user is
updated as it should be.

--- LINKS ---
Task Id - 2613780
COM PR - #74911

Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
Odoo's Mergebot
2022-08-30 15:50:56 +02:00
committed by GitHub
7 changed files with 98 additions and 33 deletions
+3
View File
@@ -32,6 +32,9 @@ a dependency towards website editing and customization capabilities.""",
'web._assets_frontend_helpers': [
('prepend', 'portal/static/src/scss/bootstrap_overridden.scss'),
],
'web.assets_backend': [
'portal/static/src/views/**/*',
],
'web.assets_frontend': [
'portal/static/src/scss/bootstrap.extend.scss',
'portal/static/src/scss/portal.scss',
@@ -0,0 +1,14 @@
/** @odoo-module **/
import { PortalWizardUserListRenderer } from "../list/portal_wizard_user_list_renderer";
import { X2ManyField } from "@web/views/fields/x2many/x2many_field";
import { registry } from "@web/core/registry";
export class PortalUserX2ManyField extends X2ManyField {
setup() {
super.setup();
this.Renderer = PortalWizardUserListRenderer;
}
}
registry.category("fields").add("portal_wizard_user_one2many", PortalUserX2ManyField);
@@ -0,0 +1,3 @@
.o_portal_wizard_user_one2many td {
width: 1%;
}
@@ -0,0 +1,23 @@
/** @odoo-module **/
import { ListRenderer } from "@web/views/list/list_renderer";
const { useSubEnv } = owl;
export class PortalWizardUserListRenderer extends ListRenderer {
setup() {
super.setup();
this.isPortalActionOngoing = false;
const defaultOnClickViewButton = this.env.onClickViewButton;
useSubEnv({
onClickViewButton: async (params) => {
if (params.clickParams.name === 'action_refresh_modal' || this.isPortalActionOngoing) {
return false;
}
this.isPortalActionOngoing = true;
await defaultOnClickViewButton(params);
this.isPortalActionOngoing = false;
},
});
}
}
+6 -3
View File
@@ -141,16 +141,19 @@ class TestPortalWizard(MailCommon):
portal_user = portal_wizard.user_ids
self.internal_user.login = 'test_error@example.com'
portal_user.email = 'test_error@example.com'
portal_user.email = 'test_error@example.com'
with self.assertRaises(UserError, msg='Must detect the already used email.'):
portal_user.action_revoke_access()
portal_user._assert_user_email_uniqueness()
self.assertEqual(portal_user.email_state, 'exist', msg='Must detect the already used email.')
portal_user.email = 'wrong email format'
with self.assertRaises(UserError, msg='Must detect wrong email format.'):
portal_user.action_revoke_access()
portal_user._assert_user_email_uniqueness()
self.assertEqual(portal_user.email_state, 'ko', msg='Must detect wrong email format.')
portal_wizard = self.env['portal.wizard'].with_context(active_ids=[self.internal_user.partner_id.id]).create({})
portal_user = portal_wizard.user_ids
with self.assertRaises(UserError, msg='Must not be able to change internal user group.'):
portal_user.action_revoke_access()
+39 -27
View File
@@ -88,6 +88,25 @@ class PortalWizardUser(models.TransientModel):
login_date = fields.Datetime(related='user_id.login_date', string='Latest Authentication')
is_portal = fields.Boolean('Is Portal', compute='_compute_group_details')
is_internal = fields.Boolean('Is Internal', compute='_compute_group_details')
email_state = fields.Selection([
('ok', 'Valid'),
('ko', 'Invalid'),
('exist', 'Already Registered')],
string='Status', compute='_compute_email_state', default='ok')
@api.depends('email')
def _compute_email_state(self):
portal_users_with_email = self.filtered(lambda user: email_normalize(user.email))
(self - portal_users_with_email).email_state = 'ko'
normalized_emails = [email_normalize(portal_user.email) for portal_user in portal_users_with_email]
existing_users = self.env['res.users'].with_context(active_test=False).sudo().search_read([('login', 'in', normalized_emails)], ['id', 'login'])
for portal_user in portal_users_with_email:
if next((user for user in existing_users if user['login'] == email_normalize(portal_user.email) and user['id'] != portal_user.user_id.id), None):
portal_user.email_state = 'exist'
else:
portal_user.email_state = 'ok'
@api.depends('partner_id')
def _compute_user_id(self):
@@ -127,10 +146,7 @@ class PortalWizardUser(models.TransientModel):
group_portal = self.env.ref('base.group_portal')
group_public = self.env.ref('base.group_public')
# update partner email, if a new one was introduced
if self.partner_id.email != self.email:
self.partner_id.write({'email': self.email})
self._update_partner_email()
user_sudo = self.user_id.sudo()
if not user_sudo:
@@ -145,7 +161,7 @@ class PortalWizardUser(models.TransientModel):
self.with_context(active_test=True)._send_email()
return self.wizard_id._action_open_modal()
return self.action_refresh_modal()
def action_revoke_access(self):
"""Remove the user of the partner from the portal group.
@@ -153,17 +169,13 @@ class PortalWizardUser(models.TransientModel):
If the user was only in the portal group, we archive it.
"""
self.ensure_one()
self._assert_user_email_uniqueness()
if not self.is_portal:
raise UserError(_('The partner "%s" has no portal access.', self.partner_id.name))
raise UserError(_('The partner "%s" has no portal access or is internal.', self.partner_id.name))
group_portal = self.env.ref('base.group_portal')
group_public = self.env.ref('base.group_public')
# update partner email, if a new one was introduced
if self.partner_id.email != self.email:
self.partner_id.write({'email': self.email})
self._update_partner_email()
# Remove the sign up token, so it can not be used
self.partner_id.sudo().signup_token = False
@@ -178,21 +190,24 @@ class PortalWizardUser(models.TransientModel):
else:
user_sudo.write({'groups_id': [(3, group_portal.id), (4, group_public.id)]})
return self.wizard_id._action_open_modal()
return self.action_refresh_modal()
def action_invite_again(self):
"""Re-send the invitation email to the partner."""
self.ensure_one()
self._assert_user_email_uniqueness()
if not self.is_portal:
raise UserError(_('You should first grant the portal access to the partner "%s".', self.partner_id.name))
# update partner email, if a new one was introduced
if self.partner_id.email != self.email:
self.partner_id.write({'email': self.email})
self._update_partner_email()
self.with_context(active_test=True)._send_email()
return self.action_refresh_modal()
def action_refresh_modal(self):
"""Refresh the portal wizard modal and keep it open. Used as fallback action of email state icon buttons,
required as they must be non-disabled buttons to fire mouse events to show tooltips on email state."""
return self.wizard_id._action_open_modal()
def _create_user(self):
@@ -229,16 +244,13 @@ class PortalWizardUser(models.TransientModel):
def _assert_user_email_uniqueness(self):
"""Check that the email can be used to create a new user."""
self.ensure_one()
email = email_normalize(self.email)
if not email:
if self.email_state == 'ko':
raise UserError(_('The contact "%s" does not have a valid email.', self.partner_id.name))
if self.email_state == 'exist':
raise UserError(_('The contact "%s" has the same email as an existing user', self.partner_id.name))
user = self.env['res.users'].sudo().with_context(active_test=False).search([
('id', '!=', self.user_id.id),
('login', '=ilike', email),
])
if user:
raise UserError(_('The contact "%s" has the same email has an existing user (%s).', self.partner_id.name, user.name))
def _update_partner_email(self):
"""Update partner email on portal action, if a new one was introduced and is valid."""
email_normalized = email_normalize(self.email)
if self.email_state == 'ok' and email_normalize(self.partner_id.email) != email_normalized:
self.partner_id.write({'email': email_normalized})
+10 -3
View File
@@ -31,19 +31,26 @@
<field name="welcome_message"
placeholder="This text is included at the end of the email sent to new portal users."
class="mb-3"/>
<field name="user_ids">
<field name="user_ids" class="o_portal_wizard_user_one2many" widget="portal_wizard_user_one2many">
<tree string="Contacts" editable="bottom" create="false" delete="false">
<field name="partner_id" force_save="1"/>
<field name="email" attrs="{'readonly': [('is_internal', '=', True)]}"/>
<field name="email_state" invisible="1"/>
<button name="action_refresh_modal" type="object" icon="fa-check text-success"
attrs="{'invisible': [('email_state', '!=', 'ok')]}" title="Valid Email Address"/>
<button name="action_refresh_modal" type="object" icon="fa-times text-danger"
attrs="{'invisible': [('email_state', '!=', 'ko')]}" title="Invalid Email Address"/>
<button name="action_refresh_modal" type="object" icon="fa-user-times text-danger"
attrs="{'invisible': [('email_state', '!=', 'exist')]}" title="Email Address already taken by another user"/>
<field name="login_date"/>
<field name="is_portal" invisible="1"/>
<field name="is_internal" invisible="1"/>
<button string="Grant Access" name="action_grant_access" type="object" class="btn-secondary"
attrs="{'invisible': ['|', ('is_portal', '=', True), ('is_internal', '=', True)]}"/>
attrs="{'invisible': ['|', '|', ('is_portal', '=', True), ('is_internal', '=', True), ('email_state', '!=', 'ok')]}"/>
<button string="Revoke Access" name="action_revoke_access" type="object" class="btn-secondary"
attrs="{'invisible': ['|', ('is_portal', '=', False), ('is_internal', '=', True)]}"/>
<button string="Re-Invite" name="action_invite_again" type="object" class="btn-secondary"
attrs="{'invisible': ['|', ('is_portal', '=', False), ('is_internal', '=', True)]}"/>
attrs="{'invisible': ['|', '|', ('is_portal', '=', False), ('is_internal', '=', True), ('email_state', '!=', 'ok')]}"/>
<button string="Internal User" attrs="{'invisible': [('is_internal', '=', False)]}"
disabled="True" title="This partner is linked to an internal User and already has access to the Portal."/>
</tree>