diff --git a/addons/account/account_invoice_view.xml b/addons/account/account_invoice_view.xml index 26e18271dc2..3c2922ff141 100644 --- a/addons/account/account_invoice_view.xml +++ b/addons/account/account_invoice_view.xml @@ -458,7 +458,6 @@ - diff --git a/addons/account_analytic_default/i18n/hu.po b/addons/account_analytic_default/i18n/hu.po index 557aa2e66f5..8b863f51679 100644 --- a/addons/account_analytic_default/i18n/hu.po +++ b/addons/account_analytic_default/i18n/hu.po @@ -7,15 +7,14 @@ msgstr "" "Project-Id-Version: OpenERP Server 6.0dev\n" "Report-Msgid-Bugs-To: support@openerp.com\n" "POT-Creation-Date: 2012-02-08 00:35+0000\n" -"PO-Revision-Date: 2011-02-01 18:11+0000\n" -"Last-Translator: NOVOTRADE RENDSZERHÁZ ( novotrade.hu ) " -"\n" +"PO-Revision-Date: 2012-10-09 13:58+0000\n" +"Last-Translator: krnkris \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2012-08-28 06:22+0000\n" -"X-Generator: Launchpad (build 15864)\n" +"X-Launchpad-Export-Date: 2012-10-10 04:44+0000\n" +"X-Generator: Launchpad (build 16112)\n" #. module: account_analytic_default #: help:account.analytic.default,partner_id:0 @@ -134,12 +133,14 @@ msgstr "Gyűjtőkód alapértelmezések" #. module: account_analytic_default #: sql_constraint:stock.picking:0 msgid "Reference must be unique per Company!" -msgstr "" +msgstr "Minden vállalkozáshoz egyedi hivatkozás kell!" #. module: account_analytic_default #: view:account.analytic.default:0 msgid "Analytical defaults whose end date is greater than today or None" msgstr "" +"A mai napnál későbbi lejáratú vagy nem létező dátumú elemzések " +"alapértelmezései" #. module: account_analytic_default #: help:account.analytic.default,product_id:0 diff --git a/addons/account_budget/i18n/hu.po b/addons/account_budget/i18n/hu.po index c48392c7c8a..35df6ac7dc9 100644 --- a/addons/account_budget/i18n/hu.po +++ b/addons/account_budget/i18n/hu.po @@ -7,15 +7,14 @@ msgstr "" "Project-Id-Version: OpenERP Server 6.0dev\n" "Report-Msgid-Bugs-To: support@openerp.com\n" "POT-Creation-Date: 2012-02-08 00:35+0000\n" -"PO-Revision-Date: 2011-01-30 16:38+0000\n" -"Last-Translator: NOVOTRADE RENDSZERHÁZ ( novotrade.hu ) " -"\n" +"PO-Revision-Date: 2012-10-09 13:49+0000\n" +"Last-Translator: krnkris \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2012-08-28 06:27+0000\n" -"X-Generator: Launchpad (build 15864)\n" +"X-Launchpad-Export-Date: 2012-10-10 04:44+0000\n" +"X-Generator: Launchpad (build 16112)\n" #. module: account_budget #: field:crossovered.budget,creating_user_id:0 @@ -105,6 +104,7 @@ msgid "Percentage" msgstr "Százalék" #. module: account_budget +#: report:account.analytic.account.budget:0 #: report:crossovered.budget.report:0 msgid "to" msgstr "-" @@ -138,21 +138,24 @@ msgstr "" #: code:addons/account_budget/account_budget.py:119 #, python-format msgid "The Budget '%s' has no accounts!" -msgstr "" +msgstr "A '%s' költségvetésnek nincs számlaszáma!" #. module: account_budget +#: report:account.analytic.account.budget:0 #: report:account.budget:0 #: report:crossovered.budget.report:0 msgid "%" msgstr "%" #. module: account_budget +#: report:account.analytic.account.budget:0 #: report:account.budget:0 #: report:crossovered.budget.report:0 msgid "Description" msgstr "Leírás" #. module: account_budget +#: report:account.analytic.account.budget:0 #: report:crossovered.budget.report:0 msgid "Currency" msgstr "Pénznem" @@ -187,6 +190,7 @@ msgid "Planned Amount" msgstr "Tervezett összeg" #. module: account_budget +#: report:account.analytic.account.budget:0 #: report:account.budget:0 #: report:crossovered.budget.report:0 msgid "Perc(%)" @@ -199,6 +203,7 @@ msgid "Done" msgstr "Kész" #. module: account_budget +#: report:account.analytic.account.budget:0 #: report:account.budget:0 #: report:crossovered.budget.report:0 msgid "Practical Amt" @@ -259,7 +264,7 @@ msgstr "Üzleti terv" #. module: account_budget #: view:crossovered.budget:0 msgid "To Approve Budgets" -msgstr "" +msgstr "Költségvetés jóváhagyása" #. module: account_budget #: code:addons/account_budget/account_budget.py:119 @@ -333,6 +338,7 @@ msgid "Account Budget crossvered summary report" msgstr "Üzleti terv összesítő kimutatás" #. module: account_budget +#: report:account.analytic.account.budget:0 #: report:account.budget:0 #: report:crossovered.budget.report:0 msgid "Theoretical Amt" @@ -359,9 +365,10 @@ msgstr "Nyomtatás" #: view:crossovered.budget:0 #: field:crossovered.budget.lines,theoritical_amount:0 msgid "Theoretical Amount" -msgstr "" +msgstr "Elméleti mennyiség" #. module: account_budget +#: report:account.analytic.account.budget:0 #: field:crossovered.budget.lines,analytic_account_id:0 #: model:ir.model,name:account_budget.model_account_analytic_account msgid "Analytic Account" @@ -373,6 +380,7 @@ msgid "Budget :" msgstr "Üzleti terv :" #. module: account_budget +#: report:account.analytic.account.budget:0 #: report:account.budget:0 #: report:crossovered.budget.report:0 msgid "Planned Amt" @@ -413,6 +421,7 @@ msgid "Error! You can not create recursive analytic accounts." msgstr "Hiba! Nem hozhat létre rekurzív gyűjtőkódokat." #. module: account_budget +#: report:account.analytic.account.budget:0 #: report:account.budget:0 #: report:crossovered.budget.report:0 msgid "Analysis from" @@ -421,7 +430,7 @@ msgstr "Elemzési időszak" #. module: account_budget #: view:crossovered.budget:0 msgid "Draft Budgets" -msgstr "" +msgstr "Előzetes, tervezett költségvetés" #, python-format #~ msgid "The General Budget '%s' has no Accounts!" diff --git a/addons/account_budget/i18n/lo.po b/addons/account_budget/i18n/lo.po index daa39b8a6e6..cbc81b84000 100644 --- a/addons/account_budget/i18n/lo.po +++ b/addons/account_budget/i18n/lo.po @@ -8,14 +8,14 @@ msgstr "" "Project-Id-Version: openobject-addons\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2012-02-08 00:35+0000\n" -"PO-Revision-Date: 2011-01-19 15:50+0000\n" +"PO-Revision-Date: 2012-10-09 16:05+0000\n" "Last-Translator: Brice Muangkhot ສຸພາ ເມືອງໂຄຕ \n" "Language-Team: Lao \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2012-08-28 06:27+0000\n" -"X-Generator: Launchpad (build 15864)\n" +"X-Launchpad-Export-Date: 2012-10-10 04:44+0000\n" +"X-Generator: Launchpad (build 16112)\n" #. module: account_budget #: field:crossovered.budget,creating_user_id:0 @@ -31,7 +31,7 @@ msgstr "ຮັບຮູ້ເເລັວ" #: model:ir.actions.act_window,name:account_budget.open_budget_post_form #: model:ir.ui.menu,name:account_budget.menu_budget_post_form msgid "Budgetary Positions" -msgstr "ຈຸດງົບປະມານ" +msgstr "ຕໍາແຫນ່ງງົບປະມານ" #. module: account_budget #: report:account.budget:0 @@ -105,6 +105,7 @@ msgid "Percentage" msgstr "ເປີເຊັນ" #. module: account_budget +#: report:account.analytic.account.budget:0 #: report:crossovered.budget.report:0 msgid "to" msgstr "ຫາ" @@ -126,6 +127,14 @@ msgid "" "revenue per analytic account and monitor its evolution based on the actuals " "realised during that period." msgstr "" +"ງົບປະມານແມ່ນຄາດຄະເນລາຍໄດ້ຂອງບໍລິສັດຂອງທ່ານແລະຄ່າໃຊ້ຈ່າຍຄາດວ່າຈະສໍາລັບໄລຍະເວລາ" +"ໃນອະນາຄົດໄດ້. ມີງົບປະມານ, " +"ບໍລິສັດສາມາດລະມັດລະວັງເບິ່ງວິທີການເງິນຫຼາຍປານເຂົາເຈົ້າໄດ້ຖືກກິນໃນໄລຍະເວລາໃນໄລ" +"ຍະໃດຫນຶ່ງ, ແລະສະວິທີທີ່ດີທີ່ສຸດເພື່ອແບ່ງມັນໃນບັນດາປະເພດຂອງລາຍການຕ່າງໆ. " +"ໂດຍການຮັກສາຕິດຕາມບ່ອນທີ່ເງິນຂອງທ່ານດີ, ທ່ານອາດຈະມີຫນ້ອຍອາດຈະ overspend, " +"ແລະອື່ນໆທີ່ອາດຈະຕອບສະຫນອງໄດ້ຕາມເປົ້າຫມາຍທາງດ້ານການເງິນຂອງທ່ານ. " +"ຄາດງົບປະມານໂດຍ detailing ລາຍຮັບຄາດວ່າຈະຕໍ່ບັນຊີແຍກທາດແລະຕິດຕາມກວດກາຂອງຕົນ " +"evolution ອີງຕາມຕົວຈິງໃນ realized ໃນໄລຍະນັ້ນ." #. module: account_budget #: code:addons/account_budget/account_budget.py:119 @@ -134,18 +143,21 @@ msgid "The Budget '%s' has no accounts!" msgstr "" #. module: account_budget +#: report:account.analytic.account.budget:0 #: report:account.budget:0 #: report:crossovered.budget.report:0 msgid "%" msgstr "" #. module: account_budget +#: report:account.analytic.account.budget:0 #: report:account.budget:0 #: report:crossovered.budget.report:0 msgid "Description" msgstr "ຄຳອະທິບາຍ" #. module: account_budget +#: report:account.analytic.account.budget:0 #: report:crossovered.budget.report:0 msgid "Currency" msgstr "ເງິນຕາ" @@ -170,7 +182,7 @@ msgstr "ເພື່ອຮັບຮູ້" #. module: account_budget #: view:crossovered.budget:0 msgid "Reset to Draft" -msgstr "" +msgstr "ປັບກັບຮ່າງ" #. module: account_budget #: view:account.budget.post:0 @@ -180,6 +192,7 @@ msgid "Planned Amount" msgstr "ຈໍານວນຄາດໝາຍມ້ຽນ" #. module: account_budget +#: report:account.analytic.account.budget:0 #: report:account.budget:0 #: report:crossovered.budget.report:0 msgid "Perc(%)" @@ -192,6 +205,7 @@ msgid "Done" msgstr "ຈົບເເລັວ" #. module: account_budget +#: report:account.analytic.account.budget:0 #: report:account.budget:0 #: report:crossovered.budget.report:0 msgid "Practical Amt" @@ -203,7 +217,7 @@ msgstr "" #: view:crossovered.budget:0 #: field:crossovered.budget.lines,practical_amount:0 msgid "Practical Amount" -msgstr "" +msgstr "ຈໍານວນເງິນປະຕິບັດໄດ້" #. module: account_budget #: field:crossovered.budget,date_to:0 @@ -292,7 +306,7 @@ msgstr "" #. module: account_budget #: selection:crossovered.budget,state:0 msgid "Cancelled" -msgstr "" +msgstr "ຍົກເລີກ" #. module: account_budget #: view:crossovered.budget:0 @@ -326,6 +340,7 @@ msgid "Account Budget crossvered summary report" msgstr "" #. module: account_budget +#: report:account.analytic.account.budget:0 #: report:account.budget:0 #: report:crossovered.budget.report:0 msgid "Theoretical Amt" @@ -355,6 +370,7 @@ msgid "Theoretical Amount" msgstr "" #. module: account_budget +#: report:account.analytic.account.budget:0 #: field:crossovered.budget.lines,analytic_account_id:0 #: model:ir.model,name:account_budget.model_account_analytic_account msgid "Analytic Account" @@ -366,6 +382,7 @@ msgid "Budget :" msgstr "ງົບປະມານ" #. module: account_budget +#: report:account.analytic.account.budget:0 #: report:account.budget:0 #: report:crossovered.budget.report:0 msgid "Planned Amt" @@ -406,6 +423,7 @@ msgid "Error! You can not create recursive analytic accounts." msgstr "" #. module: account_budget +#: report:account.analytic.account.budget:0 #: report:account.budget:0 #: report:crossovered.budget.report:0 msgid "Analysis from" diff --git a/addons/auth_oauth/auth_oauth_data.xml b/addons/auth_oauth/auth_oauth_data.xml index 83e9df2e44d..c07359a062c 100644 --- a/addons/auth_oauth/auth_oauth_data.xml +++ b/addons/auth_oauth/auth_oauth_data.xml @@ -1,6 +1,6 @@ - + OpenERP Accounts diff --git a/addons/auth_reset_password/__init__.py b/addons/auth_reset_password/__init__.py index 28bbdd18161..8341ccd60c7 100644 --- a/addons/auth_reset_password/__init__.py +++ b/addons/auth_reset_password/__init__.py @@ -1 +1,23 @@ -import auth_reset_password +# -*- 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 controllers +import res_users diff --git a/addons/auth_reset_password/__openerp__.py b/addons/auth_reset_password/__openerp__.py index ccf5ca53a21..e620d515fa1 100644 --- a/addons/auth_reset_password/__openerp__.py +++ b/addons/auth_reset_password/__openerp__.py @@ -1,3 +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 +# +############################################################################## + { 'name': 'Reset Password', 'description': """ @@ -9,9 +30,11 @@ Allow users to reset their password from the login page. 'category': 'Authentication', 'website': 'http://www.openerp.com', 'installable': True, - 'depends': ['auth_anonymous', 'email_template'], - 'data': ['auth_reset_password.xml'], + 'depends': ['auth_signup', 'email_template'], + 'data': [ + 'auth_reset_password.xml', + 'res_users_view.xml', + ], 'js': ['static/src/js/reset_password.js'], - 'css': ['static/src/css/reset_password.css'], 'qweb': ['static/src/xml/reset_password.xml'], } diff --git a/addons/auth_reset_password/auth_reset_password.py b/addons/auth_reset_password/auth_reset_password.py deleted file mode 100644 index cef7bc9c212..00000000000 --- a/addons/auth_reset_password/auth_reset_password.py +++ /dev/null @@ -1,130 +0,0 @@ -import base64 -import hashlib -import simplejson -import time -import urlparse - -from openerp.tools import config -from openerp.osv import osv, fields -from openerp import SUPERUSER_ID - -TWENTY_FOUR_HOURS = 24 * 60 * 60 - -def message_sign(data, secret): - src = simplejson.dumps([data, secret], indent=None, separators=(',', ':'), sort_keys=True) - sign = hashlib.sha1(src).hexdigest() - msg = simplejson.dumps([data, sign], indent=None, separators=(',', ':'), sort_keys=True) - # pad message to avoid '=' - pad = (3 - len(msg) % 3) % 3 - msg = msg + " " * pad - msg = base64.urlsafe_b64encode(msg) - return msg, sign - -def message_check(msg, secret): - msg = base64.urlsafe_b64decode(msg) - l = simplejson.loads(msg) - msg_data = l[0] - msg_sign = l[1] - tmp, sign = message_sign(msg_data, secret) - if msg_sign == sign: - return msg_data - -class res_users(osv.osv): - _inherit = 'res.users' - - def _auth_reset_password_secret(self, cr, uid, context=None): - uuid = self.pool.get('ir.config_parameter').get_param(cr, uid, 'database.uuid') - res = { - 'dbname': cr.dbname, - 'uuid': uuid, - 'admin_passwd': config['admin_passwd'] - } - return res - - def _auth_reset_password_host(self, cr, uid, context=None): - return self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url', '') - - def _auth_reset_password_link(self, cr, uid, ids, context=None): - assert len(ids) == 1 - host = self._auth_reset_password_host(cr, uid, context) - secret = self._auth_reset_password_secret(cr, uid, context) - msg_src = { - 'time': time.time(), - 'uid': ids[0], - } - msg, sign = message_sign(msg_src, secret) - link = urlparse.urljoin(host, '/login?db=%s&login=anonymous&key=anonymous#action=reset_password&token=%s' % (cr.dbname, msg)) - return link - - def _auth_reset_password_check_token(self, cr, uid, token, context=None): - secret = self._auth_reset_password_secret(cr, uid, context) - data = message_check(token, secret) - if data and (time.time() - data['time'] < TWENTY_FOUR_HOURS): - return data - return None - - def _auth_reset_password_send_email(self, cr, uid, email_to, tpl_name, res_id, context=None): - model, tpl_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'auth_reset_password', tpl_name) - assert model == 'email.template' - - msg_id = self.pool.get(model).send_mail(cr, uid, tpl_id, res_id, force_send=False, context=context) - MailMessage = self.pool.get('mail.message') - MailMessage.write(cr, uid, [msg_id], {'email_to': email_to}, context=context) - MailMessage.send(cr, uid, [msg_id], context=context) - - def send_reset_password_request(self, cr, uid, email, context=None): - # TODO reseting a password knowing only an email is not good enough (email can be shared between multiple logins). - ids = self.pool.get('res.users').search(cr, SUPERUSER_ID, [('user_email', '=', email)], context=context) - if ids: - self._auth_reset_password_send_email(cr, SUPERUSER_ID, email, 'reset_password_email', ids[0], context=context) - return True - #else: - # _m, company_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'main_company') - # self._auth_reset_password_send_email(cr, uid, email, 'email_no_user', company_id, context=context) - return False - -class auth_reset_password(osv.TransientModel): - _name = 'auth.reset_password' - _rec_name = 'password' - _columns = { - 'password': fields.char('Password', size=64), - 'password_confirmation': fields.char('Confirm Password', size=64), - 'token': fields.char('Token', size=128), - 'state': fields.selection([(x, x) for x in 'draft done missmatch error'.split()], required=True), - } - _defaults = { - 'state': 'draft', - } - - def create(self, cr, uid, values, context=None): - # NOTE here, invalid values raises exceptions to avoid storing - # sensitive data into the database (which then are available to anyone) - - pw = values.get('password') - if not pw or pw != values.get('password_confirmation'): - raise osv.except_osv('Error', 'Passwords missmatch') - - Users = self.pool.get('res.users') - data = Users._auth_reset_password_check_token(cr, uid, values.get('token', '')) - if data: - Users.write(cr, SUPERUSER_ID, data['uid'], {'password': pw}, context=context) - else: - raise osv.except_osv('Error', 'Invalid token') - - # Dont store password - values = {'state': 'done'} - return super(auth_reset_password, self).create(cr, uid, values, context) - - def change(self, cr, uid, ids, context=None): - return True - - def onchange_pw(self, cr, uid, ids, password, password_confirmation, context=None): - if password != password_confirmation: - return {'value': {'state': 'missmatch'}} - return {'value': {'state': 'draft'}} - - def onchange_token(self, cr, uid, ids, token, context=None): - Users = self.pool.get('res.users') - if not Users._auth_reset_password_check_token(cr, uid, token, context=context): - return {'value': {'state': 'error'}} - return {} diff --git a/addons/auth_reset_password/auth_reset_password.xml b/addons/auth_reset_password/auth_reset_password.xml index bb75da34ce8..90f08a25899 100644 --- a/addons/auth_reset_password/auth_reset_password.xml +++ b/addons/auth_reset_password/auth_reset_password.xml @@ -7,56 +7,15 @@ Reset Password ]]> - + ${object.email} Password reset A password reset was requested the OpenERP account linked to this email on ${object._auth_reset_password_host()}

