summaryrefslogtreecommitdiff
path: root/addons/portal/wizard
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/portal/wizard
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/portal/wizard')
-rw-r--r--addons/portal/wizard/__init__.py5
-rw-r--r--addons/portal/wizard/portal_share.py86
-rw-r--r--addons/portal/wizard/portal_share_views.xml35
-rw-r--r--addons/portal/wizard/portal_wizard.py194
-rw-r--r--addons/portal/wizard/portal_wizard_views.xml39
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>