diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/portal/wizard | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/portal/wizard')
| -rw-r--r-- | addons/portal/wizard/__init__.py | 5 | ||||
| -rw-r--r-- | addons/portal/wizard/portal_share.py | 86 | ||||
| -rw-r--r-- | addons/portal/wizard/portal_share_views.xml | 35 | ||||
| -rw-r--r-- | addons/portal/wizard/portal_wizard.py | 194 | ||||
| -rw-r--r-- | addons/portal/wizard/portal_wizard_views.xml | 39 |
5 files changed, 359 insertions, 0 deletions
diff --git a/addons/portal/wizard/__init__.py b/addons/portal/wizard/__init__.py new file mode 100644 index 00000000..fe113bb9 --- /dev/null +++ b/addons/portal/wizard/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import portal_share +from . import portal_wizard diff --git a/addons/portal/wizard/portal_share.py b/addons/portal/wizard/portal_share.py new file mode 100644 index 00000000..369fa1cd --- /dev/null +++ b/addons/portal/wizard/portal_share.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. +from odoo import api, fields, models, _ + + +class PortalShare(models.TransientModel): + _name = 'portal.share' + _description = 'Portal Sharing' + + @api.model + def default_get(self, fields): + result = super(PortalShare, self).default_get(fields) + result['res_model'] = self._context.get('active_model', False) + result['res_id'] = self._context.get('active_id', False) + if result['res_model'] and result['res_id']: + record = self.env[result['res_model']].browse(result['res_id']) + result['share_link'] = record.get_base_url() + record._get_share_url(redirect=True) + return result + + res_model = fields.Char('Related Document Model', required=True) + res_id = fields.Integer('Related Document ID', required=True) + partner_ids = fields.Many2many('res.partner', string="Recipients", required=True) + note = fields.Text(help="Add extra content to display in the email") + share_link = fields.Char(string="Link", compute='_compute_share_link') + access_warning = fields.Text("Access warning", compute="_compute_access_warning") + + @api.depends('res_model', 'res_id') + def _compute_share_link(self): + for rec in self: + rec.share_link = False + if rec.res_model: + res_model = self.env[rec.res_model] + if isinstance(res_model, self.pool['portal.mixin']) and rec.res_id: + record = res_model.browse(rec.res_id) + rec.share_link = record.get_base_url() + record._get_share_url(redirect=True) + + @api.depends('res_model', 'res_id') + def _compute_access_warning(self): + for rec in self: + rec.access_warning = False + if rec.res_model: + res_model = self.env[rec.res_model] + if isinstance(res_model, self.pool['portal.mixin']) and rec.res_id: + record = res_model.browse(rec.res_id) + rec.access_warning = record.access_warning + + def action_send_mail(self): + active_record = self.env[self.res_model].browse(self.res_id) + note = self.env.ref('mail.mt_note') + signup_enabled = self.env['ir.config_parameter'].sudo().get_param('auth_signup.invitation_scope') == 'b2c' + + if hasattr(active_record, 'access_token') and active_record.access_token or not signup_enabled: + partner_ids = self.partner_ids + else: + partner_ids = self.partner_ids.filtered(lambda x: x.user_ids) + # if partner already user or record has access token send common link in batch to all user + for partner in self.partner_ids: + share_link = active_record.get_base_url() + active_record._get_share_url(redirect=True, pid=partner.id) + saved_lang = self.env.lang + self = self.with_context(lang=partner.lang) + template = self.env.ref('portal.portal_share_template', False) + active_record.with_context(mail_post_autofollow=True).message_post_with_view(template, + values={'partner': partner, 'note': self.note, 'record': active_record, + 'share_link': share_link}, + subject=_("You are invited to access %s", active_record.display_name), + subtype_id=note.id, + email_layout_xmlid='mail.mail_notification_light', + partner_ids=[(6, 0, partner.ids)]) + self = self.with_context(lang=saved_lang) + # when partner not user send individual mail with signup token + for partner in self.partner_ids - partner_ids: + # prepare partner for signup and send singup url with redirect url + partner.signup_get_auth_param() + share_link = partner._get_signup_url_for_action(action='/mail/view', res_id=self.res_id, model=self.model)[partner.id] + saved_lang = self.env.lang + self = self.with_context(lang=partner.lang) + template = self.env.ref('portal.portal_share_template', False) + active_record.with_context(mail_post_autofollow=True).message_post_with_view(template, + values={'partner': partner, 'note': self.note, 'record': active_record, + 'share_link': share_link}, + subject=_("You are invited to access %s", active_record.display_name), + subtype_id=note.id, + email_layout_xmlid='mail.mail_notification_light', + partner_ids=[(6, 0, partner.ids)]) + self = self.with_context(lang=saved_lang) + return {'type': 'ir.actions.act_window_close'} diff --git a/addons/portal/wizard/portal_share_views.xml b/addons/portal/wizard/portal_share_views.xml new file mode 100644 index 00000000..75410572 --- /dev/null +++ b/addons/portal/wizard/portal_share_views.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + <record id="portal_share_wizard" model="ir.ui.view"> + <field name="name">portal.share.wizard</field> + <field name="model">portal.share</field> + <field name="arch" type="xml"> + <form string="Share Document"> + <p class="alert alert-warning" attrs="{'invisible': [('access_warning', '=', '')]}" role="alert"><field name="access_warning"/></p> + <group name="share_link"> + <field name="res_model" invisible="1"/> + <field name="res_id" invisible="1"/> + <field name="share_link" widget="CopyClipboardChar" options="{'string': 'Copy Link'}"/> + </group> + <group> + <field name="partner_ids" widget="many2many_tags_email" placeholder="Add contacts to share the document..."/> + </group> + <group> + <field name="note" placeholder="Add a note"/> + </group> + <footer> + <button string="Send" name="action_send_mail" attrs="{'invisible': [('access_warning', '!=', '')]}" type="object" class="btn-primary"/> + <button string="Cancel" class="btn-default" special="cancel" /> + </footer> + </form> + </field> + </record> + + <record id="portal_share_action" model="ir.actions.act_window"> + <field name="name">Share Document</field> + <field name="res_model">portal.share</field> + <field name="binding_model_id" ref="model_portal_share"/> + <field name="view_mode">form</field> + <field name="target">new</field> + </record> +</odoo> diff --git a/addons/portal/wizard/portal_wizard.py b/addons/portal/wizard/portal_wizard.py new file mode 100644 index 00000000..d555ba40 --- /dev/null +++ b/addons/portal/wizard/portal_wizard.py @@ -0,0 +1,194 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import logging + +from odoo.tools.translate import _ +from odoo.tools import email_split +from odoo.exceptions import UserError + +from odoo import api, fields, models + + +_logger = logging.getLogger(__name__) + +# welcome email sent to portal users +# (note that calling '_' has no effect except exporting those strings for translation) + +def extract_email(email): + """ extract the email address from a user-friendly email address """ + addresses = email_split(email) + return addresses[0] if addresses else '' + + +class PortalWizard(models.TransientModel): + """ + A wizard to manage the creation/removal of portal users. + """ + + _name = 'portal.wizard' + _description = 'Grant Portal Access' + + def _default_user_ids(self): + # for each partner, determine corresponding portal.wizard.user records + partner_ids = self.env.context.get('active_ids', []) + contact_ids = set() + user_changes = [] + for partner in self.env['res.partner'].sudo().browse(partner_ids): + contact_partners = partner.child_ids.filtered(lambda p: p.type in ('contact', 'other')) | partner + for contact in contact_partners: + # make sure that each contact appears at most once in the list + if contact.id not in contact_ids: + contact_ids.add(contact.id) + in_portal = False + if contact.user_ids: + in_portal = self.env.ref('base.group_portal') in contact.user_ids[0].groups_id + user_changes.append((0, 0, { + 'partner_id': contact.id, + 'email': contact.email, + 'in_portal': in_portal, + })) + return user_changes + + user_ids = fields.One2many('portal.wizard.user', 'wizard_id', string='Users',default=_default_user_ids) + welcome_message = fields.Text('Invitation Message', help="This text is included in the email sent to new users of the portal.") + + def action_apply(self): + self.ensure_one() + self.user_ids.action_apply() + return {'type': 'ir.actions.act_window_close'} + + +class PortalWizardUser(models.TransientModel): + """ + A model to configure users in the portal wizard. + """ + + _name = 'portal.wizard.user' + _description = 'Portal User Config' + + wizard_id = fields.Many2one('portal.wizard', string='Wizard', required=True, ondelete='cascade') + partner_id = fields.Many2one('res.partner', string='Contact', required=True, readonly=True, ondelete='cascade') + email = fields.Char('Email') + in_portal = fields.Boolean('In Portal') + user_id = fields.Many2one('res.users', string='Login User') + + def get_error_messages(self): + emails = [] + partners_error_empty = self.env['res.partner'] + partners_error_emails = self.env['res.partner'] + partners_error_user = self.env['res.partner'] + partners_error_internal_user = self.env['res.partner'] + + for wizard_user in self.with_context(active_test=False).filtered(lambda w: w.in_portal and not w.partner_id.user_ids): + email = extract_email(wizard_user.email) + if not email: + partners_error_empty |= wizard_user.partner_id + elif email in emails: + partners_error_emails |= wizard_user.partner_id + user = self.env['res.users'].sudo().with_context(active_test=False).search([('login', '=ilike', email)]) + if user: + partners_error_user |= wizard_user.partner_id + emails.append(email) + + for wizard_user in self.with_context(active_test=False): + if any(u.has_group('base.group_user') for u in wizard_user.sudo().partner_id.user_ids): + partners_error_internal_user |= wizard_user.partner_id + + error_msg = [] + if partners_error_empty: + error_msg.append("%s\n- %s" % (_("Some contacts don't have a valid email: "), + '\n- '.join(partners_error_empty.mapped('display_name')))) + if partners_error_emails: + error_msg.append("%s\n- %s" % (_("Several contacts have the same email: "), + '\n- '.join(partners_error_emails.mapped('email')))) + if partners_error_user: + error_msg.append("%s\n- %s" % (_("Some contacts have the same email as an existing portal user:"), + '\n- '.join([p.email_formatted for p in partners_error_user]))) + if partners_error_internal_user: + error_msg.append("%s\n- %s" % (_("Some contacts are already internal users:"), + '\n- '.join(partners_error_internal_user.mapped('email')))) + if error_msg: + error_msg.append(_("To resolve this error, you can: \n" + "- Correct the emails of the relevant contacts\n" + "- Grant access only to contacts with unique emails")) + error_msg[-1] += _("\n- Switch the internal users to portal manually") + return error_msg + + def action_apply(self): + self.env['res.partner'].check_access_rights('write') + """ From selected partners, add corresponding users to chosen portal group. It either granted + existing user, or create new one (and add it to the group). + """ + error_msg = self.get_error_messages() + if error_msg: + raise UserError("\n\n".join(error_msg)) + + for wizard_user in self.sudo().with_context(active_test=False): + + group_portal = self.env.ref('base.group_portal') + #Checking if the partner has a linked user + user = wizard_user.partner_id.user_ids[0] if wizard_user.partner_id.user_ids else None + # update partner email, if a new one was introduced + if wizard_user.partner_id.email != wizard_user.email: + wizard_user.partner_id.write({'email': wizard_user.email}) + # add portal group to relative user of selected partners + if wizard_user.in_portal: + user_portal = None + # create a user if necessary, and make sure it is in the portal group + if not user: + if wizard_user.partner_id.company_id: + company_id = wizard_user.partner_id.company_id.id + else: + company_id = self.env.company.id + user_portal = wizard_user.sudo().with_company(company_id)._create_user() + else: + user_portal = user + wizard_user.write({'user_id': user_portal.id}) + if not wizard_user.user_id.active or group_portal not in wizard_user.user_id.groups_id: + wizard_user.user_id.write({'active': True, 'groups_id': [(4, group_portal.id)]}) + # prepare for the signup process + wizard_user.user_id.partner_id.signup_prepare() + wizard_user.with_context(active_test=True)._send_email() + wizard_user.refresh() + else: + # remove the user (if it exists) from the portal group + if user and group_portal in user.groups_id: + # if user belongs to portal only, deactivate it + if len(user.groups_id) <= 1: + user.write({'groups_id': [(3, group_portal.id)], 'active': False}) + else: + user.write({'groups_id': [(3, group_portal.id)]}) + + def _create_user(self): + """ create a new user for wizard_user.partner_id + :returns record of res.users + """ + return self.env['res.users'].with_context(no_reset_password=True)._create_user_from_template({ + 'email': extract_email(self.email), + 'login': extract_email(self.email), + 'partner_id': self.partner_id.id, + 'company_id': self.env.company.id, + 'company_ids': [(6, 0, self.env.company.ids)], + }) + + def _send_email(self): + """ send notification email to a new portal user """ + if not self.env.user.email: + raise UserError(_('You must have an email address in your User Preferences to send emails.')) + + # determine subject and body in the portal user's language + template = self.env.ref('portal.mail_template_data_portal_welcome') + for wizard_line in self: + lang = wizard_line.user_id.lang + partner = wizard_line.user_id.partner_id + + portal_url = partner.with_context(signup_force_type_in_url='', lang=lang)._get_signup_url_for_action()[partner.id] + partner.signup_prepare() + + if template: + template.with_context(dbname=self._cr.dbname, portal_url=portal_url, lang=lang).send_mail(wizard_line.id, force_send=True) + else: + _logger.warning("No email template found for sending email to the portal user") + + return True diff --git a/addons/portal/wizard/portal_wizard_views.xml b/addons/portal/wizard/portal_wizard_views.xml new file mode 100644 index 00000000..b12af10c --- /dev/null +++ b/addons/portal/wizard/portal_wizard_views.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + <!-- wizard action on res.partner --> + <record id="partner_wizard_action" model="ir.actions.act_window"> + <field name="name">Grant portal access</field> + <field name="res_model">portal.wizard</field> + <field name="view_mode">form</field> + <field name="target">new</field> + <field name="binding_model_id" ref="base.model_res_partner"/> + </record> + + <!-- wizard view --> + <record id="wizard_view" model="ir.ui.view"> + <field name="name">Grant Portal Access</field> + <field name="model">portal.wizard</field> + <field name="arch" type="xml"> + <form string="Grant Portal Access"> + <div> + Select which contacts should belong to the portal in the list below. + The email address of each selected contact must be valid and unique. + If necessary, you can fix any contact's email address directly in the list. + </div> + <field name="user_ids"> + <tree string="Contacts" editable="bottom" create="false" delete="false"> + <field name="partner_id" force_save="1"/> + <field name="email"/> + <field name="in_portal"/> + </tree> + </field> + <field name="welcome_message" + placeholder="This text is included in the email sent to new portal users."/> + <footer> + <button string="Apply" name="action_apply" type="object" class="btn-primary"/> + <button string="Cancel" class="btn-secondary" special="cancel" /> + </footer> + </form> + </field> + </record> +</odoo> |
