diff --git a/addons/account/data/email_template_data_invoice.xml b/addons/account/data/email_template_data_invoice.xml index 87b42b829cc..b0e8dd1d175 100644 --- a/addons/account/data/email_template_data_invoice.xml +++ b/addons/account/data/email_template_data_invoice.xml @@ -19,7 +19,7 @@

Dear ${object.partner_id.name} % set access_action = object.with_context(force_website=True).get_access_action() % set is_online = access_action and access_action['type'] == 'ir.actions.act_url' -% set access_url = is_online and '/mail/view?model=account.invoice&res_id=%d' % (object.id) or '/report/pdf/account.report_invoice/' + str(object.id) +% set access_url = is_online and object.get_mail_url() or '/report/pdf/account.report_invoice/' + str(object.id) % if object.partner_id.parent_id: (${object.partner_id.parent_id.name}) diff --git a/addons/auth_oauth/models/res_users.py b/addons/auth_oauth/models/res_users.py index dd2496bdea7..dd61505b485 100644 --- a/addons/auth_oauth/models/res_users.py +++ b/addons/auth_oauth/models/res_users.py @@ -6,7 +6,7 @@ import json import requests from odoo import api, fields, models -from odoo.exceptions import AccessDenied +from odoo.exceptions import AccessDenied, UserError from odoo.addons.auth_signup.models.res_users import SignupError from odoo.addons import base @@ -82,7 +82,7 @@ class ResUsers(models.Model): try: _, login, _ = self.signup(values, token) return login - except SignupError: + except (SignupError, UserError): raise access_denied_exception @api.model diff --git a/addons/auth_signup/controllers/main.py b/addons/auth_signup/controllers/main.py index 533f84a8d82..8f8db0fab0d 100644 --- a/addons/auth_signup/controllers/main.py +++ b/addons/auth_signup/controllers/main.py @@ -6,11 +6,13 @@ import werkzeug from odoo import http, _ from odoo.addons.auth_signup.models.res_users import SignupError from odoo.addons.web.controllers.main import ensure_db, Home +from odoo.exceptions import UserError from odoo.http import request from odoo.tools import pycompat _logger = logging.getLogger(__name__) + class AuthSignupHome(Home): @http.route() @@ -33,7 +35,19 @@ class AuthSignupHome(Home): if 'error' not in qcontext and request.httprequest.method == 'POST': try: self.do_signup(qcontext) + # Send an account creation confirmation email + if qcontext.get('token'): + user_sudo = request.env['res.users'].sudo().search([('login', '=', qcontext.get('login'))]) + template = request.env.ref('auth_signup.mail_template_user_signup_account_created', raise_if_not_found=False) + if user_sudo and template: + template.sudo().with_context( + lang=user_sudo.lang, + auth_login=werkzeug.url_encode({'auth_login': user_sudo.email}), + password=request.params.get('password') + ).send_mail(user_sudo.id, force_send=True) return super(AuthSignupHome, self).web_login(*args, **kw) + except UserError as e: + qcontext['error'] = str(e) except (SignupError, AssertionError) as e: if request.env["res.users"].sudo().search([("login", "=", qcontext.get("login"))]): qcontext["error"] = _("Another user is already registered using this email address.") @@ -57,7 +71,7 @@ class AuthSignupHome(Home): return super(AuthSignupHome, self).web_login(*args, **kw) else: login = qcontext.get('login') - assert login, "No login provided." + assert login, _("No login provided.") _logger.info( "Password reset attempt for <%s> by user <%s> from %s", login, request.env.user.login, request.httprequest.remote_addr) @@ -86,6 +100,8 @@ class AuthSignupHome(Home): """ Shared helper returning the rendering context for signup and reset password """ qcontext = request.params.copy() qcontext.update(self.get_auth_signup_config()) + if not qcontext.get('token') and request.session.get('auth_signup_token'): + qcontext['token'] = request.session.get('auth_signup_token') if qcontext.get('token'): try: # retrieve the user info (name, login or email) corresponding to a signup token @@ -100,8 +116,10 @@ class AuthSignupHome(Home): def do_signup(self, qcontext): """ Shared helper that creates a res.partner out of a token """ values = { key: qcontext.get(key) for key in ('login', 'name', 'password') } - assert values, "The form was not properly filled in." - assert values.get('password') == qcontext.get('confirm_password'), "Passwords do not match; please retype them." + if not values: + raise UserError(_("The form was not properly filled in.")) + if values.get('password') != qcontext.get('confirm_password'): + raise UserError(_("Passwords do not match; please retype them.")) supported_langs = [lang['code'] for lang in request.env['res.lang'].sudo().search_read([], ['code'])] if request.lang in supported_langs: values['lang'] = request.lang diff --git a/addons/auth_signup/data/auth_signup_data.xml b/addons/auth_signup/data/auth_signup_data.xml index 2b6ceeeed79..b9f88893d2a 100644 --- a/addons/auth_signup/data/auth_signup_data.xml +++ b/addons/auth_signup/data/auth_signup_data.xml @@ -91,5 +91,49 @@ + + + Auth Signup: Odoo Account Created + + "${object.company_id.name|safe}" <${(object.company_id.email or user.email)|safe}> + ${object.email|safe} + Welcome to ${object.company_id.name}! + +