+

A password reset was requested for the OpenERP account linked to this email.

-

You may change your password following this link, -or by copy-pasting the following URL in your browser: ${object._auth_reset_password_link()}

+

You may change your password following this link.

Note: If you did not ask for a password reset, you can safely ignore this email.

]]>
- - - auth.reset_password.form - auth.reset_password - -
- - - - - - -
Passwords missmatch
-
- - -
  • - +
  • Reset password
  • + 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 @@ - + - - -
  • - -
  • + + + + + + + + + + + + + + + + + + +
  • +
  • +
    -
    - - -
    -
    - Name =
    - Email =
    - Password =
    - Confirmation =
    - -
    -
    -
    - -
    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.inherited3 res.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': '
    %s
    ' % body, + 'subject': _(WELCOME_EMAIL_SUBJECT) % data, + 'body_html': '
    %s
    ' % (_(WELCOME_EMAIL_BODY) % data), 'state': 'outgoing', } return mail_mail.create(cr, uid, mail_values, context=this_context) diff --git a/addons/portal/wizard/portal_wizard_view.xml b/addons/portal/wizard/portal_wizard_view.xml index c8e3f30b128..373b4ad8e7a 100644 --- a/addons/portal/wizard/portal_wizard_view.xml +++ b/addons/portal/wizard/portal_wizard_view.xml @@ -27,8 +27,6 @@ -