diff --git a/addons/portal/__openerp__.py b/addons/portal/__openerp__.py index c7d9640d291..5d3bbc8a408 100644 --- a/addons/portal/__openerp__.py +++ b/addons/portal/__openerp__.py @@ -2,7 +2,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. { - 'name' : 'Portal', + 'name': 'Portal', 'version': '1.0', 'depends': [ 'base', diff --git a/addons/portal/data/portal_data.xml b/addons/portal/data/portal_data.xml index bb38ca6156f..60da9bf2b63 100644 --- a/addons/portal/data/portal_data.xml +++ b/addons/portal/data/portal_data.xml @@ -1,5 +1,5 @@ - + @@ -40,4 +40,4 @@ ${object.wizard_id.welcome_message or ''}]]> - + diff --git a/addons/portal/data/portal_demo.xml b/addons/portal/data/portal_demo.xml index c37b0915d5c..f70cc62b698 100644 --- a/addons/portal/data/portal_demo.xml +++ b/addons/portal/data/portal_demo.xml @@ -1,5 +1,5 @@ - + @@ -31,4 +31,4 @@ Mr Demo Portal]]> - + diff --git a/addons/portal/models/res_groups.py b/addons/portal/models/res_groups.py index aa30c818259..01897204bc5 100644 --- a/addons/portal/models/res_groups.py +++ b/addons/portal/models/res_groups.py @@ -1,14 +1,13 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from openerp.osv import fields, osv +from odoo import fields, models - -class portal(osv.osv): +class PortalGroup(models.Model): """ A portal is simply a group of users with the flag 'is_portal' set to True. The flag 'is_portal' makes a user group usable as a portal. """ + _inherit = 'res.groups' - _columns = { - 'is_portal': fields.boolean('Portal', help="If checked, this group is usable as a portal."), - } + + is_portal = fields.Boolean('Portal', help="If checked, this group is usable as a portal.") diff --git a/addons/portal/tests/__init__.py b/addons/portal/tests/__init__.py index e00314d2c88..3cc7401124d 100644 --- a/addons/portal/tests/__init__.py +++ b/addons/portal/tests/__init__.py @@ -1,3 +1,4 @@ # -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. from . import test_portal diff --git a/addons/portal/tests/test_portal.py b/addons/portal/tests/test_portal.py index 8d7cb10e3a1..6878b4c61be 100644 --- a/addons/portal/tests/test_portal.py +++ b/addons/portal/tests/test_portal.py @@ -1,10 +1,11 @@ # -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. -from openerp.addons.mail.tests.common import TestMail -from openerp.tools.misc import mute_logger +from odoo.addons.mail.tests.common import TestMail +from odoo.tools.misc import mute_logger -class test_portal(TestMail): +class TestPortal(TestMail): def test_mail_compose_access_rights(self): self.group_pigs.write({'group_public_id': self.env.ref('base.group_portal').id}) diff --git a/addons/portal/views/portal_views.xml b/addons/portal/views/portal_views.xml index a30edceba76..a48cc2c68f6 100644 --- a/addons/portal/views/portal_views.xml +++ b/addons/portal/views/portal_views.xml @@ -1,7 +1,5 @@ - - - + - - + diff --git a/addons/portal/wizard/portal_wizard.py b/addons/portal/wizard/portal_wizard.py index 670b563d99f..694186c0803 100644 --- a/addons/portal/wizard/portal_wizard.py +++ b/addons/portal/wizard/portal_wizard.py @@ -3,198 +3,187 @@ import logging -from openerp.osv import fields, osv -from openerp.tools.translate import _ -from openerp.tools import email_split -from openerp import SUPERUSER_ID -from openerp.exceptions import UserError +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 wizard(osv.osv_memory): +class PortalWizard(models.TransientModel): """ A wizard to manage the creation/removal of portal users. """ + _name = 'portal.wizard' _description = 'Portal Access Management' - _columns = { - 'portal_id': fields.many2one('res.groups', domain=[('is_portal', '=', True)], required=True, - string='Portal', help="The portal that users can be added in or removed from."), - 'user_ids': fields.one2many('portal.wizard.user', 'wizard_id', string='Users'), - 'welcome_message': fields.text(string='Invitation Message', - help="This text is included in the email sent to new users of the portal."), - } + def _default_portal(self): + return self.env['res.groups'].search([('is_portal', '=', True)], limit=1) - def _default_portal(self, cr, uid, context): - portal_ids = self.pool.get('res.groups').search(cr, uid, [('is_portal', '=', True)]) - return portal_ids and portal_ids[0] or False + portal_id = fields.Many2one('res.groups', domain=[('is_portal', '=', True)], required=True, string='Portal', + default=_default_portal, help="The portal that users can be added in or removed from.") + user_ids = fields.One2many('portal.wizard.user', 'wizard_id', string='Users') + welcome_message = fields.Text('Invitation Message', help="This text is included in the email sent to new users of the portal.") - _defaults = { - 'portal_id': _default_portal, - } - - def onchange_portal_id(self, cr, uid, ids, portal_id, context=None): + @api.onchange('portal_id') + def onchange_portal_id(self): # for each partner, determine corresponding portal.wizard.user records - res_partner = self.pool.get('res.partner') - partner_ids = context and context.get('active_ids') or [] + partner_ids = self.env.context.get('active_ids', []) contact_ids = set() user_changes = [] - for partner in res_partner.browse(cr, SUPERUSER_ID, partner_ids, context): - for contact in (partner.child_ids or [partner]): + for partner in self.env['res.partner'].sudo().browse(partner_ids): + contact_partners = partner.child_ids or [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 = portal_id in [g.id for g in contact.user_ids[0].groups_id] + in_portal = self.portal_id in contact.user_ids[0].groups_id user_changes.append((0, 0, { 'partner_id': contact.id, 'email': contact.email, 'in_portal': in_portal, })) - return {'value': {'user_ids': user_changes}} + self.user_ids = user_changes - def action_apply(self, cr, uid, ids, context=None): - wizard = self.browse(cr, uid, ids[0], context) - portal_user_ids = [user.id for user in wizard.user_ids] - self.pool.get('portal.wizard.user').action_apply(cr, uid, portal_user_ids, context) + @api.multi + def action_apply(self): + self.ensure_one() + self.user_ids.action_apply() return {'type': 'ir.actions.act_window_close'} -class wizard_user(osv.osv_memory): + +class PortalWizardUser(models.TransientModel): """ A model to configure users in the portal wizard. """ + _name = 'portal.wizard.user' _description = 'Portal User Config' - _columns = { - 'wizard_id': fields.many2one('portal.wizard', string='Wizard', required=True, ondelete='cascade'), - 'partner_id': fields.many2one('res.partner', string='Contact', required=True, readonly=True), - 'email': fields.char(string='Email', size=240), - 'in_portal': fields.boolean('In Portal'), - 'user_id': fields.many2one('res.users', string='Login User'), - } + wizard_id = fields.Many2one('portal.wizard', string='Wizard', required=True, ondelete='cascade') + partner_id = fields.Many2one('res.partner', string='Contact', required=True, readonly=True) + email = fields.Char('Email') + in_portal = fields.Boolean('In Portal') + user_id = fields.Many2one('res.users', string='Login User') - def get_error_messages(self, cr, uid, ids, context=None): - res_users = self.pool.get('res.users') + @api.multi + def get_error_messages(self): emails = [] - error_empty = [] - error_emails = [] - error_user = [] - ctx = dict(context or {}, active_test=False) - for wizard_user in self.browse(cr, SUPERUSER_ID, ids, ctx): - if wizard_user.in_portal and not wizard_user.partner_id.user_ids: - email = extract_email(wizard_user.email) - if not email: - error_empty.append(wizard_user.partner_id) - elif email in emails and email not in error_emails: - error_emails.append(wizard_user.partner_id) - user = res_users.search(cr, SUPERUSER_ID, [('login', '=', email)], context=ctx) - if user: - error_user.append(wizard_user.partner_id) - emails.append(email) + partners_error_empty = self.env['res.partner'] + partners_error_emails = self.env['res.partner'] + partners_error_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', '=', email)]) + if user: + partners_error_user |= wizard_user.partner_id + emails.append(email) error_msg = [] - if error_empty: + if partners_error_empty: error_msg.append("%s\n- %s" % (_("Some contacts don't have a valid email: "), - '\n- '.join(['%s' % (p.display_name,) for p in error_empty]))) - if error_emails: + '\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([p.email for p in error_emails]))) - if error_user: + '\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(['%s <%s>' % (p.display_name, p.email) for p in error_user]))) + '\n- '.join(['%s <%s>' % (p.display_name, p.email) for p in partners_error_user]))) 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")) return error_msg - def action_apply(self, cr, uid, ids, context=None): - error_msg = self.get_error_messages(cr, uid, ids, context=context) + @api.multi + def action_apply(self): + """ 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). + """ + self.ensure_one() + error_msg = self.get_error_messages() if error_msg: - raise UserError( "\n\n".join(error_msg)) + raise UserError("\n\n".join(error_msg)) - for wizard_user in self.browse(cr, SUPERUSER_ID, ids, dict(context, active_test=False)): - portal = wizard_user.wizard_id.portal_id - user = wizard_user.partner_id.user_ids and wizard_user.partner_id.user_ids[0] or False + for wizard_user in self.sudo().with_context(active_test=False): + group_portal = wizard_user.wizard_id.portal_id + 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_id = False + user_portal = None # create a user if necessary, and make sure it is in the portal group if not user: - user_id = self._create_user(cr, SUPERUSER_ID, wizard_user.id, context) + user_portal = self.sudo()._create_user() else: - user_id = user.id - wizard_user.write({'user_id': user_id}) - if (not wizard_user.user_id.active) or (portal not in wizard_user.user_id.groups_id): - wizard_user.user_id.write({'active': True, 'groups_id': [(4, portal.id)]}) + 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() - self._send_email(cr, uid, wizard_user.id, context) + wizard_user._send_email() wizard_user.refresh() else: # remove the user (if it exists) from the portal group - if user and (portal in user.groups_id): + 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, portal.id)], 'active': False}) + user.write({'groups_id': [(3, group_portal.id)], 'active': False}) else: - user.write({'groups_id': [(3, portal.id)]}) + user.write({'groups_id': [(3, group_portal.id)]}) - def _create_user(self, cr, uid, ids, context=None): + @api.multi + def _create_user(self): """ create a new user for wizard_user.partner_id - @param wizard_user: browse record of model portal.wizard.user - @return: browse record of model res.users + :returns record of res.users """ - wizard_user = self.browse(cr, uid, ids, context=context) - res_users = self.pool.get('res.users') - create_context = dict(context or {}, noshortcut=True, no_reset_password=True) # to prevent shortcut creation - values = { - 'email': extract_email(wizard_user.email), - 'login': extract_email(wizard_user.email), - 'partner_id': wizard_user.partner_id.id, + return self.env['res.users'].with_context(no_reset_password=True).create({ + 'email': extract_email(self.email), + 'login': extract_email(self.email), + 'partner_id': self.partner_id.id, 'groups_id': [(6, 0, [])], - } - return res_users.create(cr, uid, values, context=create_context) + }) - def _send_email(self, cr, uid, ids, context=None): - """ send notification email to a new portal user - @param wizard_user: browse record of model portal.wizard.user - @return: the id of the created mail.mail record - """ - wizard_user = self.browse(cr, uid, ids, context=context) - res_partner = self.pool['res.partner'] - this_user = self.pool.get('res.users').browse(cr, SUPERUSER_ID, uid, context) - if not this_user.email: + @api.multi + 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 - user = wizard_user.user_id - context = dict(context or {}, lang=user.lang) - ctx_portal_url = dict(context, signup_force_type_in_url='') - portal_url = res_partner._get_signup_url_for_action(cr, uid, - [user.partner_id.id], - context=ctx_portal_url)[user.partner_id.id] - res_partner.signup_prepare(cr, uid, [user.partner_id.id], context=context) + 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") - context.update({'dbname': cr.dbname, 'portal_url': portal_url}) - template_id = self.pool['ir.model.data'].xmlid_to_res_id(cr, uid, 'portal.mail_template_data_portal_welcome') - if template_id: - self.pool['mail.template'].send_mail(cr, uid, template_id, wizard_user.id, force_send=True, context=context) - 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 index 53e284108c8..b1cb31e63ba 100644 --- a/addons/portal/wizard/portal_wizard_views.xml +++ b/addons/portal/wizard/portal_wizard_views.xml @@ -1,6 +1,5 @@ - - +
- +
Select which contacts should belong to the portal in the list below. @@ -40,5 +39,4 @@ - - +