+ + + + +
+ ${user.company_id.name} +
+
+
+

Dear ${object.name},

+

+ Your account has been successfully created! +

+

+ Your login: ${object.email} +
+ Your password: ${ctx['password']} +

+

+ To gain access to your account, you can use the following link: +

+
+ Go to My Account +
+

Best regards,

+
+
+ ${user.signature | safe} +

+ Sent by ${user.company_id.name} using Odoo +

+
+ + + diff --git a/addons/auth_signup/models/base_config_settings.py b/addons/auth_signup/models/base_config_settings.py index eb830c24e37..8ea0a8f2041 100644 --- a/addons/auth_signup/models/base_config_settings.py +++ b/addons/auth_signup/models/base_config_settings.py @@ -2,24 +2,27 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo import api, fields, models -from odoo.tools.safe_eval import safe_eval + class BaseConfigSettings(models.TransientModel): _inherit = 'base.config.settings' auth_signup_reset_password = fields.Boolean(string='Enable password reset from Login page') - auth_signup_uninvited = fields.Boolean(string='Allow external users to sign up') + auth_signup_uninvited = fields.Selection([ + ('b2b', 'On invitation (B2B)'), + ('b2c', 'Free sign up (B2C)'), + ], string='Customer Account') auth_signup_template_user_id = fields.Many2one('res.users', string='Template user for new users created through signup') @api.model def get_values(self): res = super(BaseConfigSettings, self).get_values() get_param = self.env['ir.config_parameter'].sudo().get_param - # we use safe_eval on the result, since the value of the parameter is a nonempty string + # the value of the parameter is a nonempty string res.update( - auth_signup_reset_password=safe_eval(get_param('auth_signup.reset_password', 'False')), - auth_signup_uninvited=safe_eval(get_param('auth_signup.allow_uninvited', 'False')), - auth_signup_template_user_id=safe_eval(get_param('auth_signup.template_user_id', 'False')), + auth_signup_reset_password=get_param('auth_signup.reset_password', 'False').lower() == 'true', + auth_signup_uninvited='b2c' if get_param('auth_signup.allow_uninvited', 'False').lower() == 'true' else 'b2b', + auth_signup_template_user_id=get_param('auth_signup.template_user_id', 'False').lower() == 'true', ) return res @@ -29,7 +32,7 @@ class BaseConfigSettings(models.TransientModel): set_param = self.env['ir.config_parameter'].sudo().set_param # we store the repr of the values, since the value of the parameter is a required string set_param('auth_signup.reset_password', repr(self.auth_signup_reset_password)) - set_param('auth_signup.allow_uninvited', repr(self.auth_signup_uninvited)) + set_param('auth_signup.allow_uninvited', repr(self.auth_signup_uninvited == 'b2c')) set_param('auth_signup.template_user_id', repr(self.auth_signup_template_user_id.id)) @api.multi diff --git a/addons/auth_signup/models/res_partner.py b/addons/auth_signup/models/res_partner.py index d8111420a3c..302d69f173e 100644 --- a/addons/auth_signup/models/res_partner.py +++ b/addons/auth_signup/models/res_partner.py @@ -4,9 +4,10 @@ import random import werkzeug.urls +from collections import defaultdict from datetime import datetime, timedelta -from odoo import api, fields, models, _ +from odoo import api, exceptions, fields, models, _ from odoo.tools import pycompat @@ -97,6 +98,21 @@ class ResPartner(models.Model): def action_signup_prepare(self): return self.signup_prepare() + def signup_get_auth_param(self): + """ Get a signup token related to the partner if signup is enabled. + If the partner already has a user, get the login parameter. + """ + res = defaultdict(dict) + + allow_signup = self.env['ir.config_parameter'].get_param('auth_signup.allow_uninvited', 'False').lower() == 'true' + for partner in self: + if allow_signup and not partner.user_ids: + partner.signup_prepare() + res[partner.id]['auth_signup_token'] = partner.signup_token + elif partner.user_ids: + res[partner.id]['auth_login'] = partner.user_ids[0].login + return res + @api.multi def signup_cancel(self): return self.write({'signup_token': False, 'signup_type': False, 'signup_expiration': False}) @@ -125,11 +141,11 @@ class ResPartner(models.Model): partner = self.search([('signup_token', '=', token)], limit=1) if not partner: if raise_exception: - raise SignupError("Signup token '%s' is not valid" % token) + raise exceptions.UserError(_("Signup token '%s' is not valid") % token) return False if check_validity and not partner.signup_valid: if raise_exception: - raise SignupError("Signup token '%s' is no longer valid" % token) + raise exceptions.UserError(_("Signup token '%s' is no longer valid") % token) return False return partner diff --git a/addons/auth_signup/models/res_users.py b/addons/auth_signup/models/res_users.py index 05d52596c85..b58606b73ab 100644 --- a/addons/auth_signup/models/res_users.py +++ b/addons/auth_signup/models/res_users.py @@ -85,7 +85,7 @@ class ResUsers(models.Model): # check that uninvited users may sign up if 'partner_id' not in values: if not literal_eval(get_param('auth_signup.allow_uninvited', 'False')): - raise SignupError('Signup is not allowed for uninvited users') + raise SignupError(_('Signup is not allowed for uninvited users')) assert values.get('login'), "Signup: no login given for new user" assert values.get('partner_id') or values.get('name'), "Signup: no name or partner given for new user" diff --git a/addons/auth_signup/views/auth_signup_login_templates.xml b/addons/auth_signup/views/auth_signup_login_templates.xml index b3e17efac90..a38596abe9c 100644 --- a/addons/auth_signup/views/auth_signup_login_templates.xml +++ b/addons/auth_signup/views/auth_signup_login_templates.xml @@ -2,7 +2,7 @@ @@ -40,7 +40,7 @@ - +

