diff --git a/addons/auth_oauth/res_users.py b/addons/auth_oauth/res_users.py
index 7b3e1a3a0d5..4d6dcd356c1 100644
--- a/addons/auth_oauth/res_users.py
+++ b/addons/auth_oauth/res_users.py
@@ -6,7 +6,7 @@ import urllib2
import json
import openerp
-from openerp.addons.auth_signup.res_users import SignupError
+from openerp.addons.auth_signup.models.res_partner import SignupError
from openerp.osv import osv, fields
from openerp import SUPERUSER_ID
diff --git a/addons/auth_signup/__init__.py b/addons/auth_signup/__init__.py
index 620b7d866b1..ac0011716f6 100644
--- a/addons/auth_signup/__init__.py
+++ b/addons/auth_signup/__init__.py
@@ -2,5 +2,4 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import controllers
-import res_config
-import res_users
+import models
diff --git a/addons/auth_signup/__openerp__.py b/addons/auth_signup/__openerp__.py
index a79d6690cda..31920e62cac 100644
--- a/addons/auth_signup/__openerp__.py
+++ b/addons/auth_signup/__openerp__.py
@@ -7,9 +7,7 @@
Allow users to sign up and reset their password
===============================================
""",
- 'version': '1.0',
'category': 'Authentication',
- 'installable': True,
'auto_install': True,
'depends': [
'base_setup',
@@ -17,10 +15,10 @@ Allow users to sign up and reset their password
'web',
],
'data': [
- 'auth_signup_data.xml',
- 'res_config.xml',
- 'res_users_view.xml',
- 'views/auth_signup_login.xml',
+ 'data/auth_signup_data.xml',
+ 'views/res_config_views.xml',
+ 'views/res_users_views.xml',
+ 'views/auth_signup_login_templates.xml',
],
'bootstrap': True,
}
diff --git a/addons/auth_signup/controllers/__init__.py b/addons/auth_signup/controllers/__init__.py
index 8ee9bae18d9..bbd183e955b 100644
--- a/addons/auth_signup/controllers/__init__.py
+++ b/addons/auth_signup/controllers/__init__.py
@@ -1 +1,3 @@
+# -*- coding: utf-8 -*-
+
import main
diff --git a/addons/auth_signup/controllers/main.py b/addons/auth_signup/controllers/main.py
index 9035a650809..9ebd4e657b5 100644
--- a/addons/auth_signup/controllers/main.py
+++ b/addons/auth_signup/controllers/main.py
@@ -3,16 +3,14 @@
import logging
import werkzeug
-import openerp
-from openerp.addons.auth_signup.res_users import SignupError
-from openerp.addons.web.controllers.main import ensure_db
-from openerp import http
-from openerp.http import request
-from openerp.tools.translate import _
+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.http import request
_logger = logging.getLogger(__name__)
-class AuthSignupHome(openerp.addons.web.controllers.main.Home):
+class AuthSignupHome(Home):
@http.route()
def web_login(self, *args, **kw):
@@ -55,8 +53,7 @@ class AuthSignupHome(openerp.addons.web.controllers.main.Home):
else:
login = qcontext.get('login')
assert login, "No login provided."
- res_users = request.registry.get('res.users')
- res_users.reset_password(request.cr, openerp.SUPERUSER_ID, login)
+ request.env['res.users'].sudo().reset_password(login)
qcontext['message'] = _("An email has been sent with credentials to reset your password")
except SignupError:
qcontext['error'] = _("Could not reset your password")
@@ -70,10 +67,10 @@ class AuthSignupHome(openerp.addons.web.controllers.main.Home):
def get_auth_signup_config(self):
"""retrieve the module config (which features are enabled) for the login page"""
- icp = request.registry.get('ir.config_parameter')
+ IrConfigParam = request.env['ir.config_parameter']
return {
- 'signup_enabled': icp.get_param(request.cr, openerp.SUPERUSER_ID, 'auth_signup.allow_uninvited') == 'True',
- 'reset_password_enabled': icp.get_param(request.cr, openerp.SUPERUSER_ID, 'auth_signup.reset_password') == 'True',
+ 'signup_enabled': IrConfigParam.sudo().get_param('auth_signup.allow_uninvited') == 'True',
+ 'reset_password_enabled': IrConfigParam.sudo().get_param('auth_signup.reset_password') == 'True',
}
def get_auth_signup_qcontext(self):
@@ -83,8 +80,7 @@ class AuthSignupHome(openerp.addons.web.controllers.main.Home):
if qcontext.get('token'):
try:
# retrieve the user info (name, login or email) corresponding to a signup token
- res_partner = request.registry.get('res.partner')
- token_infos = res_partner.signup_retrieve_info(request.cr, openerp.SUPERUSER_ID, qcontext.get('token'))
+ token_infos = request.env['res.partner'].sudo().signup_retrieve_info(qcontext.get('token'))
for k, v in token_infos.items():
qcontext.setdefault(k, v)
except:
@@ -94,18 +90,18 @@ class AuthSignupHome(openerp.addons.web.controllers.main.Home):
def do_signup(self, qcontext):
""" Shared helper that creates a res.partner out of a token """
- values = dict((key, qcontext.get(key)) for key in ('login', 'name', 'password'))
- assert any([k for k in values.values()]), "The form was not properly filled in."
+ values = { key: qcontext.get(key) for key in ('login', 'name', 'password') }
+ assert values.values(), "The form was not properly filled in."
assert values.get('password') == qcontext.get('confirm_password'), "Passwords do not match; please retype them."
- supported_langs = [lang['code'] for lang in request.registry['res.lang'].search_read(request.cr, openerp.SUPERUSER_ID, [], ['code'])]
+ 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
self._signup_with_values(qcontext.get('token'), values)
- request.cr.commit()
+ request.env.cr.commit()
def _signup_with_values(self, token, values):
- db, login, password = request.registry['res.users'].signup(request.cr, openerp.SUPERUSER_ID, values, token)
- request.cr.commit() # as authenticate will use its own cursor we need to commit the current transaction
+ db, login, password = request.env['res.users'].sudo().signup(values, token)
+ request.env.cr.commit() # as authenticate will use its own cursor we need to commit the current transaction
uid = request.session.authenticate(db, login, password)
if not uid:
raise SignupError(_('Authentication Failed.'))
diff --git a/addons/auth_signup/auth_signup_data.xml b/addons/auth_signup/data/auth_signup_data.xml
similarity index 98%
rename from addons/auth_signup/auth_signup_data.xml
rename to addons/auth_signup/data/auth_signup_data.xml
index 98d1902cfc1..c8ded45916e 100644
--- a/addons/auth_signup/auth_signup_data.xml
+++ b/addons/auth_signup/data/auth_signup_data.xml
@@ -1,5 +1,5 @@
-
-
+
+
@@ -94,4 +94,4 @@
-
+
diff --git a/addons/auth_signup/models/__init__.py b/addons/auth_signup/models/__init__.py
new file mode 100644
index 00000000000..62ce1aa437e
--- /dev/null
+++ b/addons/auth_signup/models/__init__.py
@@ -0,0 +1,5 @@
+# -*- coding: utf-8 -*-
+
+import res_config
+import res_users
+import res_partner
diff --git a/addons/auth_signup/models/res_config.py b/addons/auth_signup/models/res_config.py
new file mode 100644
index 00000000000..bfb89e76d3d
--- /dev/null
+++ b/addons/auth_signup/models/res_config.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+# 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', help="This allows users to trigger a password reset from the Login page.")
+ auth_signup_uninvited = fields.Boolean(string='Allow external users to sign up', help="If unchecked, only invited users may sign up.")
+ auth_signup_template_user_id = fields.Many2one('res.users', string='Template user for new users created through signup')
+
+ @api.model
+ def get_default_auth_signup_template_user_id(self, fields):
+ IrConfigParam = self.env['ir.config_parameter']
+ # we use safe_eval on the result, since the value of the parameter is a nonempty string
+ return {
+ 'auth_signup_reset_password': safe_eval(IrConfigParam.get_param('auth_signup.reset_password', 'False')),
+ 'auth_signup_uninvited': safe_eval(IrConfigParam.get_param('auth_signup.allow_uninvited', 'False')),
+ 'auth_signup_template_user_id': safe_eval(IrConfigParam.get_param('auth_signup.template_user_id', 'False')),
+ }
+
+ @api.multi
+ def set_auth_signup_template_user_id(self):
+ self.ensure_one()
+ IrConfigParam = self.env['ir.config_parameter']
+ # we store the repr of the values, since the value of the parameter is a required string
+ IrConfigParam.set_param('auth_signup.reset_password', repr(self.auth_signup_reset_password))
+ IrConfigParam.set_param('auth_signup.allow_uninvited', repr(self.auth_signup_uninvited))
+ IrConfigParam.set_param('auth_signup.template_user_id', repr(self.auth_signup_template_user_id.id))
diff --git a/addons/auth_signup/models/res_partner.py b/addons/auth_signup/models/res_partner.py
new file mode 100644
index 00000000000..8f20b722167
--- /dev/null
+++ b/addons/auth_signup/models/res_partner.py
@@ -0,0 +1,152 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+import random
+import werkzeug
+
+from datetime import datetime, timedelta
+from urlparse import urljoin
+
+from odoo import api, fields, models, _
+
+
+class SignupError(Exception):
+ pass
+
+def random_token():
+ # the token has an entropy of about 120 bits (6 bits/char * 20 chars)
+ chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
+ return ''.join(random.SystemRandom().choice(chars) for i in xrange(20))
+
+def now(**kwargs):
+ dt = datetime.now() + timedelta(**kwargs)
+ return fields.Datetime.to_string(dt)
+
+
+class ResPartner(models.Model):
+ _inherit = 'res.partner'
+
+ signup_token = fields.Char(copy=False)
+ signup_type = fields.Char(string='Signup Token Type', copy=False)
+ signup_expiration = fields.Datetime(copy=False)
+ signup_valid = fields.Boolean(compute='_compute_signup_valid', string='Signup Token is Valid')
+ signup_url = fields.Char(compute='_compute_signup_url', string='Signup URL')
+
+ @api.multi
+ def _compute_signup_valid(self):
+ dt = now()
+ for partner in self:
+ partner.signup_valid = bool(partner.signup_token) and \
+ (not partner.signup_expiration or dt <= partner.signup_expiration)
+
+ @api.multi
+ def _compute_signup_url(self):
+ """ proxy for function field towards actual implementation """
+ result = self._get_signup_url_for_action()
+ for partner in self:
+ partner.signup_url = result.get(partner.id, False)
+
+ @api.multi
+ def _get_signup_url_for_action(self, action=None, view_type=None, menu_id=None, res_id=None, model=None):
+ """ generate a signup url for the given partner ids and action, possibly overriding
+ the url state components (menu_id, id, view_type) """
+
+ res = dict.fromkeys(self.ids, False)
+ base_url = self.env['ir.config_parameter'].get_param('web.base.url')
+ for partner in self:
+ # when required, make sure the partner has a valid signup token
+ if self.env.context.get('signup_valid') and not partner.user_ids:
+ partner.signup_prepare()
+
+ route = 'login'
+ # the parameters to encode for the query
+ query = dict(db=self.env.cr.dbname)
+ signup_type = self.env.context.get('signup_force_type_in_url', partner.signup_type or '')
+ if signup_type:
+ route = 'reset_password' if signup_type == 'reset' else signup_type
+
+ if partner.signup_token and signup_type:
+ query['token'] = partner.signup_token
+ elif partner.user_ids:
+ query['login'] = partner.user_ids[0].login
+ else:
+ continue # no signup token, no user, thus no signup url!
+
+ fragment = dict()
+ if action:
+ fragment['action'] = action
+ if view_type:
+ fragment['view_type'] = view_type
+ if menu_id:
+ fragment['menu_id'] = menu_id
+ if model:
+ fragment['model'] = model
+ if res_id:
+ fragment['id'] = res_id
+
+ if fragment:
+ query['redirect'] = '/web#' + werkzeug.url_encode(fragment)
+
+ res[partner.id] = urljoin(base_url, "/web/%s?%s" % (route, werkzeug.url_encode(query)))
+ return res
+
+ @api.multi
+ def action_signup_prepare(self):
+ return self.signup_prepare()
+
+ @api.multi
+ def signup_cancel(self):
+ return self.write({'signup_token': False, 'signup_type': False, 'signup_expiration': False})
+
+ @api.multi
+ def signup_prepare(self, signup_type="signup", expiration=False):
+ """ generate a new token for the partners with the given validity, if necessary
+ :param expiration: the expiration datetime of the token (string, optional)
+ """
+ for partner in self:
+ if expiration or not partner.signup_valid:
+ token = random_token()
+ while self._signup_retrieve_partner(token):
+ token = random_token()
+ partner.write({'signup_token': token, 'signup_type': signup_type, 'signup_expiration': expiration})
+ return True
+
+ @api.model
+ def _signup_retrieve_partner(self, token, check_validity=False, raise_exception=False):
+ """ find the partner corresponding to a token, and possibly check its validity
+ :param token: the token to resolve
+ :param check_validity: if True, also check validity
+ :param raise_exception: if True, raise exception instead of returning False
+ :return: partner (browse record) or False (if raise_exception is False)
+ """
+ partner = self.search([('signup_token', '=', token)], limit=1)
+ if not partner:
+ if raise_exception:
+ raise SignupError("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)
+ return False
+ return partner
+
+ @api.model
+ def signup_retrieve_info(self, token):
+ """ retrieve the user info about the token
+ :return: a dictionary with the user information:
+ - 'db': the name of the database
+ - 'token': the token, if token is valid
+ - 'name': the name of the partner, if token is valid
+ - 'login': the user login, if the user already exists
+ - 'email': the partner email, if the user does not exist
+ """
+ partner = self._signup_retrieve_partner(token, raise_exception=True)
+ res = {'db': self.env.cr.dbname}
+ if partner.signup_valid:
+ res['token'] = token
+ res['name'] = partner.name
+ if partner.user_ids:
+ res['login'] = partner.user_ids[0].login
+ else:
+ res['email'] = partner.email or ''
+ return res
diff --git a/addons/auth_signup/models/res_users.py b/addons/auth_signup/models/res_users.py
new file mode 100644
index 00000000000..bb87389b5a0
--- /dev/null
+++ b/addons/auth_signup/models/res_users.py
@@ -0,0 +1,156 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+
+from ast import literal_eval
+
+from odoo import api, fields, models, _
+from odoo.exceptions import UserError
+from odoo.tools.misc import ustr
+
+from odoo.addons.base.ir.ir_mail_server import MailDeliveryException
+from odoo.addons.auth_signup.models.res_partner import SignupError, now
+
+
+class ResUsers(models.Model):
+ _inherit = 'res.users'
+
+ state = fields.Selection(compute='_compute_state', string='Status',
+ selection=[('new', 'Never Connected'), ('active', 'Activated')])
+
+ @api.multi
+ def _compute_state(self):
+ for user in self:
+ user.state = 'active' if user.login_date else 'new'
+
+ @api.model
+ def signup(self, values, token=None):
+ """ signup a user, to either:
+ - create a new user (no token), or
+ - create a user for a partner (with token, but no user for partner), or
+ - change the password of a user (with token, and existing user).
+ :param values: a dictionary with field values that are written on user
+ :param token: signup token (optional)
+ :return: (dbname, login, password) for the signed up user
+ """
+ if token:
+ # signup with a token: find the corresponding partner id
+ partner = self.env['res.partner']._signup_retrieve_partner(token, check_validity=True, raise_exception=True)
+ # invalidate signup token
+ partner.write({'signup_token': False, 'signup_type': False, 'signup_expiration': False})
+
+ partner_user = partner.user_ids and partner.user_ids[0] or False
+
+ # avoid overwriting existing (presumably correct) values with geolocation data
+ if partner.country_id or partner.zip or partner.city:
+ values.pop('city', None)
+ values.pop('country_id', None)
+ if partner.lang:
+ values.pop('lang', None)
+
+ if partner_user:
+ # user exists, modify it according to values
+ values.pop('login', None)
+ values.pop('name', None)
+ partner_user.write(values)
+ return (self.env.cr.dbname, partner_user.login, values.get('password'))
+ else:
+ # user does not exist: sign up invited user
+ values.update({
+ 'name': partner.name,
+ 'partner_id': partner.id,
+ 'email': values.get('email') or values.get('login'),
+ })
+ if partner.company_id:
+ values['company_id'] = partner.company_id.id
+ values['company_ids'] = [(6, 0, [partner.company_id.id])]
+ self._signup_create_user(values)
+ else:
+ # no token, sign up an external user
+ values['email'] = values.get('email') or values.get('login')
+ self._signup_create_user(values)
+
+ return (self.env.cr.dbname, values.get('login'), values.get('password'))
+
+ @api.model
+ def _signup_create_user(self, values):
+ """ create a new user from the template user """
+ IrConfigParam = self.env['ir.config_parameter']
+ template_user_id = literal_eval(IrConfigParam.get_param('auth_signup.template_user_id', 'False'))
+ template_user = self.browse(template_user_id)
+ assert template_user.exists(), 'Signup: invalid template user'
+
+ # check that uninvited users may sign up
+ if 'partner_id' not in values:
+ if not literal_eval(IrConfigParam.get_param('auth_signup.allow_uninvited', 'False')):
+ 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"
+
+ # create a copy of the template user (attached to a specific partner_id if given)
+ values['active'] = True
+ try:
+ with self.env.cr.savepoint():
+ return template_user.with_context(no_reset_password=True).copy(values)
+ except Exception, e:
+ # copy may failed if asked login is not available.
+ raise SignupError(ustr(e))
+
+ def reset_password(self, login):
+ """ retrieve the user corresponding to login (login or email),
+ and reset their password
+ """
+ users = self.search([('login', '=', login)])
+ if not users:
+ users = self.search([('email', '=', login)])
+ if len(users) != 1:
+ raise Exception(_('Reset password: invalid username or email'))
+ return users.action_reset_password()
+
+ @api.multi
+ def action_reset_password(self):
+ """ create signup token for each user, and send their signup url by email """
+ # prepare reset password signup
+ create_mode = bool(self.env.context.get('create_user'))
+
+ # no time limit for initial invitation, only for reset password
+ expiration = False if create_mode else now(days=+1)
+
+ self.mapped('partner_id').signup_prepare(signup_type="reset", expiration=expiration)
+
+ # send email to users with their signup url
+ template = False
+ if create_mode:
+ try:
+ template = self.env.ref('auth_signup.set_password_email', raise_if_not_found=False)
+ except ValueError:
+ pass
+ if not template:
+ template = self.env.ref('auth_signup.reset_password_email')
+ assert template._name == 'mail.template'
+
+ for user in self:
+ if not user.email:
+ raise UserError(_("Cannot send email: user %s has no email address.") % user.name)
+ template.with_context(lang=user.lang).send_mail(user.id, force_send=True, raise_exception=True)
+
+ @api.model
+ def create(self, values):
+ # overridden to automatically invite user to sign up
+ user = super(ResUsers, self).create(values)
+ if user.email and not self.env.context.get('no_reset_password'):
+ try:
+ user.with_context(create_user=True).action_reset_password()
+ except MailDeliveryException:
+ user.partner_id.with_context(create_user=True).signup_cancel()
+ return user
+
+ @api.multi
+ def copy(self, default=None):
+ self.ensure_one()
+ context = self.env.context
+ if not default or not default.get('email'):
+ # avoid sending email to the user we are duplicating
+ context['reset_password'] = False
+ return super(ResUsers, self.with_context(context)).copy(default=default)
diff --git a/addons/auth_signup/res_config.py b/addons/auth_signup/res_config.py
deleted file mode 100644
index b2852a95b46..00000000000
--- a/addons/auth_signup/res_config.py
+++ /dev/null
@@ -1,34 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-from openerp.osv import osv, fields
-from openerp.tools.safe_eval import safe_eval
-
-class base_config_settings(osv.TransientModel):
- _inherit = 'base.config.settings'
-
- _columns = {
- 'auth_signup_reset_password': fields.boolean('Enable password reset from Login page',
- help="This allows users to trigger a password reset from the Login page."),
- 'auth_signup_uninvited': fields.boolean('Allow external users to sign up',
- help="If unchecked, only invited users may sign up."),
- 'auth_signup_template_user_id': fields.many2one('res.users',
- string='Template user for new users created through signup'),
- }
-
- def get_default_auth_signup_template_user_id(self, cr, uid, fields, context=None):
- icp = self.pool.get('ir.config_parameter')
- # we use safe_eval on the result, since the value of the parameter is a nonempty string
- return {
- 'auth_signup_reset_password': safe_eval(icp.get_param(cr, uid, 'auth_signup.reset_password', 'False')),
- 'auth_signup_uninvited': safe_eval(icp.get_param(cr, uid, 'auth_signup.allow_uninvited', 'False')),
- 'auth_signup_template_user_id': safe_eval(icp.get_param(cr, uid, 'auth_signup.template_user_id', 'False')),
- }
-
- def set_auth_signup_template_user_id(self, cr, uid, ids, context=None):
- config = self.browse(cr, uid, ids[0], context=context)
- icp = self.pool.get('ir.config_parameter')
- # we store the repr of the values, since the value of the parameter is a required string
- icp.set_param(cr, uid, 'auth_signup.reset_password', repr(config.auth_signup_reset_password))
- icp.set_param(cr, uid, 'auth_signup.allow_uninvited', repr(config.auth_signup_uninvited))
- icp.set_param(cr, uid, 'auth_signup.template_user_id', repr(config.auth_signup_template_user_id.id))
diff --git a/addons/auth_signup/res_users.py b/addons/auth_signup/res_users.py
deleted file mode 100644
index 92ebcb093b0..00000000000
--- a/addons/auth_signup/res_users.py
+++ /dev/null
@@ -1,306 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from datetime import datetime, timedelta
-import random
-from urlparse import urljoin
-import werkzeug
-
-from openerp.addons.base.ir.ir_mail_server import MailDeliveryException
-from openerp.osv import osv, fields
-from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT, ustr
-from ast import literal_eval
-from openerp.tools.translate import _
-from openerp.exceptions import UserError
-
-class SignupError(Exception):
- pass
-
-def random_token():
- # the token has an entropy of about 120 bits (6 bits/char * 20 chars)
- chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
- return ''.join(random.SystemRandom().choice(chars) for i in xrange(20))
-
-def now(**kwargs):
- dt = datetime.now() + timedelta(**kwargs)
- return dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
-
-
-class res_partner(osv.Model):
- _inherit = 'res.partner'
-
- def _get_signup_valid(self, cr, uid, ids, name, arg, context=None):
- dt = now()
- res = {}
- for partner in self.browse(cr, uid, ids, context):
- res[partner.id] = bool(partner.signup_token) and \
- (not partner.signup_expiration or dt <= partner.signup_expiration)
- return res
-
- def _get_signup_url_for_action(self, cr, uid, ids, action=None, view_type=None, menu_id=None, res_id=None, model=None, context=None):
- """ generate a signup url for the given partner ids and action, possibly overriding
- the url state components (menu_id, id, view_type) """
- if context is None:
- context= {}
- res = dict.fromkeys(ids, False)
- base_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url')
- for partner in self.browse(cr, uid, ids, context):
- # when required, make sure the partner has a valid signup token
- if context.get('signup_valid') and not partner.user_ids:
- self.signup_prepare(cr, uid, [partner.id], context=context)
-
- route = 'login'
- # the parameters to encode for the query
- query = dict(db=cr.dbname)
- signup_type = context.get('signup_force_type_in_url', partner.signup_type or '')
- if signup_type:
- route = 'reset_password' if signup_type == 'reset' else signup_type
-
- if partner.signup_token and signup_type:
- query['token'] = partner.signup_token
- elif partner.user_ids:
- query['login'] = partner.user_ids[0].login
- else:
- continue # no signup token, no user, thus no signup url!
-
- fragment = dict()
- base = '/web#'
- if action == '/mail/view':
- base = '/mail/view?'
- elif action:
- fragment['action'] = action
- if view_type:
- fragment['view_type'] = view_type
- if menu_id:
- fragment['menu_id'] = menu_id
- if model:
- fragment['model'] = model
- if res_id:
- fragment['res_id'] = res_id
-
- if fragment:
- query['redirect'] = base + werkzeug.url_encode(fragment)
-
- res[partner.id] = urljoin(base_url, "/web/%s?%s" % (route, werkzeug.url_encode(query)))
-
- return res
-
- def _get_signup_url(self, cr, uid, ids, name, arg, context=None):
- """ proxy for function field towards actual implementation """
- return self._get_signup_url_for_action(cr, uid, ids, context=context)
-
- _columns = {
- 'signup_token': fields.char('Signup Token', copy=False),
- 'signup_type': fields.char('Signup Token Type', copy=False),
- 'signup_expiration': fields.datetime('Signup Expiration', copy=False),
- 'signup_valid': fields.function(_get_signup_valid, type='boolean', string='Signup Token is Valid'),
- 'signup_url': fields.function(_get_signup_url, type='char', string='Signup URL'),
- }
-
- def action_signup_prepare(self, cr, uid, ids, context=None):
- return self.signup_prepare(cr, uid, ids, context=context)
-
- def signup_cancel(self, cr, uid, ids, context=None):
- return self.write(cr, uid, ids, {'signup_token': False, 'signup_type': False, 'signup_expiration': False}, context=context)
-
- def signup_prepare(self, cr, uid, ids, signup_type="signup", expiration=False, context=None):
- """ generate a new token for the partners with the given validity, if necessary
- :param expiration: the expiration datetime of the token (string, optional)
- """
- for partner in self.browse(cr, uid, ids, context):
- if expiration or not partner.signup_valid:
- token = random_token()
- while self._signup_retrieve_partner(cr, uid, token, context=context):
- token = random_token()
- partner.write({'signup_token': token, 'signup_type': signup_type, 'signup_expiration': expiration})
- return True
-
- def _signup_retrieve_partner(self, cr, uid, token,
- check_validity=False, raise_exception=False, context=None):
- """ find the partner corresponding to a token, and possibly check its validity
- :param token: the token to resolve
- :param check_validity: if True, also check validity
- :param raise_exception: if True, raise exception instead of returning False
- :return: partner (browse record) or False (if raise_exception is False)
- """
- partner_ids = self.search(cr, uid, [('signup_token', '=', token)], context=context)
- if not partner_ids:
- if raise_exception:
- raise SignupError("Signup token '%s' is not valid" % token)
- return False
- partner = self.browse(cr, uid, partner_ids[0], context)
- if check_validity and not partner.signup_valid:
- if raise_exception:
- raise SignupError("Signup token '%s' is no longer valid" % token)
- return False
- return partner
-
- def signup_retrieve_info(self, cr, uid, token, context=None):
- """ retrieve the user info about the token
- :return: a dictionary with the user information:
- - 'db': the name of the database
- - 'token': the token, if token is valid
- - 'name': the name of the partner, if token is valid
- - 'login': the user login, if the user already exists
- - 'email': the partner email, if the user does not exist
- """
- partner = self._signup_retrieve_partner(cr, uid, token, raise_exception=True, context=None)
- res = {'db': cr.dbname}
- if partner.signup_valid:
- res['token'] = token
- res['name'] = partner.name
- if partner.user_ids:
- res['login'] = partner.user_ids[0].login
- else:
- res['email'] = res['login'] = partner.email or ''
- return res
-
-class res_users(osv.Model):
- _inherit = 'res.users'
-
- def _get_state(self, cr, uid, ids, name, arg, context=None):
- res = {}
- for user in self.browse(cr, uid, ids, context):
- res[user.id] = ('active' if user.login_date else 'new')
- return res
-
- _columns = {
- 'state': fields.function(_get_state, string='Status', type='selection',
- selection=[('new', 'Never Connected'), ('active', 'Connected')]),
- }
-
- def signup(self, cr, uid, values, token=None, context=None):
- """ signup a user, to either:
- - create a new user (no token), or
- - create a user for a partner (with token, but no user for partner), or
- - change the password of a user (with token, and existing user).
- :param values: a dictionary with field values that are written on user
- :param token: signup token (optional)
- :return: (dbname, login, password) for the signed up user
- """
- if token:
- # signup with a token: find the corresponding partner id
- res_partner = self.pool.get('res.partner')
- partner = res_partner._signup_retrieve_partner(
- cr, uid, token, check_validity=True, raise_exception=True, context=None)
- # invalidate signup token
- partner.write({'signup_token': False, 'signup_type': False, 'signup_expiration': False})
-
- partner_user = partner.user_ids and partner.user_ids[0] or False
-
- # avoid overwriting existing (presumably correct) values with geolocation data
- if partner.country_id or partner.zip or partner.city:
- values.pop('city', None)
- values.pop('country_id', None)
- if partner.lang:
- values.pop('lang', None)
-
- if partner_user:
- # user exists, modify it according to values
- values.pop('login', None)
- values.pop('name', None)
- partner_user.write(values)
- return (cr.dbname, partner_user.login, values.get('password'))
- else:
- # user does not exist: sign up invited user
- values.update({
- 'name': partner.name,
- 'partner_id': partner.id,
- 'email': values.get('email') or values.get('login'),
- })
- if partner.company_id:
- values['company_id'] = partner.company_id.id
- values['company_ids'] = [(6, 0, [partner.company_id.id])]
- self._signup_create_user(cr, uid, values, context=context)
- else:
- # no token, sign up an external user
- values['email'] = values.get('email') or values.get('login')
- self._signup_create_user(cr, uid, values, context=context)
-
- return (cr.dbname, values.get('login'), values.get('password'))
-
- def _signup_create_user(self, cr, uid, values, context=None):
- """ create a new user from the template user """
- ir_config_parameter = self.pool.get('ir.config_parameter')
- template_user_id = literal_eval(ir_config_parameter.get_param(cr, uid, 'auth_signup.template_user_id', 'False'))
- assert template_user_id and self.exists(cr, uid, template_user_id, context=context), 'Signup: invalid template user'
-
- # check that uninvited users may sign up
- if 'partner_id' not in values:
- if not literal_eval(ir_config_parameter.get_param(cr, uid, 'auth_signup.allow_uninvited', 'False')):
- 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"
-
- # create a copy of the template user (attached to a specific partner_id if given)
- values['active'] = True
- context = dict(context or {}, no_reset_password=True)
- try:
- with cr.savepoint():
- return self.copy(cr, uid, template_user_id, values, context=context)
- except Exception, e:
- # copy may failed if asked login is not available.
- raise SignupError(ustr(e))
-
- def reset_password(self, cr, uid, login, context=None):
- """ retrieve the user corresponding to login (login or email),
- and reset their password
- """
- user_ids = self.search(cr, uid, [('login', '=', login)], context=context)
- if not user_ids:
- user_ids = self.search(cr, uid, [('email', '=', login)], context=context)
- if len(user_ids) != 1:
- raise Exception(_('Reset password: invalid username or email'))
- return self.action_reset_password(cr, uid, user_ids, context=context)
-
- def action_reset_password(self, cr, uid, ids, context=None):
- """ create signup token for each user, and send their signup url by email """
- # prepare reset password signup
- create_mode = bool(context.get('create_user'))
- res_partner = self.pool.get('res.partner')
- partner_ids = [user.partner_id.id for user in self.browse(cr, uid, ids, context)]
-
- # no time limit for initial invitation, only for reset password
- expiration = False if create_mode else now(days=+1)
-
- res_partner.signup_prepare(cr, uid, partner_ids, signup_type="reset", expiration=expiration, context=context)
-
- context = dict(context or {})
-
- # send email to users with their signup url
- template = False
- if create_mode:
- try:
- # get_object() raises ValueError if record does not exist
- template = self.pool.get('ir.model.data').get_object(cr, uid, 'auth_signup', 'set_password_email')
- except ValueError:
- pass
- if not bool(template):
- template = self.pool.get('ir.model.data').get_object(cr, uid, 'auth_signup', 'reset_password_email')
- assert template._name == 'mail.template'
-
- for user in self.browse(cr, uid, ids, context):
- if not user.email:
- raise UserError(_("Cannot send email: user %s has no email address.") % user.name)
- context['lang'] = user.lang
- self.pool.get('mail.template').send_mail(cr, uid, template.id, user.id, force_send=True, raise_exception=True, context=context)
-
- def create(self, cr, uid, values, context=None):
- if context is None:
- context = {}
- # overridden to automatically invite user to sign up
- user_id = super(res_users, self).create(cr, uid, values, context=context)
- user = self.browse(cr, uid, user_id, context=context)
- if user.email and not context.get('no_reset_password'):
- context = dict(context, create_user=True)
- try:
- self.action_reset_password(cr, uid, [user.id], context=context)
- except MailDeliveryException:
- self.pool.get('res.partner').signup_cancel(cr, uid, [user.partner_id.id], context=context)
- return user_id
-
- def copy(self, cr, uid, id, default=None, context=None):
- if not default or not default.get('email'):
- # avoid sending email to the user we are duplicating
- context = dict(context or {}, reset_password=False)
- return super(res_users, self).copy(cr, uid, id, default=default, context=context)
diff --git a/addons/auth_signup/views/auth_signup_login.xml b/addons/auth_signup/views/auth_signup_login_templates.xml
similarity index 99%
rename from addons/auth_signup/views/auth_signup_login.xml
rename to addons/auth_signup/views/auth_signup_login_templates.xml
index a8efc4b3cc1..3af04d123b4 100644
--- a/addons/auth_signup/views/auth_signup_login.xml
+++ b/addons/auth_signup/views/auth_signup_login_templates.xml
@@ -1,6 +1,5 @@
-
-
+
Sign up
@@ -99,6 +98,4 @@
-
-
-
+
diff --git a/addons/auth_signup/res_config.xml b/addons/auth_signup/views/res_config_views.xml
similarity index 89%
rename from addons/auth_signup/res_config.xml
rename to addons/auth_signup/views/res_config_views.xml
index 598ef3c40f8..66c53ef2254 100644
--- a/addons/auth_signup/res_config.xml
+++ b/addons/auth_signup/views/res_config_views.xml
@@ -1,9 +1,8 @@
-
-
-
+
+
- base.config.settings.signup
+ base.config.settings.inherit.signup
base.config.settings
@@ -26,5 +25,4 @@
-
-
+
diff --git a/addons/auth_signup/res_users_view.xml b/addons/auth_signup/views/res_users_views.xml
similarity index 93%
rename from addons/auth_signup/res_users_view.xml
rename to addons/auth_signup/views/res_users_views.xml
index 0d5eeed2178..0a5f43eb8a0 100644
--- a/addons/auth_signup/res_users_view.xml
+++ b/addons/auth_signup/views/res_users_views.xml
@@ -1,9 +1,8 @@
-
-
-
+
+
- user.form.state
+ res.users.form.inherit
res.users
@@ -34,5 +33,4 @@
-
-
+