Note: If you did not ask for a password reset, you can safely ignore this email.
]]>
-
-
- auth.reset_password.form
- auth.reset_password
-
-
-
-
-
-
- Reset Password
- ir.actions.act_window
- auth.reset_password
- form
- form
- new
-
-
-
diff --git a/addons/auth_reset_password/controllers/__init__.py b/addons/auth_reset_password/controllers/__init__.py
new file mode 100644
index 00000000000..f40f0ee976e
--- /dev/null
+++ b/addons/auth_reset_password/controllers/__init__.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2012-today OpenERP SA ()
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see
+#
+##############################################################################
+
+import main
+
+# vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/auth_reset_password/controllers/main.py b/addons/auth_reset_password/controllers/main.py
new file mode 100644
index 00000000000..5185a65c3ce
--- /dev/null
+++ b/addons/auth_reset_password/controllers/main.py
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2012-today OpenERP SA ()
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see
+#
+##############################################################################
+
+from openerp import SUPERUSER_ID
+from openerp.modules.registry import RegistryManager
+import openerp.addons.web.common.http as openerpweb
+
+import werkzeug
+
+import logging
+_logger = logging.getLogger(__name__)
+
+class Controller(openerpweb.Controller):
+ _cp_path = '/auth_reset_password'
+
+ @openerpweb.httprequest
+ def reset_password(self, req, dbname, login):
+ """ retrieve user, and perform reset password """
+ url = '/'
+ registry = RegistryManager.get(dbname)
+ with registry.cursor() as cr:
+ try:
+ res_users = registry.get('res.users')
+ res_users.reset_password(cr, SUPERUSER_ID, login)
+ cr.commit()
+ message = 'An email has been sent with credentials to reset your password'
+ except Exception as e:
+ # signup error
+ _logger.exception('error when resetting password')
+ message = e.message
+ url = "/#action=login&error_message=%s" % werkzeug.urls.url_quote(message)
+ return werkzeug.utils.redirect(url)
+
+# vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/auth_reset_password/res_users.py b/addons/auth_reset_password/res_users.py
new file mode 100644
index 00000000000..b1039031812
--- /dev/null
+++ b/addons/auth_reset_password/res_users.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2012-today OpenERP SA ()
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see
+#
+##############################################################################
+
+from openerp.osv import osv, fields
+from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT
+
+from datetime import datetime, timedelta
+
+def now(**kwargs):
+ dt = datetime.now() + timedelta(**kwargs)
+ return dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
+
+
+class res_users(osv.osv):
+ _inherit = 'res.users'
+
+ 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
+ res_partner = self.pool.get('res.partner')
+ partner_ids = [user.partner_id.id for user in self.browse(cr, uid, ids, context)]
+ res_partner.signup_prepare(cr, uid, partner_ids, expiration=now(days=+1), context=context)
+
+ # send email to users with their signup url
+ template = self.pool.get('ir.model.data').get_object(cr, uid, 'auth_reset_password', 'reset_password_email')
+ assert template._name == 'email.template'
+ for user in self.browse(cr, uid, ids, context):
+ self.pool.get('email.template').send_mail(cr, uid, template.id, user.id, context=context)
+
+ return True
diff --git a/addons/auth_reset_password/res_users_view.xml b/addons/auth_reset_password/res_users_view.xml
new file mode 100644
index 00000000000..9952162ae65
--- /dev/null
+++ b/addons/auth_reset_password/res_users_view.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+ user.form.reset_password
+ res.users
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/auth_reset_password/static/src/css/reset_password.css b/addons/auth_reset_password/static/src/css/reset_password.css
deleted file mode 100644
index dd590365918..00000000000
--- a/addons/auth_reset_password/static/src/css/reset_password.css
+++ /dev/null
@@ -1,12 +0,0 @@
-.openerp .oe_login .oe_login_pane {
- height: 152px;
-}
-.openerp .oe_login .oe_login_pane ul.oe_login_switch a {
- color: #eeeeee;
- margin: 0 8px;
-}
-.openerp .oe_login .oe_login_pane ul.oe_login_switch a:hover {
- text-decoration: underline;
-}
-
-
diff --git a/addons/auth_reset_password/static/src/js/reset_password.js b/addons/auth_reset_password/static/src/js/reset_password.js
index 4e09b5c5318..13fe2a39ce5 100644
--- a/addons/auth_reset_password/static/src/js/reset_password.js
+++ b/addons/auth_reset_password/static/src/js/reset_password.js
@@ -1,72 +1,30 @@
openerp.auth_reset_password = function(instance) {
var _t = instance.web._t;
+
instance.web.Login.include({
start: function() {
- var $e = this.$el;
- $e.find('a.oe_login_switch').click(function() {
- $e.find('ul.oe_login_switch').toggle();
- var $m = $e.find('form input[name=is_reset_pw]');
- $m.attr('checked', !$m.is(':checked'));
- });
+ this.$('a.oe_reset_password').click(this.do_reset_password);
return this._super();
},
- on_submit: function(ev) {
- if(ev) {
+ do_reset_password: function(ev) {
+ if (ev) {
ev.preventDefault();
}
-
- var $e = this.$el;
- var db = $e.find("form [name=db]").val();
+ var db = this.$("form [name=db]").val();
+ var login = this.$("form input[name=login]").val();
if (!db) {
- this.do_warn(_t("Login"), _t("No database selected !"));
+ this.do_warn("Login", "No database selected !");
+ return false;
+ } else if (!login) {
+ this.do_warn("Login", "Please enter a username or email address.")
return false;
}
- var $m = $e.find('form input[name=is_reset_pw]');
- if ($m.is(':checked')) {
- var email = $e.find('form input[name=email]').val();
- return this.do_reset_password(db, email);
- } else {
- return this._super(ev);
- }
- },
- do_reset_password: function(db, email) {
- var self = this;
- instance.session.session_authenticate(db, 'anonymous', 'anonymous', true).pipe(function () {
- var func = new instance.web.Model("res.users").get_func("send_reset_password_request");
- return func(email).then(function(res) {
- // show message
- self.do_notify(_t('Reset Password'), _.str.sprintf(_t('We have sent an email to %s with further instructions'), email), true);
- }, function(error, event) {
- // no traceback please
- event.preventDefault();
- });
- }).fail(function(error, event) {
- // cannot log as anonymous or reset_password not installed
- self.do_warn(_t('Reset Password'), _.str.sprintf(_t('Reset Password functionnality is not available for database %s'), db), true);
- });
+ var params = {
+ dbname : db,
+ login: login,
+ };
+ var url = "/auth_reset_password/reset_password?" + $.param(params);
+ window.location = url;
}
});
-
- instance.reset_password = {};
- instance.reset_password.ResetPassword = instance.web.Widget.extend({
- init: function(parent, params) {
- this._super(parent);
- this.token = (params && params.token) || false;
- },
- start: function() {
- this.do_action({
- name: 'Reset Password',
- type: 'ir.actions.act_window',
- context: {default_token: this.token},
- res_model: 'auth.reset_password',
- target: 'new',
- views: [[false, 'form']]
- });
- }
- });
-
-
- instance.web.client_actions.add("reset_password", "instance.reset_password.ResetPassword");
-
-
};
diff --git a/addons/auth_reset_password/static/src/xml/reset_password.xml b/addons/auth_reset_password/static/src/xml/reset_password.xml
index e57334b9c15..841f3906622 100644
--- a/addons/auth_reset_password/static/src/xml/reset_password.xml
+++ b/addons/auth_reset_password/static/src/xml/reset_password.xml
@@ -1,26 +1,11 @@
-
+
-
- // addClass does not work :(
- this.attr('class', (this.attr('class') || '') + ' oe_login_switch');
-
-
+
diff --git a/addons/auth_signup/__openerp__.py b/addons/auth_signup/__openerp__.py
index 9cfe89cdc1c..9cbeb60638f 100644
--- a/addons/auth_signup/__openerp__.py
+++ b/addons/auth_signup/__openerp__.py
@@ -34,7 +34,9 @@ Allow users to sign up.
'data': [
'auth_signup_data.xml',
'res_config.xml',
+ 'res_users_view.xml',
],
'js': ['static/src/js/auth_signup.js'],
+ 'css' : ['static/src/css/base.css'],
'qweb': ['static/src/xml/auth_signup.xml'],
}
diff --git a/addons/auth_signup/controllers/main.py b/addons/auth_signup/controllers/main.py
index 821ef44f459..56e5e910212 100644
--- a/addons/auth_signup/controllers/main.py
+++ b/addons/auth_signup/controllers/main.py
@@ -1,35 +1,63 @@
-import logging
-
-import werkzeug.urls
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2012-today OpenERP SA ()
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see
+#
+##############################################################################
+from openerp import SUPERUSER_ID
from openerp.modules.registry import RegistryManager
from openerp.addons.web.controllers.main import login_and_redirect
import openerp.addons.web.common.http as openerpweb
-from openerp import SUPERUSER_ID
+import werkzeug
+
+import logging
_logger = logging.getLogger(__name__)
-class OpenIDController(openerpweb.Controller):
+class Controller(openerpweb.Controller):
_cp_path = '/auth_signup'
+ @openerpweb.jsonrequest
+ def retrieve(self, req, dbname, token):
+ """ retrieve the user info (name, login or email) corresponding to a signup token """
+ registry = RegistryManager.get(dbname)
+ user_info = None
+ with registry.cursor() as cr:
+ res_partner = registry.get('res.partner')
+ user_info = res_partner.signup_retrieve_info(cr, SUPERUSER_ID, token)
+ return user_info
+
@openerpweb.httprequest
- def signup(self, req, dbname, name, login, password):
+ def signup(self, req, dbname, token, name, login, password):
+ """ sign up a user (new or existing), and log it in """
url = '/'
registry = RegistryManager.get(dbname)
with registry.cursor() as cr:
try:
- Users = registry.get('res.users')
- credentials = Users.auth_signup(cr, SUPERUSER_ID, name, login, password)
+ res_users = registry.get('res.users')
+ values = {'name': name, 'login': login, 'password': password}
+ credentials = res_users.signup(cr, SUPERUSER_ID, values, token)
cr.commit()
return login_and_redirect(req, *credentials)
- except AttributeError:
- # auth_signup is not installed
- _logger.exception('attribute error when signup')
- url = "/#action=auth_signup&error=NA" # Not Available
- except Exception:
+ except Exception as e:
# signup error
_logger.exception('error when signup')
- url = "/#action=auth_signup&error=UE" # Unexcpected Error
+ url = "/#action=login&error_message=%s" % werkzeug.urls.url_quote(e.message)
return werkzeug.utils.redirect(url)
# vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/auth_signup/res_config.py b/addons/auth_signup/res_config.py
index 8bfe1fc3ddb..21fce19745c 100644
--- a/addons/auth_signup/res_config.py
+++ b/addons/auth_signup/res_config.py
@@ -20,22 +20,27 @@
##############################################################################
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_uninvited': fields.boolean('Allow public users to sign up', help="If unchecked only invited users may sign up"),
+ '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', '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_template_user_id': icp.get_param(cr, uid, 'auth_signup.template_user_id', 0) or 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')
- icp.set_param(cr, uid, 'auth_signup.template_user_id', config.auth_signup_template_user_id.id)
+ # we store the repr of the values, since the value of the parameter is a required string
+ 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_config.xml b/addons/auth_signup/res_config.xml
index c17634ececb..9d3eb4eaa5a 100644
--- a/addons/auth_signup/res_config.xml
+++ b/addons/auth_signup/res_config.xml
@@ -14,7 +14,9 @@
-
+
diff --git a/addons/auth_signup/res_users.py b/addons/auth_signup/res_users.py
index 5f6463508b6..7b663bd0e33 100644
--- a/addons/auth_signup/res_users.py
+++ b/addons/auth_signup/res_users.py
@@ -1,47 +1,205 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2012-today OpenERP SA ()
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see
+#
+##############################################################################
+
import openerp
-from openerp.osv import osv
+from openerp.osv import osv, fields
from openerp import SUPERUSER_ID
+from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT
+from openerp.tools.safe_eval import safe_eval
+
+import time
+import random
+import urllib
+import urlparse
+
+def random_token():
+ # the token has an entropy of about 120 bits (6 bits/char * 20 chars)
+ chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
+ return ''.join(random.choice(chars) for i in xrange(20))
+
+def now():
+ return time.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(self, cr, uid, ids, name, arg, context=None):
+ """ determine a signup url for a given partner """
+ base_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url')
+
+ # if required, make sure that every partner without user has a valid signup token
+ if context and context.get('signup_valid'):
+ unsigned_ids = [p.id for p in self.browse(cr, uid, ids, context) if not p.user_ids]
+ self.signup_prepare(cr, uid, unsigned_ids, context=context)
+
+ res = dict.fromkeys(ids, False)
+ for partner in self.browse(cr, uid, ids, context):
+ if partner.signup_token:
+ params = (urllib.quote(cr.dbname), urllib.quote(partner.signup_token))
+ res[partner.id] = urlparse.urljoin(base_url, "#action=login&db=%s&token=%s" % params)
+ elif partner.user_ids:
+ user = partner.user_ids[0]
+ params = (urllib.quote(cr.dbname), urllib.quote(user.login))
+ res[partner.id] = urlparse.urljoin(base_url, "#action=login&db=%s&login=%s" % params)
+ return res
+
+ _columns = {
+ 'signup_token': fields.char(size=24, string='Signup Token'),
+ 'signup_expiration': fields.datetime(string='Signup Expiration'),
+ '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_prepare(self, cr, uid, ids, 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_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 Exception("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 Exception("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'] = partner.email or ''
+ return res
+
+
class res_users(osv.Model):
_inherit = 'res.users'
- def auth_signup_create(self, cr, uid, new_user, context=None):
- # new_user:
- # login
- # email
- # name (optional)
- # partner_id (optional)
- # groups (optional)
- # sign (for partner_id and groups)
- #
- user_template_id = self.pool.get('ir.config_parameter').get_param(cr, uid, 'auth.signup_template_user_id', 0)
- if user_template_id:
- self.pool.get('res.users').copy(cr, SUPERUSER_ID, user_template_id, new_user, context=context)
- else:
- self.pool.get('res.users').create(cr, SUPERUSER_ID, new_user, context=context)
+ def _get_state(self, cr, uid, ids, name, arg, context=None):
+ return dict((user.id, 'new' if not user.login_date else 'reset' if user.signup_token else 'active')
+ for user in self.browse(cr, uid, ids, context))
- def auth_signup(self, cr, uid, name, login, password, context=None):
- r = (cr.dbname, login, password)
- res = self.search(cr, uid, [("login", "=", login)])
- if res:
- # Existing user
- user_id = res[0]
- try:
- self.check(cr.dbname, user_id, password)
- # Same password
- except openerp.exceptions.AccessDenied:
- # Different password
- raise
- else:
- # New user
- new_user = {
- 'name': name,
- 'login': login,
- 'user_email': login,
- 'password': password,
- 'active': True,
- }
- self.auth_signup_create(cr, uid, new_user)
- return r
+ _columns = {
+ 'state': fields.function(_get_state, string='State', type='selection',
+ selection=[('new', 'New'), ('active', 'Active'), ('reset', 'Resetting Password')]),
+ }
-#
+ 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
+ :param token: signup token (optional)
+ :return: (dbname, login, password) for the signed up user
+ """
+ assert values.get('login') and values.get('password')
+ result = (cr.dbname, values['login'], values['password'])
+
+ 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_expiration': False})
+ if partner.user_ids:
+ # user exists, modify its password
+ partner.user_ids[0].write({'password': values['password']})
+ else:
+ # user does not exist: sign up invited user
+ self._signup_create_user(cr, uid, {
+ 'name': partner.name,
+ 'login': values['login'],
+ 'password': values['password'],
+ 'email': values['login'],
+ 'partner_id': partner.id,
+ }, context=context)
+ return result
+
+ # sign up an external user
+ assert values.get('name'), 'Signup: no name given for new user'
+ self._signup_create_user(cr, uid, {
+ 'name': values['name'],
+ 'login': values['login'],
+ 'password': values['password'],
+ 'email': values['login'],
+ }, context=context)
+ return result
+
+ 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 = safe_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 safe_eval(ir_config_parameter.get_param(cr, uid, 'auth_signup.allow_uninvited', 'False')):
+ raise Exception('Signup is not allowed for uninvited users')
+
+ # create a copy of the template user (attached to a specific partner_id if given)
+ values['active'] = True
+ return self.copy(cr, uid, template_user_id, values, context=context)
diff --git a/addons/auth_signup/res_users_view.xml b/addons/auth_signup/res_users_view.xml
new file mode 100644
index 00000000000..c42e1ab5f41
--- /dev/null
+++ b/addons/auth_signup/res_users_view.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+ user.form.state
+ res.users
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/auth_signup/static/src/css/Makefile b/addons/auth_signup/static/src/css/Makefile
new file mode 100644
index 00000000000..d6b4f4b2fc8
--- /dev/null
+++ b/addons/auth_signup/static/src/css/Makefile
@@ -0,0 +1,3 @@
+base.css: base.sass
+ sass --trace -t expanded base.sass base.css
+
diff --git a/addons/auth_signup/static/src/css/base.css b/addons/auth_signup/static/src/css/base.css
new file mode 100644
index 00000000000..3f97e7a9301
--- /dev/null
+++ b/addons/auth_signup/static/src/css/base.css
@@ -0,0 +1,10 @@
+@charset "utf-8";
+.openerp .oe_login .oe_signup_show {
+ display: none;
+}
+.openerp .oe_login_signup .oe_signup_show {
+ display: block !important;
+}
+.openerp .oe_login_signup .oe_signup_hide {
+ display: none;
+}
diff --git a/addons/auth_signup/static/src/css/base.sass b/addons/auth_signup/static/src/css/base.sass
new file mode 100644
index 00000000000..f97665d192c
--- /dev/null
+++ b/addons/auth_signup/static/src/css/base.sass
@@ -0,0 +1,13 @@
+@charset "utf-8"
+
+.openerp
+ // Regular login form
+ .oe_login
+ .oe_signup_show
+ display: none
+ // Signup form
+ .oe_login_signup
+ .oe_signup_show
+ display: block !important
+ .oe_signup_hide
+ display: none
diff --git a/addons/auth_signup/static/src/js/auth_signup.js b/addons/auth_signup/static/src/js/auth_signup.js
index 2ab13aa7598..509fafa35cc 100644
--- a/addons/auth_signup/static/src/js/auth_signup.js
+++ b/addons/auth_signup/static/src/js/auth_signup.js
@@ -5,56 +5,99 @@ openerp.auth_signup = function(instance) {
instance.web.Login.include({
start: function() {
var self = this;
- this.$('a.oe_signup').click(function() {
- var dbname = self.$("form [name=db]").val();
- self.do_action({
- type: 'ir.actions.client',
- tag: 'auth_signup.signup',
- params: {'dbname': dbname},
- target: 'new',
- name: 'Sign up'
- });
- return true;
+ var d = this._super();
+
+ // to switch between the signup and regular login form
+ this.$('a.oe_signup_signup').click(function() {
+ self.$el.addClass("oe_login_signup");
+ });
+ this.$('a.oe_signup_back').click(function() {
+ self.$el.removeClass("oe_login_signup");
+ delete self.params.token;
});
- return this._super();
- },
- });
+ // if there is an error message in params, show it then forget it
+ if (self.params.error_message) {
+ this.show_error(self.params.error_message);
+ delete self.params.error_message;
+ }
- instance.auth_signup.Signup = instance.web.Widget.extend({
- template: 'auth_signup.signup',
- init: function(parent, params) {
- this.params = params;
- return this._super();
+ // in case of a signup, retrieve the user information from the token
+ if (self.params.db && self.params.token) {
+ d = self.rpc("/auth_signup/retrieve", {dbname: self.params.db, token: self.params.token})
+ .done(self.on_token_loaded)
+ .fail(self.on_token_failed);
+ }
+ return d;
},
- start: function() {
- var self = this;
- this.$('input[name=password_confirmation]').keyup(function() {
- var v = $(this).val();
- var $b = self.$('button');
- if (_.isEmpty(v) || self.$('input[name=password]').val() === v) {
- $b.removeAttr('disabled');
+ on_token_loaded: function(result) {
+ // select the right the database
+ this.selected_db = result.db;
+ this.on_db_loaded({db_list: [result.db]});
+ if (result.token) {
+ // switch to signup mode, set user name and login
+ this.$el.addClass("oe_login_signup");
+ this.$("form input[name=name]").val(result.name).attr("readonly", "readonly");
+ if (result.login) {
+ this.$("form input[name=login]").val(result.login).attr("readonly", "readonly");
} else {
- $b.attr('disabled', 'disabled');
+ this.$("form input[name=login]").val(result.email);
}
- });
-
- this.$('form').submit(function(ev) {
- if(ev) {
- ev.preventDefault();
+ } else {
+ // remain in login mode, set login if present
+ delete this.params.token;
+ this.$("form input[name=login]").val(result.login || "");
+ }
+ },
+ on_token_failed: function(result, ev) {
+ if (ev) {
+ ev.preventDefault();
+ }
+ this.show_error("Invalid signup token");
+ delete this.params.db;
+ delete this.params.token;
+ },
+ on_submit: function(ev) {
+ if (ev) {
+ ev.preventDefault();
+ }
+ if (this.$el.hasClass("oe_login_signup")) {
+ // signup user (or reset password)
+ var db = this.$("form [name=db]").val();
+ var name = this.$("form input[name=name]").val();
+ var login = this.$("form input[name=login]").val();
+ var password = this.$("form input[name=password]").val();
+ var confirm_password = this.$("form input[name=confirm_password]").val();
+ if (!db) {
+ this.do_warn("Login", "No database selected !");
+ return false;
+ } else if (!name) {
+ this.do_warn("Login", "Please enter a name.")
+ return false;
+ } else if (!login) {
+ this.do_warn("Login", "Please enter a username.")
+ return false;
+ } else if (!password || !confirm_password) {
+ this.do_warn("Login", "Please enter a password and confirm it.")
+ return false;
+ } else if (password !== confirm_password) {
+ this.do_warn("Login", "Passwords do not match; please retype them.")
+ return false;
}
var params = {
- dbname : self.params.dbname,
- name: self.$('input[name=name]').val(),
- login: self.$('input[name=email]').val(),
- password: self.$('input[name=password]').val(),
+ dbname : db,
+ token: this.params.token || "",
+ name: name,
+ login: login,
+ password: password,
};
var url = "/auth_signup/signup?" + $.param(params);
window.location = url;
- });
- return this._super();
- }
+ } else {
+ // regular login
+ this._super(ev);
+ }
+ },
});
- instance.web.client_actions.add("auth_signup.signup", "instance.auth_signup.Signup");
};
diff --git a/addons/auth_signup/static/src/xml/auth_signup.xml b/addons/auth_signup/static/src/xml/auth_signup.xml
index c2eec846d15..e63bd5863b0 100644
--- a/addons/auth_signup/static/src/xml/auth_signup.xml
+++ b/addons/auth_signup/static/src/xml/auth_signup.xml
@@ -1,28 +1,28 @@
-
+
-
-
-
-
-
-
diff --git a/addons/base_calendar/crm_meeting_view.xml b/addons/base_calendar/crm_meeting_view.xml
index 37797a012aa..4df0a8a22b2 100644
--- a/addons/base_calendar/crm_meeting_view.xml
+++ b/addons/base_calendar/crm_meeting_view.xml
@@ -280,7 +280,7 @@
-
+
diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py
index 7e33f397e8a..48c6491a7c3 100644
--- a/addons/crm/crm_lead.py
+++ b/addons/crm/crm_lead.py
@@ -26,6 +26,7 @@ from osv import fields, osv
import time
import tools
from tools.translate import _
+from tools import html2plaintext
from base.res.res_partner import format_address
@@ -812,9 +813,11 @@ class crm_lead(base_stage, format_address, osv.osv):
This override updates the document according to the email.
"""
if custom_values is None: custom_values = {}
+
+ desc = html2plaintext(msg.get('body')) if msg.get('body') else ''
custom_values.update({
'name': msg.get('subject') or _("No Subject"),
- 'description': msg.get('body'),
+ 'description': desc,
'email_from': msg.get('from'),
'email_cc': msg.get('cc'),
'user_id': False,
diff --git a/addons/crm/crm_lead_view.xml b/addons/crm/crm_lead_view.xml
index f91a8756598..6a152f44b67 100644
--- a/addons/crm/crm_lead_view.xml
+++ b/addons/crm/crm_lead_view.xml
@@ -356,10 +356,10 @@
-
+
-
+
@@ -569,10 +569,10 @@
-
+
-
+
@@ -580,9 +580,8 @@
domain="['|', ('section_id.user_id','=',uid), ('section_id.member_ids', 'in', [uid])]" context="{'invisible_section': False}"
help="Opportunities that are assigned to either me or one of the sale teams I manage" />
-
-
+
diff --git a/addons/crm/res_partner_view.xml b/addons/crm/res_partner_view.xml
index 84a0bd4cf49..b7aa334a9c3 100644
--- a/addons/crm/res_partner_view.xml
+++ b/addons/crm/res_partner_view.xml
@@ -15,31 +15,12 @@
-
- view.res.partner.tree.crm.inherited2
- res.partner
-
-
-
-
-
-
-
-
-
view.res.partner.search.crm.inherited3res.partner
-
-
-
-
-
-
-
diff --git a/addons/crm_claim/crm_claim.py b/addons/crm_claim/crm_claim.py
index cbf3ec9f13a..c88b2fe26af 100644
--- a/addons/crm_claim/crm_claim.py
+++ b/addons/crm_claim/crm_claim.py
@@ -26,6 +26,7 @@ from osv import fields, osv
import time
import tools
from tools.translate import _
+from tools import html2plaintext
CRM_CLAIM_PENDING_STATES = (
crm.AVAILABLE_STATES[2][0], # Cancelled
@@ -192,9 +193,10 @@ class crm_claim(base_stage, osv.osv):
This override updates the document according to the email.
"""
if custom_values is None: custom_values = {}
+ desc = html2plaintext(msg.get('body')) if msg.get('body') else ''
custom_values.update({
'name': msg.get('subject') or _("No Subject"),
- 'description': msg.get('body'),
+ 'description': desc,
'email_from': msg.get('from'),
'email_cc': msg.get('cc'),
})
diff --git a/addons/crm_helpdesk/crm_helpdesk.py b/addons/crm_helpdesk/crm_helpdesk.py
index f36a3b72640..ceffd282aa8 100644
--- a/addons/crm_helpdesk/crm_helpdesk.py
+++ b/addons/crm_helpdesk/crm_helpdesk.py
@@ -25,6 +25,7 @@ from crm import crm
from osv import fields, osv
import tools
from tools.translate import _
+from tools import html2plaintext
CRM_HELPDESK_STATES = (
crm.AVAILABLE_STATES[2][0], # Cancelled
@@ -104,9 +105,10 @@ class crm_helpdesk(base_state, base_stage, osv.osv):
This override updates the document according to the email.
"""
if custom_values is None: custom_values = {}
+ desc = html2plaintext(msg.get('body')) if msg.get('body') else ''
custom_values.update({
'name': msg.get('subject') or _("No Subject"),
- 'description': msg.get('body'),
+ 'description': desc,
'email_from': msg.get('from'),
'email_cc': msg.get('cc'),
'user_id': False,
diff --git a/addons/crm_partner_assign/crm_lead_view.xml b/addons/crm_partner_assign/crm_lead_view.xml
index cd2e3f08c9a..2cffb6fb899 100644
--- a/addons/crm_partner_assign/crm_lead_view.xml
+++ b/addons/crm_partner_assign/crm_lead_view.xml
@@ -56,7 +56,7 @@
-
+
diff --git a/addons/event/event_view.xml b/addons/event/event_view.xml
index b46fafe270f..0155846fec3 100644
--- a/addons/event/event_view.xml
+++ b/addons/event/event_view.xml
@@ -339,7 +339,7 @@
-
+
@@ -529,7 +529,7 @@
-
+
diff --git a/addons/hr/hr_view.xml b/addons/hr/hr_view.xml
index 69567902763..89c327f4d13 100644
--- a/addons/hr/hr_view.xml
+++ b/addons/hr/hr_view.xml
@@ -112,8 +112,6 @@
-
-
diff --git a/addons/hr_contract/i18n/mk.po b/addons/hr_contract/i18n/mk.po
new file mode 100644
index 00000000000..2a4011c9e0d
--- /dev/null
+++ b/addons/hr_contract/i18n/mk.po
@@ -0,0 +1,271 @@
+# Macedonian translation for openobject-addons
+# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012
+# This file is distributed under the same license as the openobject-addons package.
+# FIRST AUTHOR , 2012.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: openobject-addons\n"
+"Report-Msgid-Bugs-To: FULL NAME \n"
+"POT-Creation-Date: 2012-02-08 00:36+0000\n"
+"PO-Revision-Date: 2012-10-09 08:00+0000\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: Macedonian \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Launchpad-Export-Date: 2012-10-10 04:44+0000\n"
+"X-Generator: Launchpad (build 16112)\n"
+
+#. module: hr_contract
+#: field:hr.contract,wage:0
+msgid "Wage"
+msgstr "Плата"
+
+#. module: hr_contract
+#: view:hr.contract:0
+msgid "Information"
+msgstr "Информации"
+
+#. module: hr_contract
+#: view:hr.contract:0
+msgid "Trial Period"
+msgstr "Пробен Период"
+
+#. module: hr_contract
+#: field:hr.contract,trial_date_start:0
+msgid "Trial Start Date"
+msgstr "Пробен Почетен Датум"
+
+#. module: hr_contract
+#: view:hr.employee:0
+msgid "Medical Examination"
+msgstr "Систематски Преглед"
+
+#. module: hr_contract
+#: field:hr.employee,vehicle:0
+msgid "Company Vehicle"
+msgstr "Службено Возило"
+
+#. module: hr_contract
+#: view:hr.employee:0
+msgid "Miscellaneous"
+msgstr "Разно"
+
+#. module: hr_contract
+#: view:hr.contract:0
+msgid "Current"
+msgstr "Тековен"
+
+#. module: hr_contract
+#: view:hr.contract:0
+msgid "Group By..."
+msgstr "Групирај По..."
+
+#. module: hr_contract
+#: field:hr.contract,department_id:0
+msgid "Department"
+msgstr "Сектор"
+
+#. module: hr_contract
+#: view:hr.contract:0
+msgid "Overpassed"
+msgstr "Изминат"
+
+#. module: hr_contract
+#: view:hr.contract:0
+#: field:hr.contract,employee_id:0
+#: model:ir.model,name:hr_contract.model_hr_employee
+msgid "Employee"
+msgstr "Вработен"
+
+#. module: hr_contract
+#: view:hr.contract:0
+msgid "Search Contract"
+msgstr "Пребарај Договор"
+
+#. module: hr_contract
+#: view:hr.contract:0
+msgid "Contracts in progress"
+msgstr "Договори во тек"
+
+#. module: hr_contract
+#: field:hr.employee,vehicle_distance:0
+msgid "Home-Work Distance"
+msgstr "Дома-Работа Одалеченост"
+
+#. module: hr_contract
+#: view:hr.contract:0
+#: field:hr.employee,contract_ids:0
+#: model:ir.actions.act_window,name:hr_contract.act_hr_employee_2_hr_contract
+#: model:ir.actions.act_window,name:hr_contract.action_hr_contract
+#: model:ir.ui.menu,name:hr_contract.hr_menu_contract
+msgid "Contracts"
+msgstr "Договори"
+
+#. module: hr_contract
+#: view:hr.employee:0
+msgid "Personal Info"
+msgstr "Лични информации"
+
+#. module: hr_contract
+#: view:hr.contract:0
+msgid "Contracts whose end date already passed"
+msgstr "Договори чии што краен рок е веќе поминат"
+
+#. module: hr_contract
+#: help:hr.employee,contract_id:0
+msgid "Latest contract of the employee"
+msgstr "Последен договор на вработениот"
+
+#. module: hr_contract
+#: view:hr.contract:0
+msgid "Job"
+msgstr "Работно Место"
+
+#. module: hr_contract
+#: view:hr.contract:0
+#: field:hr.contract,advantages:0
+msgid "Advantages"
+msgstr "Придобивки"
+
+#. module: hr_contract
+#: view:hr.contract:0
+msgid "Valid for"
+msgstr "Валиден за"
+
+#. module: hr_contract
+#: view:hr.contract:0
+msgid "Work Permit"
+msgstr "Дозвола за Работа"
+
+#. module: hr_contract
+#: field:hr.employee,children:0
+msgid "Number of Children"
+msgstr "Број на Деца"
+
+#. module: hr_contract
+#: model:ir.actions.act_window,name:hr_contract.action_hr_contract_type
+#: model:ir.ui.menu,name:hr_contract.hr_menu_contract_type
+msgid "Contract Types"
+msgstr "Типови на Договор"
+
+#. module: hr_contract
+#: constraint:hr.employee:0
+msgid "Error ! You cannot create recursive Hierarchy of Employees."
+msgstr "Грешка! Не можете да креирате рекурзивна хиерархија на вработените."
+
+#. module: hr_contract
+#: field:hr.contract,date_end:0
+msgid "End Date"
+msgstr "Краен датум"
+
+#. module: hr_contract
+#: help:hr.contract,wage:0
+msgid "Basic Salary of the employee"
+msgstr "Основна Плата на вработениот"
+
+#. module: hr_contract
+#: field:hr.contract,name:0
+msgid "Contract Reference"
+msgstr "Референца"
+
+#. module: hr_contract
+#: help:hr.employee,vehicle_distance:0
+msgid "In kilometers"
+msgstr "во километри"
+
+#. module: hr_contract
+#: view:hr.contract:0
+#: field:hr.contract,notes:0
+msgid "Notes"
+msgstr "Забелешки"
+
+#. module: hr_contract
+#: field:hr.contract,permit_no:0
+msgid "Work Permit No"
+msgstr "Број на Дозвола за Работа"
+
+#. module: hr_contract
+#: view:hr.contract:0
+#: field:hr.employee,contract_id:0
+#: model:ir.model,name:hr_contract.model_hr_contract
+#: model:ir.ui.menu,name:hr_contract.next_id_56
+msgid "Contract"
+msgstr "Договор"
+
+#. module: hr_contract
+#: view:hr.contract:0
+#: field:hr.contract,type_id:0
+#: view:hr.contract.type:0
+#: field:hr.contract.type,name:0
+#: model:ir.model,name:hr_contract.model_hr_contract_type
+msgid "Contract Type"
+msgstr "Тип на Договор"
+
+#. module: hr_contract
+#: view:hr.contract:0
+#: field:hr.contract,working_hours:0
+msgid "Working Schedule"
+msgstr "Распоред за Работа"
+
+#. module: hr_contract
+#: view:hr.employee:0
+msgid "Job Info"
+msgstr "Информации за Работата"
+
+#. module: hr_contract
+#: field:hr.contract,visa_expire:0
+msgid "Visa Expire Date"
+msgstr "Виза Датум на Истекување"
+
+#. module: hr_contract
+#: field:hr.contract,job_id:0
+msgid "Job Title"
+msgstr "Звање"
+
+#. module: hr_contract
+#: field:hr.employee,manager:0
+msgid "Is a Manager"
+msgstr "Менаџер"
+
+#. module: hr_contract
+#: field:hr.contract,date_start:0
+msgid "Start Date"
+msgstr "Почетен датум"
+
+#. module: hr_contract
+#: constraint:hr.contract:0
+msgid "Error! contract start-date must be lower then contract end-date."
+msgstr ""
+"Грешка! Почетниот датум на договорот мора да биде помал од крајниот датум."
+
+#. module: hr_contract
+#: field:hr.contract,visa_no:0
+msgid "Visa No"
+msgstr "Број на Виза"
+
+#. module: hr_contract
+#: field:hr.employee,place_of_birth:0
+msgid "Place of Birth"
+msgstr "Место на раѓање"
+
+#. module: hr_contract
+#: view:hr.contract:0
+msgid "Duration"
+msgstr "Траење"
+
+#. module: hr_contract
+#: field:hr.employee,medic_exam:0
+msgid "Medical Examination Date"
+msgstr "Датум за Систематски Преглед"
+
+#. module: hr_contract
+#: field:hr.contract,trial_date_end:0
+msgid "Trial End Date"
+msgstr "Пробен Краен Датум"
+
+#. module: hr_contract
+#: view:hr.contract.type:0
+msgid "Search Contract Type"
+msgstr "Пребарај Тип на Договор"
diff --git a/addons/hr_expense/hr_expense_view.xml b/addons/hr_expense/hr_expense_view.xml
index 388e5262f6e..67d629eee6a 100644
--- a/addons/hr_expense/hr_expense_view.xml
+++ b/addons/hr_expense/hr_expense_view.xml
@@ -155,11 +155,12 @@
-
+
+
+
-
diff --git a/addons/hr_holidays/i18n/mk.po b/addons/hr_holidays/i18n/mk.po
new file mode 100644
index 00000000000..2694317ebe9
--- /dev/null
+++ b/addons/hr_holidays/i18n/mk.po
@@ -0,0 +1,864 @@
+# Macedonian translation for openobject-addons
+# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012
+# This file is distributed under the same license as the openobject-addons package.
+# FIRST AUTHOR , 2012.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: openobject-addons\n"
+"Report-Msgid-Bugs-To: FULL NAME \n"
+"POT-Creation-Date: 2012-02-08 01:37+0100\n"
+"PO-Revision-Date: 2012-10-09 08:43+0000\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: Macedonian \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Launchpad-Export-Date: 2012-10-10 04:44+0000\n"
+"X-Generator: Launchpad (build 16112)\n"
+
+#. module: hr_holidays
+#: selection:hr.holidays.status,color_name:0
+msgid "Blue"
+msgstr "Сина"
+
+#. module: hr_holidays
+#: field:hr.holidays,holiday_type:0
+msgid "Allocation Type"
+msgstr "Тип на Алокација"
+
+#. module: hr_holidays
+#: selection:hr.employee,current_leave_state:0
+#: selection:hr.holidays,state:0
+msgid "Waiting Second Approval"
+msgstr "Се чека на второ одобрување"
+
+#. module: hr_holidays
+#: help:hr.holidays.status,remaining_leaves:0
+msgid "Maximum Leaves Allowed - Leaves Already Taken"
+msgstr "Мкасимален број на дозволени отсуства - Отсуствата се веќе земени"
+
+#. module: hr_holidays
+#: view:hr.holidays:0
+msgid "Leaves Management"
+msgstr "Менаџирање на Отсуства"
+
+#. module: hr_holidays
+#: view:hr.holidays:0
+msgid "Group By..."
+msgstr "Групирај По..."
+
+#. module: hr_holidays
+#: view:hr.holidays:0
+msgid "Allocation Mode"
+msgstr "Мод на Алокација"
+
+#. module: hr_holidays
+#: view:hr.holidays:0
+#: field:hr.holidays,department_id:0
+msgid "Department"
+msgstr "Сектор"
+
+#. module: hr_holidays
+#: model:ir.actions.act_window,name:hr_holidays.request_approve_holidays
+msgid "Requests Approve"
+msgstr "Одобри Барања"
+
+#. module: hr_holidays
+#: selection:hr.employee,current_leave_state:0
+#: selection:hr.holidays,state:0
+msgid "Refused"
+msgstr "Одбиени"
+
+#. module: hr_holidays
+#: help:hr.holidays,category_id:0
+msgid "Category of Employee"
+msgstr "Категорија на Вработен"
+
+#. module: hr_holidays
+#: view:hr.holidays:0
+msgid " Month-1"
+msgstr " Месец-1"
+
+#. module: hr_holidays
+#: selection:hr.holidays.status,color_name:0
+msgid "Brown"
+msgstr "Кафена"
+
+#. module: hr_holidays
+#: view:hr.holidays:0
+msgid "Remaining Days"
+msgstr "Останати Денови"
+
+#. module: hr_holidays
+#: selection:hr.holidays,holiday_type:0
+msgid "By Employee"
+msgstr "По Вработен"
+
+#. module: hr_holidays
+#: help:hr.holidays,employee_id:0
+msgid ""
+"Leave Manager can let this field empty if this leave request/allocation is "
+"for every employee"
+msgstr ""
+"Менаџерот за отсуства може да го остави ова поле празно ако барањето за "
+"отсуство/преместување е за секој вработен"
+
+#. module: hr_holidays
+#: view:hr.holidays:0
+msgid "Set to Draft"
+msgstr "Подеси на нацрт"
+
+#. module: hr_holidays
+#: selection:hr.holidays.status,color_name:0
+msgid "Light Cyan"
+msgstr "Светла Цијан"
+
+#. module: hr_holidays
+#: selection:hr.holidays.status,color_name:0
+msgid "Light Green"
+msgstr "Светло зелено"
+
+#. module: hr_holidays
+#: field:hr.employee,current_leave_id:0
+msgid "Current Leave Type"
+msgstr "Тековен Тип на Отсуство"
+
+#. module: hr_holidays
+#: model:ir.actions.act_window,help:hr_holidays.open_ask_holidays
+msgid ""
+"Leave requests can be recorded by employees and validated by their managers. "
+"Once a leave request is validated, it appears automatically in the agenda of "
+"the employee. You can define several allowance types (paid holidays, "
+"sickness, etc.) and manage allowances per type."
+msgstr ""
+
+#. module: hr_holidays
+#: model:ir.actions.report.xml,name:hr_holidays.report_holidays_summary
+msgid "Summary Of Leaves"
+msgstr "Збир на Отсуства"
+
+#. module: hr_holidays
+#: selection:hr.employee,current_leave_state:0
+#: view:hr.holidays:0
+#: selection:hr.holidays,state:0
+msgid "Approved"
+msgstr "Одобрено"
+
+#. module: hr_holidays
+#: field:hr.employee,last_login:0
+msgid "Latest Connection"
+msgstr "Последна Конекција"
+
+#. module: hr_holidays
+#: view:hr.holidays:0
+msgid "Refuse"
+msgstr "Одбиј"
+
+#. module: hr_holidays
+#: code:addons/hr_holidays/hr_holidays.py:344
+#, python-format
+msgid ""
+"You cannot validate leaves for employee %s: too few remaining days (%s)."
+msgstr ""
+"Не можете да валидирате отсуства за вработениот %s: премалку преостанати "
+"денови (%s)."
+
+#. module: hr_holidays
+#: help:hr.holidays,state:0
+msgid ""
+"The state is set to 'Draft', when a holiday request is created. \n"
+"The state is 'Waiting Approval', when holiday request is confirmed by user. "
+" \n"
+"The state is 'Refused', when holiday request is refused by manager. "
+" \n"
+"The state is 'Approved', when holiday request is approved by manager."
+msgstr ""
+
+#. module: hr_holidays
+#: view:board.board:0
+#: model:ir.actions.act_window,name:hr_holidays.act_hr_employee_holiday_request
+#: model:ir.ui.menu,name:hr_holidays.menu_hr_reporting_holidays
+#: model:ir.ui.menu,name:hr_holidays.menu_open_ask_holidays
+msgid "Leaves"
+msgstr "Отсуства"
+
+#. module: hr_holidays
+#: model:ir.model,name:hr_holidays.model_hr_holidays
+msgid "Leave"
+msgstr ""
+
+#. module: hr_holidays
+#: model:ir.ui.menu,name:hr_holidays.menu_request_approve_holidays
+msgid "Leave Requests to Approve"
+msgstr "Барања за отсуства за одобрување"
+
+#. module: hr_holidays
+#: model:ir.ui.menu,name:hr_holidays.menu_account_central_journal
+msgid "Leaves by Department"
+msgstr "Отсуства по Сектор"
+
+#. module: hr_holidays
+#: selection:hr.employee,current_leave_state:0
+#: selection:hr.holidays,state:0
+msgid "Cancelled"
+msgstr "Откажано"
+
+#. module: hr_holidays
+#: help:hr.holidays,type:0
+msgid ""
+"Choose 'Leave Request' if someone wants to take an off-day. \n"
+"Choose 'Allocation Request' if you want to increase the number of leaves "
+"available for someone"
+msgstr ""
+
+#. module: hr_holidays
+#: help:hr.employee,remaining_leaves:0
+msgid ""
+"Total number of legal leaves allocated to this employee, change this value "
+"to create allocation/leave requests."
+msgstr ""
+
+#. module: hr_holidays
+#: view:hr.holidays.status:0
+msgid "Validation"
+msgstr ""
+
+#. module: hr_holidays
+#: code:addons/hr_holidays/hr_holidays.py:377
+#, python-format
+msgid "Warning !"
+msgstr ""
+
+#. module: hr_holidays
+#: field:hr.holidays.status,color_name:0
+msgid "Color in Report"
+msgstr ""
+
+#. module: hr_holidays
+#: model:ir.model,name:hr_holidays.model_hr_holidays_summary_employee
+msgid "HR Holidays Summary Report By Employee"
+msgstr ""
+
+#. module: hr_holidays
+#: help:hr.holidays,manager_id:0
+msgid "This area is automatically filled by the user who validate the leave"
+msgstr ""
+
+#. module: hr_holidays
+#: field:hr.holidays,holiday_status_id:0
+#: field:hr.holidays.remaining.leaves.user,leave_type:0
+#: view:hr.holidays.status:0
+#: field:hr.holidays.status,name:0
+#: field:hr.holidays.summary.dept,holiday_type:0
+#: model:ir.actions.act_window,name:hr_holidays.open_view_holiday_status
+#: model:ir.model,name:hr_holidays.model_hr_holidays_status
+#: model:ir.ui.menu,name:hr_holidays.menu_open_view_holiday_status
+msgid "Leave Type"
+msgstr ""
+
+#. module: hr_holidays
+#: code:addons/hr_holidays/hr_holidays.py:199
+#: code:addons/hr_holidays/hr_holidays.py:344
+#, python-format
+msgid "Warning!"
+msgstr ""
+
+#. module: hr_holidays
+#: selection:hr.holidays.status,color_name:0
+msgid "Magenta"
+msgstr ""
+
+#. module: hr_holidays
+#: code:addons/hr_holidays/wizard/hr_holidays_summary_department.py:44
+#, python-format
+msgid "You have to select at least 1 Department. And try again"
+msgstr ""
+
+#. module: hr_holidays
+#: selection:hr.holidays.summary.dept,holiday_type:0
+#: selection:hr.holidays.summary.employee,holiday_type:0
+msgid "Confirmed"
+msgstr ""
+
+#. module: hr_holidays
+#: field:hr.holidays.summary.dept,date_from:0
+#: field:hr.holidays.summary.employee,date_from:0
+msgid "From"
+msgstr ""
+
+#. module: hr_holidays
+#: view:hr.holidays:0
+msgid "Confirm"
+msgstr ""
+
+#. module: hr_holidays
+#: code:addons/hr_holidays/hr_holidays.py:384
+#, python-format
+msgid "Leave Request for %s"
+msgstr ""
+
+#. module: hr_holidays
+#: field:hr.holidays.status,remaining_leaves:0
+msgid "Remaining Leaves"
+msgstr ""
+
+#. module: hr_holidays
+#: view:hr.holidays:0
+#: field:hr.holidays,state:0
+msgid "State"
+msgstr ""
+
+#. module: hr_holidays
+#: model:ir.model,name:hr_holidays.model_hr_holidays_remaining_leaves_user
+msgid "Total holidays by type"
+msgstr ""
+
+#. module: hr_holidays
+#: view:hr.employee:0
+#: view:hr.holidays:0
+#: field:hr.holidays,employee_id:0
+#: field:hr.holidays.remaining.leaves.user,name:0
+#: model:ir.model,name:hr_holidays.model_hr_employee
+msgid "Employee"
+msgstr ""
+
+#. module: hr_holidays
+#: selection:hr.employee,current_leave_state:0
+#: selection:hr.holidays,state:0
+msgid "New"
+msgstr ""
+
+#. module: hr_holidays
+#: view:hr.holidays:0
+msgid "Type"
+msgstr ""
+
+#. module: hr_holidays
+#: selection:hr.holidays.status,color_name:0
+msgid "Red"
+msgstr ""
+
+#. module: hr_holidays
+#: view:hr.holidays.remaining.leaves.user:0
+msgid "Leaves by Type"
+msgstr ""
+
+#. module: hr_holidays
+#: selection:hr.holidays.status,color_name:0
+msgid "Light Salmon"
+msgstr ""
+
+#. module: hr_holidays
+#: selection:hr.holidays.status,color_name:0
+msgid "Wheat"
+msgstr ""
+
+#. module: hr_holidays
+#: constraint:resource.calendar.leaves:0
+msgid "Error! leave start-date must be lower then leave end-date."
+msgstr ""
+
+#. module: hr_holidays
+#: code:addons/hr_holidays/hr_holidays.py:382
+#, python-format
+msgid "Allocation for %s"
+msgstr ""
+
+#. module: hr_holidays
+#: view:hr.holidays:0
+#: field:hr.holidays,number_of_days:0
+#: field:hr.holidays,number_of_days_temp:0
+msgid "Number of Days"
+msgstr ""
+
+#. module: hr_holidays
+#: code:addons/hr_holidays/hr_holidays.py:377
+#, python-format
+msgid ""
+"The feature behind the field 'Remaining Legal Leaves' can only be used when "
+"there is only one leave type with the option 'Allow to Override Limit' "
+"unchecked. (%s Found). Otherwise, the update is ambiguous as we cannot "
+"decide on which leave type the update has to be done. \n"
+"You may prefer to use the classic menus 'Leave Requests' and 'Allocation "
+"Requests' located in 'Human Resources \\ Leaves' to manage the leave days of "
+"the employees if the configuration does not allow to use this field."
+msgstr ""
+
+#. module: hr_holidays
+#: view:hr.holidays.status:0
+msgid "Search Leave Type"
+msgstr ""
+
+#. module: hr_holidays
+#: sql_constraint:hr.holidays:0
+msgid "You have to select an employee or a category"
+msgstr ""
+
+#. module: hr_holidays
+#: help:hr.holidays.status,double_validation:0
+msgid ""
+"When selected, the Allocation/Leave Requests for this type require a second "
+"validation to be approved."
+msgstr ""
+
+#. module: hr_holidays
+#: selection:hr.employee,current_leave_state:0
+#: selection:hr.holidays,state:0
+msgid "Waiting Approval"
+msgstr ""
+
+#. module: hr_holidays
+#: field:hr.holidays.summary.employee,emp:0
+msgid "Employee(s)"
+msgstr ""
+
+#. module: hr_holidays
+#: help:hr.holidays.status,categ_id:0
+msgid ""
+"If you set a meeting type, OpenERP will create a meeting in the calendar "
+"once a leave is validated."
+msgstr ""
+
+#. module: hr_holidays
+#: field:hr.holidays,linked_request_ids:0
+msgid "Linked Requests"
+msgstr ""
+
+#. module: hr_holidays
+#: field:hr.holidays,parent_id:0
+msgid "Parent"
+msgstr ""
+
+#. module: hr_holidays
+#: selection:hr.holidays.status,color_name:0
+msgid "Lavender"
+msgstr ""
+
+#. module: hr_holidays
+#: xsl:holidays.summary:0
+#: view:hr.holidays:0
+msgid "Month"
+msgstr ""
+
+#. module: hr_holidays
+#: view:hr.holidays:0
+#: model:ir.actions.act_window,name:hr_holidays.open_ask_holidays
+#: model:ir.ui.menu,name:hr_holidays.menu_open_ask_holidays_new
+msgid "Leave Requests"
+msgstr ""
+
+#. module: hr_holidays
+#: field:hr.holidays.status,limit:0
+msgid "Allow to Override Limit"
+msgstr ""
+
+#. module: hr_holidays
+#: view:hr.holidays.summary.employee:0
+#: model:ir.actions.act_window,name:hr_holidays.action_hr_holidays_summary_employee
+msgid "Employee's Holidays"
+msgstr ""
+
+#. module: hr_holidays
+#: view:hr.holidays:0
+#: field:hr.holidays,category_id:0
+msgid "Category"
+msgstr ""
+
+#. module: hr_holidays
+#: help:hr.holidays.status,max_leaves:0
+msgid ""
+"This value is given by the sum of all holidays requests with a positive "
+"value."
+msgstr ""
+
+#. module: hr_holidays
+#: view:board.board:0
+msgid "All Employee Leaves"
+msgstr ""
+
+#. module: hr_holidays
+#: selection:hr.holidays.status,color_name:0
+msgid "Light Coral"
+msgstr ""
+
+#. module: hr_holidays
+#: view:hr.holidays.summary.dept:0
+#: model:ir.actions.act_window,name:hr_holidays.action_hr_holidays_summary_dept
+msgid "Holidays by Department"
+msgstr ""
+
+#. module: hr_holidays
+#: selection:hr.holidays.status,color_name:0
+msgid "Black"
+msgstr ""
+
+#. module: hr_holidays
+#: model:ir.actions.act_window,name:hr_holidays.hr_holidays_leaves_assign_legal
+msgid "Allocate Leaves for Employees"
+msgstr ""
+
+#. module: hr_holidays
+#: field:resource.calendar.leaves,holiday_id:0
+msgid "Holiday"
+msgstr ""
+
+#. module: hr_holidays
+#: field:hr.holidays,case_id:0
+#: field:hr.holidays.status,categ_id:0
+msgid "Meeting"
+msgstr ""
+
+#. module: hr_holidays
+#: selection:hr.holidays.status,color_name:0
+msgid "Ivory"
+msgstr ""
+
+#. module: hr_holidays
+#: selection:hr.holidays.summary.dept,holiday_type:0
+#: selection:hr.holidays.summary.employee,holiday_type:0
+msgid "Both Validated and Confirmed"
+msgstr ""
+
+#. module: hr_holidays
+#: field:hr.holidays.status,leaves_taken:0
+msgid "Leaves Already Taken"
+msgstr ""
+
+#. module: hr_holidays
+#: field:hr.holidays,user_id:0
+#: field:hr.holidays.remaining.leaves.user,user_id:0
+msgid "User"
+msgstr ""
+
+#. module: hr_holidays
+#: sql_constraint:hr.holidays:0
+msgid "The start date must be before the end date !"
+msgstr ""
+
+#. module: hr_holidays
+#: field:hr.holidays.status,active:0
+msgid "Active"
+msgstr ""
+
+#. module: hr_holidays
+#: constraint:hr.employee:0
+msgid "Error ! You cannot create recursive Hierarchy of Employees."
+msgstr ""
+
+#. module: hr_holidays
+#: view:hr.employee:0
+#: field:hr.employee,remaining_leaves:0
+msgid "Remaining Legal Leaves"
+msgstr ""
+
+#. module: hr_holidays
+#: field:hr.holidays,manager_id:0
+msgid "First Approval"
+msgstr ""
+
+#. module: hr_holidays
+#: model:hr.holidays.status,name:hr_holidays.holiday_status_unpaid
+msgid "Unpaid"
+msgstr ""
+
+#. module: hr_holidays
+#: view:hr.holidays:0
+#: model:ir.actions.act_window,name:hr_holidays.open_company_allocation
+#: model:ir.ui.menu,name:hr_holidays.menu_open_company_allocation
+msgid "Leaves Summary"
+msgstr ""
+
+#. module: hr_holidays
+#: view:hr.holidays:0
+msgid "Holidays during last month"
+msgstr ""
+
+#. module: hr_holidays
+#: code:addons/hr_holidays/wizard/hr_holidays_summary_department.py:44
+#, python-format
+msgid "Error"
+msgstr ""
+
+#. module: hr_holidays
+#: view:hr.employee:0
+msgid "Assign Leaves"
+msgstr ""
+
+#. module: hr_holidays
+#: selection:hr.holidays.status,color_name:0
+msgid "Light Blue"
+msgstr ""
+
+#. module: hr_holidays
+#: view:hr.holidays:0
+msgid "My Department Leaves"
+msgstr ""
+
+#. module: hr_holidays
+#: field:hr.employee,current_leave_state:0
+msgid "Current Leave Status"
+msgstr ""
+
+#. module: hr_holidays
+#: field:hr.holidays,type:0
+msgid "Request Type"
+msgstr ""
+
+#. module: hr_holidays
+#: help:hr.holidays.status,active:0
+msgid ""
+"If the active field is set to false, it will allow you to hide the leave "
+"type without removing it."
+msgstr ""
+
+#. module: hr_holidays
+#: view:hr.holidays.status:0
+msgid "Misc"
+msgstr ""
+
+#. module: hr_holidays
+#: view:hr.holidays:0
+msgid "General"
+msgstr ""
+
+#. module: hr_holidays
+#: model:hr.holidays.status,name:hr_holidays.holiday_status_comp
+msgid "Compensatory Days"
+msgstr ""
+
+#. module: hr_holidays
+#: view:hr.holidays:0
+#: field:hr.holidays,notes:0
+msgid "Reasons"
+msgstr ""
+
+#. module: hr_holidays
+#: model:ir.actions.act_window,name:hr_holidays.action_hr_available_holidays_report
+#: model:ir.ui.menu,name:hr_holidays.menu_hr_available_holidays_report_tree
+msgid "Leaves Analysis"
+msgstr ""
+
+#. module: hr_holidays
+#: view:hr.holidays.summary.dept:0
+#: view:hr.holidays.summary.employee:0
+msgid "Cancel"
+msgstr ""
+
+#. module: hr_holidays
+#: help:hr.holidays.status,color_name:0
+msgid ""
+"This color will be used in the leaves summary located in Reporting\\Leaves "
+"by Departement"
+msgstr ""
+
+#. module: hr_holidays
+#: view:hr.holidays:0
+#: selection:hr.holidays.summary.dept,holiday_type:0
+#: selection:hr.holidays.summary.employee,holiday_type:0
+msgid "Validated"
+msgstr ""
+
+#. module: hr_holidays
+#: view:hr.holidays:0
+#: selection:hr.holidays,type:0
+msgid "Allocation Request"
+msgstr ""
+
+#. module: hr_holidays
+#: model:ir.model,name:hr_holidays.model_resource_calendar_leaves
+msgid "Leave Detail"
+msgstr ""
+
+#. module: hr_holidays
+#: field:hr.holidays,double_validation:0
+#: field:hr.holidays.status,double_validation:0
+msgid "Apply Double Validation"
+msgstr ""
+
+#. module: hr_holidays
+#: view:hr.holidays.summary.dept:0
+#: view:hr.holidays.summary.employee:0
+msgid "Print"
+msgstr ""
+
+#. module: hr_holidays
+#: view:hr.holidays.status:0
+msgid "Details"
+msgstr ""
+
+#. module: hr_holidays
+#: view:hr.holidays:0
+#: model:ir.actions.act_window,name:hr_holidays.action_hr_holidays_leaves_by_month
+msgid "My Leaves"
+msgstr ""
+
+#. module: hr_holidays
+#: selection:hr.holidays,holiday_type:0
+msgid "By Employee Category"
+msgstr ""
+
+#. module: hr_holidays
+#: view:hr.holidays:0
+#: selection:hr.holidays,type:0
+msgid "Leave Request"
+msgstr ""
+
+#. module: hr_holidays
+#: field:hr.holidays,name:0
+msgid "Description"
+msgstr ""
+
+#. module: hr_holidays
+#: model:hr.holidays.status,name:hr_holidays.holiday_status_cl
+msgid "Legal Leaves"
+msgstr ""
+
+#. module: hr_holidays
+#: sql_constraint:hr.holidays:0
+msgid "The number of days must be greater than 0 !"
+msgstr ""
+
+#. module: hr_holidays
+#: help:hr.holidays,holiday_type:0
+msgid ""
+"By Employee: Allocation/Request for individual Employee, By Employee "
+"Category: Allocation/Request for group of employees in category"
+msgstr ""
+
+#. module: hr_holidays
+#: code:addons/hr_holidays/hr_holidays.py:199
+#, python-format
+msgid "You cannot delete a leave which is not in draft state !"
+msgstr ""
+
+#. module: hr_holidays
+#: view:hr.holidays:0
+msgid "Search Leave"
+msgstr "Пребарај Отсуство"
+
+#. module: hr_holidays
+#: field:hr.holidays.summary.employee,holiday_type:0
+msgid "Select Holiday Type"
+msgstr ""
+
+#. module: hr_holidays
+#: field:hr.holidays.remaining.leaves.user,no_of_leaves:0
+msgid "Remaining leaves"
+msgstr ""
+
+#. module: hr_holidays
+#: field:hr.holidays.summary.dept,depts:0
+msgid "Department(s)"
+msgstr ""
+
+#. module: hr_holidays
+#: field:hr.holidays,manager_id2:0
+msgid "Second Approval"
+msgstr ""
+
+#. module: hr_holidays
+#: field:hr.holidays,date_to:0
+msgid "End Date"
+msgstr ""
+
+#. module: hr_holidays
+#: model:hr.holidays.status,name:hr_holidays.holiday_status_sl
+msgid "Sick Leaves"
+msgstr ""
+
+#. module: hr_holidays
+#: help:hr.holidays.status,limit:0
+msgid ""
+"If you select this checkbox, the system allows the employees to take more "
+"leaves than the available ones for this type."
+msgstr ""
+
+#. module: hr_holidays
+#: help:hr.holidays.status,leaves_taken:0
+msgid ""
+"This value is given by the sum of all holidays requests with a negative "
+"value."
+msgstr ""
+
+#. module: hr_holidays
+#: selection:hr.holidays.status,color_name:0
+msgid "Violet"
+msgstr ""
+
+#. module: hr_holidays
+#: model:ir.actions.act_window,help:hr_holidays.hr_holidays_leaves_assign_legal
+msgid ""
+"You can assign remaining Legal Leaves for each employee, OpenERP will "
+"automatically create and validate allocation requests."
+msgstr ""
+
+#. module: hr_holidays
+#: field:hr.holidays.status,max_leaves:0
+msgid "Maximum Allowed"
+msgstr ""
+
+#. module: hr_holidays
+#: help:hr.holidays,manager_id2:0
+msgid ""
+"This area is automaticly filled by the user who validate the leave with "
+"second level (If Leave type need second validation)"
+msgstr ""
+
+#. module: hr_holidays
+#: view:hr.holidays:0
+msgid "Mode"
+msgstr ""
+
+#. module: hr_holidays
+#: model:ir.model,name:hr_holidays.model_hr_holidays_summary_dept
+msgid "HR Holidays Summary Report By Department"
+msgstr ""
+
+#. module: hr_holidays
+#: view:hr.holidays:0
+msgid "Approve"
+msgstr ""
+
+#. module: hr_holidays
+#: view:hr.holidays:0
+#: field:hr.holidays,date_from:0
+msgid "Start Date"
+msgstr ""
+
+#. module: hr_holidays
+#: view:hr.holidays:0
+#: model:ir.actions.act_window,name:hr_holidays.open_allocation_holidays
+#: model:ir.ui.menu,name:hr_holidays.menu_open_allocation_holidays
+msgid "Allocation Requests"
+msgstr ""
+
+#. module: hr_holidays
+#: selection:hr.holidays.status,color_name:0
+msgid "Light Yellow"
+msgstr ""
+
+#. module: hr_holidays
+#: selection:hr.holidays.status,color_name:0
+msgid "Light Pink"
+msgstr ""
+
+#. module: hr_holidays
+#: view:hr.holidays:0
+msgid "Manager"
+msgstr ""
+
+#. module: hr_holidays
+#: view:hr.holidays:0
+msgid "To Confirm"
+msgstr ""
+
+#. module: hr_holidays
+#: view:hr.holidays:0
+msgid "Year"
+msgstr ""
+
+#. module: hr_holidays
+#: view:hr.holidays:0
+msgid "To Approve"
+msgstr ""
diff --git a/addons/hr_recruitment/hr_recruitment.py b/addons/hr_recruitment/hr_recruitment.py
index 31959d7e9c7..ccfcae0d26e 100644
--- a/addons/hr_recruitment/hr_recruitment.py
+++ b/addons/hr_recruitment/hr_recruitment.py
@@ -26,6 +26,7 @@ from base_status.base_stage import base_stage
from datetime import datetime
from osv import fields, osv
from tools.translate import _
+from tools import html2plaintext
AVAILABLE_STATES = [
('draft', 'New'),
@@ -327,9 +328,10 @@ class hr_applicant(base_stage, osv.Model):
This override updates the document according to the email.
"""
if custom_values is None: custom_values = {}
+ desc = html2plaintext(msg.get('body')) if msg.get('body') else ''
custom_values.update({
'name': msg.get('subject') or _("No Subject"),
- 'description': msg.get('body'),
+ 'description': desc,
'email_from': msg.get('from'),
'email_cc': msg.get('cc'),
'user_id': False,
diff --git a/addons/hr_recruitment/hr_recruitment_view.xml b/addons/hr_recruitment/hr_recruitment_view.xml
index 08e5f31a2d2..d48086f5663 100644
--- a/addons/hr_recruitment/hr_recruitment_view.xml
+++ b/addons/hr_recruitment/hr_recruitment_view.xml
@@ -208,7 +208,7 @@
-
+
diff --git a/addons/mail/data/mail_demo.xml b/addons/mail/data/mail_demo.xml
index 1350fd33547..7bda32c002b 100644
--- a/addons/mail/data/mail_demo.xml
+++ b/addons/mail/data/mail_demo.xml
@@ -42,10 +42,5 @@ This month you also get 250 EUR of eco-vouchers if you have been in the company
comment
-
- mail.catchall.domain
- demo.openerp.com
-
-
diff --git a/addons/mail/mail_mail.py b/addons/mail/mail_mail.py
index e9a569a8192..741184ef7a3 100644
--- a/addons/mail/mail_mail.py
+++ b/addons/mail/mail_mail.py
@@ -63,10 +63,12 @@ class mail_mail(osv.Model):
}
def _get_default_from(self, cr, uid, context=None):
- cur = self.pool.get('res.users').browse(cr, uid, uid, context=context)
- if not cur.alias_domain:
- raise osv.except_osv(_('Invalid Action!'), _('Unable to send email, set an alias domain in your server settings.'))
- return cur.alias_name + '@' + cur.alias_domain
+ this = self.pool.get('res.users').browse(cr, uid, uid, context=context)
+ if this.alias_domain:
+ return '%s@%s' % (this.alias_name, this.alias_domain)
+ elif this.email:
+ return this.email
+ raise osv.except_osv(_('Invalid Action!'), _("Unable to send email, please configure the sender's email address or alias."))
_defaults = {
'state': 'outgoing',
diff --git a/addons/mail/mail_message.py b/addons/mail/mail_message.py
index 9c873a31097..7e77b2f2e8c 100644
--- a/addons/mail/mail_message.py
+++ b/addons/mail/mail_message.py
@@ -180,7 +180,6 @@ class mail_message(osv.Model):
fields allow to have the foreign record name without having
to check external access rights).
"""
- child_nbr = len(msg.child_ids)
has_voted = False
vote_ids = self.pool.get('res.users').name_get(cr, SUPERUSER_ID, [user.id for user in msg.vote_user_ids], context=context)
for vote in vote_ids:
@@ -194,7 +193,7 @@ class mail_message(osv.Model):
try:
author_id = self.pool.get('res.partner').name_get(cr, uid, [msg.author_id.id], context=context)[0]
is_author = uid == msg.author_id.user_ids[0].id
- except (orm.except_orm, osv.except_osv):
+ except Exception:
author_id = False
is_author = False
try:
@@ -502,7 +501,7 @@ class mail_message(osv.Model):
def create(self, cr, uid, values, context=None):
if not values.get('message_id') and values.get('res_id') and values.get('model'):
- values['message_id'] = tools.generate_tracking_message_id('%(model)s-%(res_id)s' % values)
+ values['message_id'] = tools.generate_tracking_message_id('%(res_id)s-%(model)s' % values)
newid = super(mail_message, self).create(cr, uid, values, context)
self._notify(cr, SUPERUSER_ID, newid, context=context)
return newid
diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py
index 9f53b4c08b1..529ee8c65cc 100644
--- a/addons/mail/mail_thread.py
+++ b/addons/mail/mail_thread.py
@@ -39,63 +39,6 @@ _logger = logging.getLogger(__name__)
def decode_header(message, header, separator=' '):
return separator.join(map(decode, message.get_all(header, [])))
-class many2many_reference(fields.many2many):
- """ many2many_reference manages many2many fields where one id is found
- by a reference-like key (a char column in addition to the foreign id).
- The reference_column attribute on the many2many fields is used;
- if not defined, ``res_model`` is used. """
-
- def _get_query_and_where_params(self, cr, model, ids, values, where_params):
- """ Add in where condition like mail_followers.res_model = 'crm.lead' """
- reference_column = self.reference_column if self.reference_column else 'res_model'
- values.update(reference_column=reference_column, reference_value=model._name)
- query = 'SELECT %(rel)s.%(id2)s, %(rel)s.%(id1)s \
- FROM %(rel)s, %(from_c)s \
- WHERE %(rel)s.%(id1)s IN %%s \
- AND %(rel)s.%(id2)s = %(tbl)s.id \
- AND %(rel)s.%(reference_column)s = \'%(reference_value)s\' \
- %(where_c)s \
- %(order_by)s \
- %(limit)s \
- OFFSET %(offset)d' \
- % values
- return query, where_params
-
- def set(self, cr, model, id, name, values, user=None, context=None):
- """ Override to add the reference field in queries. """
- if not values: return
- rel, id1, id2 = self._sql_names(model)
- obj = model.pool.get(self._obj)
- # reference column name: given by attribute or res_model
- reference_column = self.reference_column if self.reference_column else 'res_model'
- for act in values:
- if not (isinstance(act, list) or isinstance(act, tuple)) or not act:
- continue
- if act[0] == 0:
- idnew = obj.create(cr, user, act[2], context=context)
- cr.execute('INSERT INTO '+rel+' ('+id1+','+id2+','+reference_column+') VALUES (%s,%s,%s)', (id, idnew, model._name))
- elif act[0] == 3:
- cr.execute('DELETE FROM '+rel+' WHERE '+id1+'=%s AND '+id2+'=%s AND '+reference_column+'=%s', (id, act[1], model._name))
- elif act[0] == 4:
- # following queries are in the same transaction - so should be relatively safe
- cr.execute('SELECT 1 FROM '+rel+' WHERE '+id1+'=%s AND '+id2+'=%s AND '+reference_column+'=%s', (id, act[1], model._name))
- if not cr.fetchone():
- cr.execute('INSERT INTO '+rel+' ('+id1+','+id2+','+reference_column+') VALUES (%s,%s,%s)', (id, act[1], model._name))
- elif act[0] == 5:
- cr.execute('delete from '+rel+' where '+id1+' = %s AND '+reference_column+'=%s', (id, model._name))
- elif act[0] == 6:
- d1, d2,tables = obj.pool.get('ir.rule').domain_get(cr, user, obj._name, context=context)
- if d1:
- d1 = ' and ' + ' and '.join(d1)
- else:
- d1 = ''
- cr.execute('DELETE FROM '+rel+' WHERE '+id1+'=%s AND '+reference_column+'=%s AND '+id2+' IN (SELECT '+rel+'.'+id2+' FROM '+rel+', '+','.join(tables)+' WHERE '+rel+'.'+id1+'=%s AND '+rel+'.'+id2+' = '+obj._table+'.id '+ d1 +')', [id, model._name, id]+d2)
- for act_nbr in act[2]:
- cr.execute('INSERT INTO '+rel+' ('+id1+','+id2+','+reference_column+') VALUES (%s,%s,%s)', (id, act_nbr, model._name))
- # cases 1, 2: performs write and unlink -> default implementation is ok
- else:
- return super(many2many_reference, self).set(cr, model, id, name, values, user, context)
-
class mail_thread(osv.AbstractModel):
''' mail_thread model is meant to be inherited by any model that needs to
@@ -186,6 +129,62 @@ class mail_thread(osv.AbstractModel):
res[notif.message_id.res_id] = True
return [('id', 'in', res.keys())]
+ def _get_followers(self, cr, uid, ids, name, arg, context=None):
+ fol_obj = self.pool.get('mail.followers')
+ fol_ids = fol_obj.search(cr, SUPERUSER_ID, [('res_model', '=', self._name), ('res_id', 'in', ids)])
+ res = dict((res_id, []) for res_id in ids)
+ for fol in fol_obj.browse(cr, SUPERUSER_ID, fol_ids):
+ res[fol.res_id].append(fol.partner_id.id)
+ return res
+
+ def _set_followers(self, cr, uid, id, name, value, arg, context=None):
+ partner_obj = self.pool.get('res.partner')
+ fol_obj = self.pool.get('mail.followers')
+
+ # read the old set of followers, and determine the new set of followers
+ fol_ids = fol_obj.search(cr, SUPERUSER_ID, [('res_model', '=', self._name), ('res_id', '=', id)])
+ old = set(fol.partner_id.id for fol in fol_obj.browse(cr, SUPERUSER_ID, fol_ids))
+ new = set(old)
+
+ for command in value:
+ if isinstance(command, (int, long)):
+ new.add(command)
+ elif command[0] == 0:
+ new.add(partner_obj.create(cr, uid, command[2], context=context))
+ elif command[0] == 1:
+ partner_obj.write(cr, uid, [command[1]], command[2], context=context)
+ new.add(command[1])
+ elif command[0] == 2:
+ partner_obj.unlink(cr, uid, [command[1]], context=context)
+ new.discard(command[1])
+ elif command[0] == 3:
+ new.discard(command[1])
+ elif command[0] == 4:
+ new.add(command[1])
+ elif command[0] == 5:
+ new.clear()
+ elif command[0] == 6:
+ new = set(command[2])
+
+ # remove partners that are no longer followers
+ fol_ids = fol_obj.search(cr, SUPERUSER_ID,
+ [('res_model', '=', self._name), ('res_id', '=', id), ('partner_id', 'not in', list(new))])
+ fol_obj.unlink(cr, SUPERUSER_ID, fol_ids)
+
+ # add new followers
+ for partner_id in new - old:
+ fol_obj.create(cr, SUPERUSER_ID, {'res_model': self._name, 'res_id': id, 'partner_id': partner_id})
+
+ def _search_followers(self, cr, uid, obj, name, args, context):
+ fol_obj = self.pool.get('mail.followers')
+ res = []
+ for field, operator, value in args:
+ assert field == name
+ fol_ids = fol_obj.search(cr, SUPERUSER_ID, [('res_model', '=', self._name), ('partner_id', operator, value)])
+ res_ids = [fol.res_id for fol in fol_obj.browse(cr, SUPERUSER_ID, fol_ids)]
+ res.append(('id', 'in', res_ids))
+ return res
+
_columns = {
'message_is_follower': fields.function(_get_subscription_data,
type='boolean', string='Is a Follower', multi='_get_subscription_data,'),
@@ -194,9 +193,8 @@ class mail_thread(osv.AbstractModel):
help="Holds data about the subtypes. The content of this field "\
"is a structure holding the current model subtypes, and the "\
"current document followed subtypes."),
- 'message_follower_ids': many2many_reference('res.partner',
- 'mail_followers', 'res_id', 'partner_id',
- reference_column='res_model', string='Followers'),
+ 'message_follower_ids': fields.function(_get_followers, fnct_inv=_set_followers,
+ fnct_search=_search_followers, type='many2many', obj='res.partner', string='Followers'),
'message_comment_ids': fields.one2many('mail.message', 'res_id',
domain=lambda self: [('model', '=', self._name), ('type', 'in', ('comment', 'email'))],
string='Comments and emails',
@@ -422,12 +420,12 @@ class mail_thread(osv.AbstractModel):
model_pool = self.pool.get(model)
assert thread_id and hasattr(model_pool, 'message_update') or hasattr(model_pool, 'message_new'), \
"Undeliverable mail with Message-Id %s, model %s does not accept incoming emails" % \
- (msg['message-id'], model)
+ (msg['message_id'], model)
if thread_id and hasattr(model_pool, 'message_update'):
model_pool.message_update(cr, user_id, [thread_id], msg, context=context)
else:
thread_id = model_pool.message_new(cr, user_id, msg, custom_values, context=context)
- self.message_post(cr, uid, [thread_id], context=context, **msg)
+ model_pool.message_post(cr, uid, [thread_id], context=context, **msg)
return thread_id
def message_new(self, cr, uid, msg_dict, custom_values=None, context=None):
@@ -538,7 +536,7 @@ class mail_thread(osv.AbstractModel):
field may not be present if missing in original
message::
- { 'message-id': msg_id,
+ { 'message_id': msg_id,
'subject': subject,
'from': from,
'to': to,
diff --git a/addons/mail/res_config.py b/addons/mail/res_config.py
index ce2f75687ba..91606e1c720 100644
--- a/addons/mail/res_config.py
+++ b/addons/mail/res_config.py
@@ -19,6 +19,8 @@
#
##############################################################################
+import urlparse
+
from openerp.osv import osv, fields
class project_configuration(osv.TransientModel):
@@ -31,9 +33,16 @@ class project_configuration(osv.TransientModel):
}
def get_default_alias_domain(self, cr, uid, ids, context=None):
- return {'alias_domain': self.pool.get("ir.config_parameter").get_param(cr, uid, "mail.catchall.domain", context=context)}
+ alias_domain = self.pool.get("ir.config_parameter").get_param(cr, uid, "mail.catchall.domain", context=context)
+ if not alias_domain:
+ domain = self.pool.get("ir.config_parameter").get_param(cr, uid, "web.base.url", context=context)
+ try:
+ alias_domain = urlparse.urlsplit(domain).netloc.split(':')[0]
+ except Exception:
+ pass
+ return {'alias_domain': alias_domain}
def set_alias_domain(self, cr, uid, ids, context=None):
config_parameters = self.pool.get("ir.config_parameter")
for record in self.browse(cr, uid, ids, context=context):
- config_parameters.set_param(cr, uid, "mail.catchall.domain", record.alias_domain or '', context=context)
\ No newline at end of file
+ config_parameters.set_param(cr, uid, "mail.catchall.domain", record.alias_domain or '', context=context)
diff --git a/addons/mail/res_users.py b/addons/mail/res_users.py
index 545198f4c7f..f870604b03a 100644
--- a/addons/mail/res_users.py
+++ b/addons/mail/res_users.py
@@ -110,9 +110,19 @@ class res_users(osv.Model):
alias_pool.unlink(cr, uid, alias_ids, context=context)
return res
- def message_post(self, cr, uid, thread_id, **kwargs):
- partner_id = self.pool.get('res.users').browse(cr, uid, thread_id)[0].partner_id.id
- return self.pool.get('res.partner').message_post(cr, uid, partner_id, **kwargs)
+ def message_post(self, cr, uid, thread_id, context=None, **kwargs):
+ assert thread_id, "res.users does not support posting global messages"
+ if context and 'thread_model' in context:
+ context['thread_model'] = 'res.partner'
+ if isinstance(thread_id, (list, tuple)):
+ thread_id = thread_id[0]
+ partner_id = self.pool.get('res.users').browse(cr, uid, thread_id).partner_id.id
+ return self.pool.get('res.partner').message_post(cr, uid, partner_id, context=context, **kwargs)
+
+ def message_update(self, cr, uid, ids, msg_dict, update_vals=None, context=None):
+ partner_id = self.pool.get('res.users').browse(cr, uid, ids)[0].partner_id.id
+ return self.pool.get('res.partner').message_update(cr, uid, [partner_id], msg_dict,
+ update_vals=update_vals, context=context)
class res_users_mail_group(osv.Model):
""" Update of res.users class
diff --git a/addons/mail/security/mail_security.xml b/addons/mail/security/mail_security.xml
index b503565bd11..be3bb667628 100644
--- a/addons/mail/security/mail_security.xml
+++ b/addons/mail/security/mail_security.xml
@@ -7,7 +7,7 @@
Mail.group: access only public and joined groups
- ['|', '|', ('public', '=', 'public'), ('message_follower_ids', 'in', [user.id]), '&', ('public','=','groups'), ('group_public_id','in', [x.id for x in user.groups_id])]
+ ['|', '|', ('public', '=', 'public'), ('message_follower_ids', 'in', [user.partner_id.id]), '&', ('public','=','groups'), ('group_public_id','in', [g.id for g in user.groups_id])]
diff --git a/addons/portal/tests/test_portal.py b/addons/portal/tests/test_portal.py
index 653a05d40aa..83b10b39c28 100644
--- a/addons/portal/tests/test_portal.py
+++ b/addons/portal/tests/test_portal.py
@@ -33,6 +33,7 @@ class test_portal(test_mail.TestMailMockups):
self.mail_group = self.registry('mail.group')
self.mail_mail = self.registry('mail.mail')
self.mail_message = self.registry('mail.message')
+ self.mail_invite = self.registry('mail.wizard.invite')
self.res_users = self.registry('res.users')
self.res_partner = self.registry('res.partner')
@@ -41,19 +42,20 @@ class test_portal(test_mail.TestMailMockups):
{'name': 'Pigs', 'description': 'Fans of Pigs, unite !'})
# Find Portal group
- group_portal_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'portal', 'group_portal')
- self.group_portal_id = group_portal_ref and group_portal_ref[1] or False
+ group_portal = self.registry('ir.model.data').get_object(cr, uid, 'portal', 'group_portal')
+ self.group_portal_id = group_portal.id
# Create Chell (portal user)
self.user_chell_id = self.res_users.create(cr, uid, {'name': 'Chell Gladys', 'login': 'chell', 'groups_id': [(6, 0, [self.group_portal_id])]})
- self.user_chell = self.res_users.browse(cr, uid, self.user_chell_id)
- self.partner_chell_id = self.user_chell.partner_id.id
+ user_chell = self.res_users.browse(cr, uid, self.user_chell_id)
+ self.partner_chell_id = user_chell.partner_id.id
+
+ # Set an email address for the user running the tests, used as Sender for outgoing mails
+ self.res_users.write(cr, uid, uid, {'email': 'test@localhost'})
def test_00_access_rights(self):
""" Test basic mail_message and mail_group access rights for portal users. """
cr, uid = self.cr, self.uid
- partner_chell_id = self.partner_chell_id
- user_chell_id = self.user_chell_id
# Prepare group: Pigs (portal)
self.mail_group.message_post(cr, uid, self.group_pigs_id, body='Message')
@@ -64,56 +66,56 @@ class test_portal(test_mail.TestMailMockups):
# ----------------------------------------
# Do: Chell reads Pigs messages, ok because restricted to portal group
- message_ids = self.mail_group.read(cr, user_chell_id, self.group_pigs_id, ['message_ids'])['message_ids']
- self.mail_message.read(cr, user_chell_id, message_ids)
+ message_ids = self.mail_group.read(cr, self.user_chell_id, self.group_pigs_id, ['message_ids'])['message_ids']
+ self.mail_message.read(cr, self.user_chell_id, message_ids)
+
# Do: Chell posts a message on Pigs, crash because can not write on group or is not in the followers
- self.assertRaises(except_orm,
- self.mail_group.message_post,
- cr, user_chell_id, self.group_pigs_id, body='Message')
+ with self.assertRaises(except_orm):
+ self.mail_group.message_post(cr, self.user_chell_id, self.group_pigs_id, body='Message')
+
# Do: Chell is added to Pigs followers
- self.mail_group.message_subscribe(cr, uid, [self.group_pigs_id], [partner_chell_id])
+ self.mail_group.message_subscribe(cr, uid, [self.group_pigs_id], [self.partner_chell_id])
+
# Test: Chell posts a message on Pigs, ok because in the followers
- self.mail_group.message_post(cr, user_chell_id, self.group_pigs_id, body='Message')
+ self.mail_group.message_post(cr, self.user_chell_id, self.group_pigs_id, body='Message')
def test_50_mail_invite(self):
cr, uid = self.cr, self.uid
user_admin = self.res_users.browse(cr, uid, uid)
- self.mail_invite = self.registry('mail.wizard.invite')
base_url = self.registry('ir.config_parameter').get_param(cr, uid, 'web.base.url', default='')
- portal_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'portal', 'group_portal')
- portal_id = portal_ref and portal_ref[1] or False
-
# 0 - Admin
- p_a_id = user_admin.partner_id.id
+ partner_admin_id = user_admin.partner_id.id
# 1 - Bert Tartopoils, with email, should receive emails for comments and emails
- p_b_id = self.res_partner.create(cr, uid, {'name': 'Bert Tartopoils', 'email': 'b@b'})
+ partner_bert_id = self.res_partner.create(cr, uid, {'name': 'Bert Tartopoils', 'email': 'b@b'})
# ----------------------------------------
- # CASE1: generated URL
+ # CASE: invite Bert to follow Pigs
# ----------------------------------------
- url = self.mail_mail._generate_signin_url(cr, uid, p_b_id, portal_id, 1234)
- self.assertEqual(url, base_url + '/login?action=signin&partner_id=%s&group=%s&key=%s' % (p_b_id, portal_id, 1234),
- 'generated signin URL incorrect')
-
- # ----------------------------------------
- # CASE2: invite Bert
- # ----------------------------------------
-
- _sent_email_subject = 'Invitation to follow Pigs'
- _sent_email_body = append_content_to_html('
You have been invited to follow Pigs.
', url)
-
# Do: create a mail_wizard_invite, validate it
self._init_mock_build_email()
- mail_invite_id = self.mail_invite.create(cr, uid, {'partner_ids': [(4, p_b_id)]}, {'default_res_model': 'mail.group', 'default_res_id': self.group_pigs_id})
+ context = {'default_res_model': 'mail.group', 'default_res_id': self.group_pigs_id}
+ mail_invite_id = self.mail_invite.create(cr, uid, {'partner_ids': [(4, partner_bert_id)]}, context)
self.mail_invite.add_followers(cr, uid, [mail_invite_id])
- group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
# Test: Pigs followers should contain Admin and Bert
+ group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
follower_ids = [follower.id for follower in group_pigs.message_follower_ids]
- self.assertEqual(set(follower_ids), set([p_a_id, p_b_id]), 'Pigs followers after invite is incorrect')
- # Test: sent email subject, body
+ self.assertEqual(set(follower_ids), set([partner_admin_id, partner_bert_id]), 'Pigs followers after invite is incorrect')
+
+ # Test: partner must have been prepared for signup
+ partner_bert = self.res_partner.browse(cr, uid, partner_bert_id)
+ self.assertTrue(partner_bert.signup_valid, 'partner has not been prepared for signup')
+ self.assertTrue(base_url in partner_bert.signup_url, 'signup url is incorrect')
+ self.assertTrue(cr.dbname in partner_bert.signup_url, 'signup url is incorrect')
+ self.assertTrue(partner_bert.signup_token in partner_bert.signup_url, 'signup url is incorrect')
+
+ # Test: (pretend to) send email and check subject, body
self.assertEqual(len(self._build_email_kwargs_list), 1, 'sent email number incorrect, should be only for Bert')
for sent_email in self._build_email_kwargs_list:
- self.assertEqual(sent_email.get('subject'), _sent_email_subject, 'sent email subject incorrect')
- self.assertEqual(sent_email.get('body'), _sent_email_body, 'sent email body incorrect')
+ self.assertEqual(sent_email.get('subject'), 'Invitation to follow Pigs',
+ 'subject of invitation email is incorrect')
+ self.assertTrue('You have been invited to follow Pigs' in sent_email.get('body'),
+ 'body of invitation email is incorrect')
+ self.assertTrue(partner_bert.signup_url in sent_email.get('body'),
+ 'body of invitation email does not contain signup url')
diff --git a/addons/portal/wizard/portal_wizard.py b/addons/portal/wizard/portal_wizard.py
index 3cc706c2129..c94d6509f63 100644
--- a/addons/portal/wizard/portal_wizard.py
+++ b/addons/portal/wizard/portal_wizard.py
@@ -30,17 +30,19 @@ from openerp import SUPERUSER_ID
from base.res.res_partner import _lang_get
_logger = logging.getLogger(__name__)
-# welcome/goodbye email sent to portal users
+# welcome email sent to portal users
# (note that calling '_' has no effect except exporting those strings for translation)
WELCOME_EMAIL_SUBJECT = _("Your OpenERP account at %(company)s")
WELCOME_EMAIL_BODY = _("""Dear %(name)s,
-You have been given access to %(portal)s at %(url)s.
+You have been given access to %(portal)s.
Your login account data is:
Database: %(db)s
-User: %(login)s
-Password: %(password)s
+Username: %(login)s
+
+In order to complete the signin process, click on the following url:
+%(url)s
%(welcome_message)s
@@ -49,28 +51,10 @@ OpenERP - Open Source Business Applications
http://www.openerp.com
""")
-GOODBYE_EMAIL_SUBJECT = _("Your OpenERP account at %(company)s")
-GOODBYE_EMAIL_BODY = _("""Dear %(name)s,
-
-Your access to %(portal)s has been withdrawn.
-
-%(goodbye_message)s
-
---
-OpenERP - Open Source Business Applications
-http://www.openerp.com
-""")
-
-# character sets for passwords, excluding 0, O, o, 1, I, l
-_PASSU = 'ABCDEFGHIJKLMNPQRSTUVWXYZ'
-_PASSL = 'abcdefghijkmnpqrstuvwxyz'
-_PASSD = '23456789'
-
def random_password():
- # get 3 uppercase letters, 3 lowercase letters, 2 digits, and shuffle them
- chars = map(random.choice, [_PASSU] * 3 + [_PASSL] * 3 + [_PASSD] * 2)
- random.shuffle(chars)
- return ''.join(chars)
+ # temporary random stuff; user password is reset by signup process
+ chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
+ return ''.join(random.choice(chars) for i in xrange(12))
def extract_email(email):
""" extract the email address from a user-friendly email address """
@@ -92,8 +76,6 @@ class wizard(osv.osv_memory):
'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."),
- 'goodbye_message': fields.text(string='Withdrawal Message',
- help="This text is included in the email sent to users withdrawn from the portal."),
}
def _default_portal(self, cr, uid, context):
@@ -164,6 +146,8 @@ class wizard_user(osv.osv_memory):
user = self._create_user(cr, SUPERUSER_ID, wizard_user, context)
if (not user.active) or (portal not in user.groups_id):
user.write({'active': True, 'groups_id': [(4, portal.id)]})
+ # prepare for the signup process
+ user.partner_id.signup_prepare()
wizard_user = self.browse(cr, SUPERUSER_ID, wizard_user.id, context)
self._send_email(cr, uid, wizard_user, context)
else:
@@ -174,8 +158,6 @@ class wizard_user(osv.osv_memory):
user.write({'groups_id': [(3, portal.id)], 'active': False})
else:
user.write({'groups_id': [(3, portal.id)]})
- wizard_user = self.browse(cr, SUPERUSER_ID, wizard_user.id, context)
- self._send_email(cr, uid, wizard_user, context)
def _retrieve_user(self, cr, uid, wizard_user, context=None):
""" retrieve the (possibly inactive) user corresponding to wizard_user.partner_id
@@ -208,7 +190,7 @@ class wizard_user(osv.osv_memory):
return res_users.browse(cr, uid, user_id, context)
def _send_email(self, cr, uid, wizard_user, context=None):
- """ send notification email to a new/former portal user
+ """ 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
"""
@@ -219,33 +201,23 @@ class wizard_user(osv.osv_memory):
_('You must have an email address in your User Preferences to send emails.'))
# determine subject and body in the portal user's language
- url = self.pool.get('ir.config_parameter').get_param(cr, SUPERUSER_ID, 'web.base.url', context=this_context)
user = self._retrieve_user(cr, SUPERUSER_ID, wizard_user, context)
context = dict(this_context or {}, lang=user.lang)
data = {
'company': this_user.company_id.name,
'portal': wizard_user.wizard_id.portal_id.name,
'welcome_message': wizard_user.wizard_id.welcome_message or "",
- 'goodbye_message': wizard_user.wizard_id.goodbye_message or "",
- 'url': url or _("(missing url)"),
'db': cr.dbname,
+ 'name': user.name,
'login': user.login,
- 'password': user.password,
- 'name': user.name
+ 'url': user.signup_url,
}
- if wizard_user.in_portal:
- subject = _(WELCOME_EMAIL_SUBJECT) % data
- body = _(WELCOME_EMAIL_BODY) % data
- else:
- subject = _(GOODBYE_EMAIL_SUBJECT) % data
- body = _(GOODBYE_EMAIL_BODY) % data
-
mail_mail = self.pool.get('mail.mail')
mail_values = {
'email_from': this_user.email,
'email_to': user.email,
- 'subject': subject,
- 'body_html': '