@@ -49,7 +49,7 @@

diff --git a/addons/auth_signup/views/base_config_settings_views.xml b/addons/auth_signup/views/base_config_settings_views.xml index 23c2b1f7608..aaae5a8ea22 100644 --- a/addons/auth_signup/views/base_config_settings_views.xml +++ b/addons/auth_signup/views/base_config_settings_views.xml @@ -9,14 +9,16 @@
-
-
+
+
+
+
+
+

Social Media

diff --git a/addons/website_account/models/account_invoice.py b/addons/website_account/models/account_invoice.py index 088131d83d5..3c2c086f454 100644 --- a/addons/website_account/models/account_invoice.py +++ b/addons/website_account/models/account_invoice.py @@ -2,6 +2,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo import api, exceptions, models +from werkzeug.urls import url_encode class AccountInvoice(models.Model): @@ -17,22 +18,36 @@ class AccountInvoice(models.Model): return groups @api.multi - def get_access_action(self): + def get_access_action(self, access_uid=None): """ Instead of the classic form view, redirect to the online invoice for portal users. """ self.ensure_one() - if self.env.user.share or self.env.context.get('force_website'): + user, record = self.env.user, self + if access_uid: + user = self.env['res.users'].sudo().browse(access_uid) + record = self.sudo(user) + + if user.share or self.env.context.get('force_website'): try: - self.check_access_rule('read') + record.check_access_rule('read') except exceptions.AccessError: pass else: return { 'type': 'ir.actions.act_url', - 'url': '/my/invoices', # No controller /my/invoices/, only a report pdf + 'url': '/my/invoices?', # No controller /my/invoices/, only a report pdf 'target': 'self', 'res_id': self.id, } - return super(AccountInvoice, self).get_access_action() + return super(AccountInvoice, self).get_access_action(access_uid) + + def get_mail_url(self): + self.ensure_one() + params = { + 'model': self._name, + 'res_id': self.id, + } + params.update(self.partner_id.signup_get_auth_param()[self.partner_id.id]) + return '/mail/view?' + url_encode(params) @api.multi def get_signup_url(self): diff --git a/addons/website_account/views/account_templates.xml b/addons/website_account/views/account_templates.xml index ad9cd8c46b6..0d0a1b98daa 100644 --- a/addons/website_account/views/account_templates.xml +++ b/addons/website_account/views/account_templates.xml @@ -1,19 +1,17 @@