[MERGE] merge from trunk

bzr revid: ged@openerp.com-20140319101615-9a9vb0uj0sg58nrd
This commit is contained in:
Gery Debongnie 2014-03-19 11:16:15 +01:00
commit 31645ea1cf
99 changed files with 942 additions and 488 deletions

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 05:58+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:41+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 05:58+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:41+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 05:58+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:42+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 05:58+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:42+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 05:59+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:43+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 05:59+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:43+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 05:59+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:43+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 05:59+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:43+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:00+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:44+0000\n"
"X-Generator: Launchpad (build 16963)\n"
"X-Poedit-Language: Czech\n"
#. module: base
@ -64,7 +64,7 @@ msgstr "Zobrazení architektury"
#. module: base
#: model:ir.module.module,summary:base.module_sale_stock
msgid "Quotation, Sale Orders, Delivery & Invoicing Control"
msgstr "Nabídky, zakázky, řízení dopravy a fakturace"
msgstr "Nabídky, zakázky, řízení dodávek a fakturace"
#. module: base
#: selection:ir.sequence,implementation:0
@ -87,8 +87,7 @@ msgid ""
"Helps you manage your projects and tasks by tracking them, generating "
"plannings, etc..."
msgstr ""
"Pomůže vám spravovat projekty a úkoly jejich sledováním, generováním plánů, "
"apod."
"Pomůže vám řídit projekty a úkoly jejich sledováním, generováním plánů, aj."
#. module: base
#: model:ir.module.module,summary:base.module_point_of_sale
@ -158,7 +157,7 @@ msgstr ""
#. module: base
#: help:res.partner,employee:0
msgid "Check this box if this contact is an Employee."
msgstr "Zaškrtněte toto pole, pokud je kontakt zaměstnanec."
msgstr "Zaškrtněte toto pole, pokud je kontakt zaměstnancem."
#. module: base
#: help:ir.model.fields,domain:0
@ -11056,7 +11055,7 @@ msgstr "Úkoly"
#. module: base
#: model:ir.module.module,shortdesc:base.module_product_visible_discount
msgid "Prices Visible Discounts"
msgstr ""
msgstr "Zobrazení slevy u ceny"
#. module: base
#: field:ir.attachment,datas:0

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:00+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:44+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:02+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:46+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -12,8 +12,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:03+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:46+0000\n"
"X-Generator: Launchpad (build 16963)\n"
"X-Poedit-Country: GREECE\n"
"X-Poedit-Language: Greek\n"
"X-Poedit-SourceCharset: utf-8\n"

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:13+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:55+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:10+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:53+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:13+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:55+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:14+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:56+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:14+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:56+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:15+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:57+0000\n"
"X-Generator: Launchpad (build 16963)\n"
"Language: \n"
#. module: base

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:13+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:56+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:16+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:57+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:15+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:57+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:16+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:58+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:13+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:55+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:01+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:44+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 05:59+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:42+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -9,8 +9,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:07+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:50+0000\n"
"X-Generator: Launchpad (build 16963)\n"
"X-Poedit-Country: IRAN, ISLAMIC REPUBLIC OF\n"
"X-Poedit-Language: Persian\n"

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:16+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:58+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:01+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:45+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:02+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:45+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:14+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:56+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:03+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:46+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:03+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:46+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:03+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:47+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:04+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:47+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:09+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:52+0000\n"
"X-Generator: Launchpad (build 16963)\n"
"Language: hr\n"
#. module: base

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:04+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:47+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -9,8 +9,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 05:58+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:42+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:05+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:48+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing
@ -211,6 +211,15 @@ msgid ""
"revenue\n"
"reports."
msgstr ""
"\n"
"Bentuk tagihan anda dari entri Expenses, Timesheet.\n"
"========================================================\n"
"\n"
"Modul untuk menerbitkan tagihan berbasis biaya (sdm, biaya, dsb).\n"
"\n"
"Anda dapat mendefinisikan daftar harga pada akun analitik, membuat laporan "
"anggaran \n"
"penghasilan."
#. module: base
#: code:addons/base/ir/ir_sequence.py:134
@ -299,6 +308,12 @@ msgid ""
"\n"
" "
msgstr ""
"\n"
"Lokalisasi skema akunting dan pajak Chili.\n"
"==============================================\n"
"Plan contable chileno e impuestos de acuerdo a disposiciones vigentes\n"
"\n"
" "
#. module: base
#: model:ir.module.module,shortdesc:base.module_sale
@ -398,6 +413,15 @@ msgid ""
"invoices from picking, OpenERP is able to add and compute the shipping "
"line.\n"
msgstr ""
"\n"
"Mengizinkan anda untuk menambahkan metode deliveri pada order penjualan dan "
"pengambilan.\n"
"==============================================================\n"
"\n"
"Anda dapat menentukan carrier sendiri dan metode antar untuk harga tersebut. "
"Saat membuat\n"
"tagihan dari pengantaran, OpenERP dapat menambah dan menghitung biaya "
"pengiriman.\n"
#. module: base
#: code:addons/base/ir/ir_filters.py:80

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:04+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:47+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:05+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:48+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:05+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:48+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing
@ -411,7 +411,7 @@ msgstr "与信限度"
#: field:ir.model.data,date_update:0
#: field:ir.model.relation,date_update:0
msgid "Update Date"
msgstr "日付の更新"
msgstr "更新"
#. module: base
#: model:ir.module.module,shortdesc:base.module_base_action_rule
@ -4582,7 +4582,7 @@ msgstr "スペイン語(ホンジュラス)/ Español (HN)"
#. module: base
#: view:ir.sequence.type:0
msgid "Sequence Type"
msgstr "順序タイプ"
msgstr "付番タイプ"
#. module: base
#: view:base.language.export:0
@ -5141,7 +5141,7 @@ msgstr "銀行口座タイプ"
#. module: base
#: help:ir.sequence,suffix:0
msgid "Suffix value of the record for the sequence"
msgstr "順序のためのレコードのサフィックス値"
msgstr "この付番定義でレコードに適用するサフィックス値"
#. module: base
#: help:ir.mail_server,smtp_user:0
@ -6140,7 +6140,7 @@ msgstr "ショートカット"
#. module: base
#: field:ir.model.data,date_init:0
msgid "Init Date"
msgstr "整数データ"
msgstr "作成日"
#. module: base
#: selection:base.language.install,lang:0
@ -7630,7 +7630,7 @@ msgstr ""
#. module: base
#: help:ir.sequence,number_next:0
msgid "Next number of this sequence"
msgstr "この順序の次の番号"
msgstr "この付番定義の次の番号"
#. module: base
#: view:res.partner:0
@ -10135,7 +10135,7 @@ msgstr "発注分析計画"
#: model:ir.actions.act_window,name:base.ir_sequence_type
#: model:ir.ui.menu,name:base.menu_ir_sequence_type
msgid "Sequence Codes"
msgstr "順序コード"
msgstr "付番コード"
#. module: base
#: selection:base.language.install,lang:0
@ -10790,7 +10790,7 @@ msgstr "国の状態"
#. module: base
#: model:ir.ui.menu,name:base.next_id_5
msgid "Sequences & Identifiers"
msgstr "順序と識別子"
msgstr "付番定義とID"
#. module: base
#: model:ir.module.module,description:base.module_l10n_th
@ -10925,7 +10925,7 @@ msgstr "連絡先が会社の場合はチェックしてください。チェッ
#. module: base
#: view:ir.sequence.type:0
msgid "Sequences Type"
msgstr "順序タイプ"
msgstr "付番タイプ"
#. module: base
#: view:res.partner:0
@ -11282,7 +11282,7 @@ msgstr ""
#: view:ir.sequence:0
#: model:ir.ui.menu,name:base.menu_ir_sequence_form
msgid "Sequences"
msgstr "順序"
msgstr "付番"
#. module: base
#: help:res.lang,code:0
@ -11724,7 +11724,7 @@ msgstr "月:%(month)s"
#: field:multi_company.default,sequence:0
#: field:res.partner.bank,sequence:0
msgid "Sequence"
msgstr "順序"
msgstr "付番"
#. module: base
#: model:res.country,name:base.tn
@ -12190,7 +12190,7 @@ msgstr "ハード島とマクドナルド諸島"
msgid ""
"External Key/Identifier that can be used for data integration with third-"
"party systems"
msgstr "サードパーティシステムとのデータ統合のために利用する外部キー / 識別子"
msgstr "サードパーティシステムとのデータ統合のために利用する外部キー / ID"
#. module: base
#: field:ir.actions.act_window,view_id:0
@ -12519,7 +12519,7 @@ msgstr "CRMポータル"
#. module: base
#: model:ir.ui.menu,name:base.next_id_4
msgid "Low Level Objects"
msgstr "低レベルオブジェクト"
msgstr "低レベルオブジェクト"
#. module: base
#: help:ir.values,model:0
@ -12596,7 +12596,7 @@ msgstr ""
#: view:ir.model.data:0
#: model:ir.ui.menu,name:base.ir_model_data_menu
msgid "External Identifiers"
msgstr "外部識別子"
msgstr "外部ID"
#. module: base
#: selection:base.language.install,lang:0
@ -13614,7 +13614,7 @@ msgstr ""
#: view:ir.model.data:0
#: field:ir.model.data,name:0
msgid "External Identifier"
msgstr "外部識別子"
msgstr "外部ID"
#. module: base
#: model:ir.module.module,description:base.module_audittrail
@ -14048,9 +14048,7 @@ msgid ""
"Two sequence object implementations are offered: Standard and 'No gap'. The "
"later is slower than the former but forbids any gap in the sequence (while "
"they are possible in the former)."
msgstr ""
"2つのシーケンスオブジェクトの実装が提供されます:標準と\"ギャップなし\"です。後者は前者よりも遅いですが、どんな順序のギャップを禁じます(前者は可能で"
"す)。"
msgstr "「標準」と「ギャップなし」の2種類の付番定義が可能です。後者の場合、付番ごとにギャップを設けることができません。"
#. module: base
#: model:res.country,name:base.gn
@ -14249,7 +14247,7 @@ msgstr "かつ"
#: help:ir.values,res_id:0
msgid ""
"Database identifier of the record to which this applies. 0 = for all records"
msgstr "これを適用するためのレコードのデータベース識別子。0 = 全てのレコードのため"
msgstr ""
#. module: base
#: field:ir.model.fields,relation:0
@ -14903,7 +14901,7 @@ msgstr "クック諸島"
#. module: base
#: field:ir.model.data,noupdate:0
msgid "Non Updatable"
msgstr "更新可能でない"
msgstr "更新可"
#. module: base
#: selection:base.language.install,lang:0
@ -15311,7 +15309,7 @@ msgstr ""
#. module: base
#: help:ir.sequence,prefix:0
msgid "Prefix value of the record for the sequence"
msgstr "順序のためのレコードのプレフィックス値"
msgstr "この付番定義でレコードに適用するプレフィックス値"
#. module: base
#: model:res.country,name:base.sc
@ -15353,7 +15351,7 @@ msgstr ""
#. module: base
#: field:ir.model.data,complete_name:0
msgid "Complete ID"
msgstr "完ID"
msgstr "完ID"
#. module: base
#: model:ir.module.module,description:base.module_stock
@ -15447,7 +15445,7 @@ msgstr "製造プロセスを管理し、それらのプロセスのレポート
#. module: base
#: help:ir.sequence,number_increment:0
msgid "The next number of the sequence will be incremented by this number"
msgstr "順序の次の番号はこの数値だけ増加します。"
msgstr "付番毎にこの数値分だけ番号が増加します。"
#. module: base
#: selection:workflow.activity,kind:0

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:02+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:45+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:05+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:48+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:06+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:49+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:06+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:49+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:06+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:49+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:07+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:49+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:07+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:50+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:07+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:50+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:00+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:44+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing
@ -13429,7 +13429,7 @@ msgstr ""
#. module: base
#: selection:res.currency,position:0
msgid "Before Amount"
msgstr "Voormalig bedrag"
msgstr "Voor bedrag"
#. module: base
#: field:res.request,act_from:0

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:14+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:56+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:08+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:51+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:08+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:51+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:12+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:55+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:08+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:51+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:09+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:51+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:09+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:52+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:10+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:52+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 05:58+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:41+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:09+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:52+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:16+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:58+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:10+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:53+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:11+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:53+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:11+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:53+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:11+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:54+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:11+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:54+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:12+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:54+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:12+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:54+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:15+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:57+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:12+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:54+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-03-04 06:14+0000\n"
"X-Generator: Launchpad (build 16948)\n"
"X-Launchpad-Export-Date: 2014-03-12 04:57+0000\n"
"X-Generator: Launchpad (build 16963)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -77,7 +77,9 @@ class ir_http(osv.AbstractModel):
# what if error in security.check()
# -> res_users.check()
# -> res_users.check_credentials()
except Exception:
except (openerp.exceptions.AccessDenied, openerp.http.SessionExpiredException):
# All other exceptions mean undetermined status (e.g. connection pool full),
# let them bubble up
request.session.logout()
getattr(self, "_auth_method_%s" % auth_method)()
return auth_method

View File

@ -33,7 +33,7 @@ from openerp.osv.orm import Model, browse_null
from openerp.tools.safe_eval import safe_eval as eval
from openerp.tools import config
from openerp.tools.translate import _
from openerp.osv.orm import except_orm, browse_record
from openerp.osv.orm import except_orm, browse_record, MAGIC_COLUMNS
_logger = logging.getLogger(__name__)
@ -302,6 +302,8 @@ class ir_model_fields(osv.osv):
def _drop_column(self, cr, uid, ids, context=None):
for field in self.browse(cr, uid, ids, context):
if field.name in MAGIC_COLUMNS:
continue
model = self.pool[field.model]
cr.execute('select relkind from pg_class where relname=%s', (model._table,))
result = cr.fetchone()
@ -1117,6 +1119,10 @@ class ir_model_data(osv.osv):
# Don't remove the LOG_ACCESS_COLUMNS unless _log_access
# has been turned off on the model.
field = self.pool[model].browse(cr, uid, [res_id], context=context)[0]
if not field.exists():
_logger.info('Deleting orphan external_ids %s', external_ids)
self.unlink(cr, uid, external_ids)
continue
if field.name in openerp.osv.orm.LOG_ACCESS_COLUMNS and self.pool[field.model]._log_access:
continue
if field.name == 'id':

View File

@ -32,7 +32,7 @@ class QWebException(Exception):
class QWebTemplateNotFound(QWebException):
pass
def convert_to_qweb_exception(etype=None, **kw):
def raise_qweb_exception(etype=None, **kw):
if etype is None:
etype = QWebException
orig_type, original, tb = sys.exc_info()
@ -43,7 +43,7 @@ def convert_to_qweb_exception(etype=None, **kw):
e.qweb[k] = v
# Will use `raise foo from bar` in python 3 and rename cause to __cause__
e.qweb['cause'] = original
return e
raise
class QWebContext(dict):
def __init__(self, cr, uid, data, loader=None, templates=None, context=None):
@ -166,7 +166,7 @@ class QWeb(orm.AbstractModel):
try:
xml_doc = qwebcontext.loader(name)
except ValueError:
raise convert_to_qweb_exception(QWebTemplateNotFound, message="Loader could not find template %r" % name, template=origin_template)
raise_qweb_exception(QWebTemplateNotFound, message="Loader could not find template %r" % name, template=origin_template)
self.load_document(xml_doc, qwebcontext=qwebcontext)
if name in qwebcontext.templates:
@ -179,7 +179,7 @@ class QWeb(orm.AbstractModel):
return qwebcontext.safe_eval(expr)
except Exception:
template = qwebcontext.get('__template__')
raise convert_to_qweb_exception(message="Could not evaluate expression %r" % expr, expression=expr, template=template)
raise_qweb_exception(message="Could not evaluate expression %r" % expr, expression=expr, template=template)
def eval_object(self, expr, qwebcontext):
return self.eval(expr, qwebcontext)
@ -207,7 +207,7 @@ class QWeb(orm.AbstractModel):
return str(expr % qwebcontext)
except Exception:
template = qwebcontext.get('__template__')
raise convert_to_qweb_exception(message="Format error for expression %r" % expr, expression=expr, template=template)
raise_qweb_exception(message="Format error for expression %r" % expr, expression=expr, template=template)
def eval_bool(self, expr, qwebcontext):
return int(bool(self.eval(expr, qwebcontext)))
@ -292,7 +292,7 @@ class QWeb(orm.AbstractModel):
raise
except Exception:
template = qwebcontext.get('__template__')
raise convert_to_qweb_exception(message="Could not render element %r" % element.nodeName, node=element, template=template)
raise_qweb_exception(message="Could not render element %r" % element.nodeName, node=element, template=template)
name = str(element.nodeName)
inner = "".join(g_inner)
trim = template_attributes.get("trim", 0)
@ -488,7 +488,6 @@ class FieldConverter(osv.AbstractModel):
A default configuration key is ``widget`` which can override the
field's own ``_type``.
"""
content = None
try:
content = self.record_to_html(
cr, uid, field_name, record,
@ -503,12 +502,14 @@ class FieldConverter(osv.AbstractModel):
field_name, record._model._name, exc_info=True)
content = None
g_att += ''.join(
' %s="%s"' % (name, werkzeug.utils.escape(value))
for name, value in self.attributes(
cr, uid, field_name, record, options,
source_element, g_att, t_att, qweb_context)
)
if context and context.get('inherit_branding'):
# add branding attributes
g_att += ''.join(
' %s="%s"' % (name, werkzeug.utils.escape(value))
for name, value in self.attributes(
cr, uid, field_name, record, options,
source_element, g_att, t_att, qweb_context)
)
return self.render_element(cr, uid, source_element, t_att, g_att,
qweb_context, content)
@ -611,6 +612,9 @@ class DateTimeConverter(osv.AbstractModel):
strftime_pattern = (u"%s %s" % (lang.date_format, lang.time_format))
pattern = openerp.tools.posix_to_ldml(strftime_pattern, locale=locale)
if options and options.get('hide_seconds'):
pattern = pattern.replace(":ss", "").replace(":s", "")
return babel.dates.format_datetime(value, format=pattern, locale=locale)
class TextConverter(osv.AbstractModel):

View File

@ -728,9 +728,26 @@ class view(osv.osv):
def clear_cache(self):
self.read_template.clear_cache(self)
def _contains_branded(self, node):
return node.tag == 't'\
or 't-raw' in node.attrib\
or any(self.is_node_branded(child) for child in node.iterdescendants())
def _pop_view_branding(self, element):
distributed_branding = dict(
(attribute, element.attrib.pop(attribute))
for attribute in MOVABLE_BRANDING
if element.get(attribute))
return distributed_branding
def distribute_branding(self, e, branding=None, parent_xpath='',
index_map=misc.ConstantMapping(1)):
if e.get('t-ignore') or e.tag == 'head':
# remove any view branding possibly injected by inheritance
attrs = set(MOVABLE_BRANDING)
for descendant in e.iterdescendants(tag=etree.Element):
if not attrs.intersection(descendant.attrib): continue
self._pop_view_branding(descendant)
# TODO: find a better name and check if we have a string to boolean helper
return
@ -742,15 +759,15 @@ class view(osv.osv):
e.set('data-oe-xpath', node_path)
if not e.get('data-oe-model'): return
# if a branded element contains branded elements distribute own
# branding to children unless it's t-raw, then just remove branding
# on current element
if e.tag == 't' or 't-raw' in e.attrib or \
any(self.is_node_branded(child) for child in e.iterdescendants()):
distributed_branding = dict(
(attribute, e.attrib.pop(attribute))
for attribute in MOVABLE_BRANDING
if e.get(attribute))
if set(('t-esc', 't-escf', 't-raw', 't-rawf')).intersection(e.attrib):
# nodes which fully generate their content and have no reason to
# be branded because they can not sensibly be edited
self._pop_view_branding(e)
elif self._contains_branded(e):
# if a branded element contains branded elements distribute own
# branding to children unless it's t-raw, then just remove branding
# on current element
distributed_branding = self._pop_view_branding(e)
if 't-raw' not in e.attrib:
# TODO: collections.Counter if remove p2.6 compat
@ -760,11 +777,12 @@ class view(osv.osv):
if child.get('data-oe-xpath'):
# injected by view inheritance, skip otherwise
# generated xpath is incorrect
continue
indexes[child.tag] += 1
self.distribute_branding(child, distributed_branding,
parent_xpath=node_path,
index_map=indexes)
self.distribute_branding(child)
else:
indexes[child.tag] += 1
self.distribute_branding(
child, distributed_branding,
parent_xpath=node_path, index_map=indexes)
def is_node_branded(self, node):
""" Finds out whether a node is branded or qweb-active (bears a

View File

@ -208,20 +208,19 @@ class res_company(osv.osv):
res['value'] = {'currency_id': currency_id}
return res
def _search(self, cr, uid, args, offset=0, limit=None, order=None,
context=None, count=False, access_rights_uid=None):
def name_search(self, cr, uid, name='', args=None, operator='ilike', context=None, limit=100):
if context is None:
context = {}
if context.get('user_preference'):
if context.pop('user_preference', None):
# We browse as superuser. Otherwise, the user would be able to
# select only the currently visible companies (according to rules,
# which are probably to allow to see the child companies) even if
# she belongs to some other companies.
user = self.pool.get('res.users').browse(cr, SUPERUSER_ID, uid, context=context)
cmp_ids = list(set([user.company_id.id] + [cmp.id for cmp in user.company_ids]))
return cmp_ids
return super(res_company, self)._search(cr, uid, args, offset=offset, limit=limit, order=order,
context=context, count=count, access_rights_uid=access_rights_uid)
uid = SUPERUSER_ID
args = (args or []) + [('id', 'in', cmp_ids)]
return super(res_company, self).name_search(cr, uid, name=name, args=args, operator=operator, context=context, limit=limit)
def _company_default_get(self, cr, uid, object=False, field=False, context=None):
"""

View File

@ -350,6 +350,7 @@ class res_partner(osv.osv, format_address):
def copy(self, cr, uid, id, default=None, context=None):
if default is None:
default = {}
default['user_ids'] = False
name = self.read(cr, uid, [id], ['name'], context)[0]['name']
default.update({'name': _('%s (copy)') % name})
return super(res_partner, self).copy(cr, uid, id, default, context)

View File

@ -3,7 +3,7 @@
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
# Copyright (C) 2010-2013 OpenERP s.a. (<http://openerp.com>).
# Copyright (C) 2010-2014 OpenERP s.a. (<http://openerp.com>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -169,8 +169,9 @@ class res_users(osv.osv):
}
def on_change_login(self, cr, uid, ids, login, context=None):
v = {'email': login} if tools.single_email_re.match(login) else {}
return {'value': v}
if login and tools.single_email_re.match(login):
return {'value': {'email': login}}
return {}
def onchange_state(self, cr, uid, ids, state_id, context=None):
partner_ids = [user.partner_id.id for user in self.browse(cr, uid, ids, context=context)]
@ -899,18 +900,22 @@ class change_password_wizard(osv.TransientModel):
}))
return {'user_ids': res}
def change_password_button(self, cr, uid, id, context=None):
wizard = self.browse(cr, uid, id, context=context)[0]
user_ids = []
for user in wizard.user_ids:
user_ids.append(user.id)
self.pool.get('change.password.user').change_password_button(cr, uid, user_ids, context=context)
need_reload = any(uid == user.user_id.id for user in wizard.user_ids)
line_ids = [user.id for user in wizard.user_ids]
self.pool.get('change.password.user').change_password_button(cr, uid, line_ids, context=context)
# don't keep temporary password copies in the database longer than necessary
self.pool.get('change.password.user').unlink(cr, uid, user_ids)
return {
'type': 'ir.actions.act_window_close',
}
self.pool.get('change.password.user').write(cr, uid, line_ids, {'new_passwd': False}, context=context)
if need_reload:
return {
'type': 'ir.actions.client',
'tag': 'reload'
}
return {'type': 'ir.actions.act_window_close'}
class change_password_user(osv.TransientModel):
"""

View File

@ -282,7 +282,7 @@
<group name="preferences" col="4">
<field name="lang" readonly="0"/>
<field name="tz" readonly="0"/>
<field name="company_id" widget="selection" readonly="0"
<field name="company_id" options="{'no_create': True}" readonly="0"
groups="base.group_multi_company"/>
</group>
<group string="Email Preferences">

View File

@ -9,6 +9,7 @@ class test_base(common.TransactionCase):
super(test_base,self).setUp()
self.res_partner = self.registry('res.partner')
self.res_users = self.registry('res.users')
self.res_partner_title = self.registry('res.partner.title')
# samples use effective TLDs from the Mozilla public suffix
# list at http://publicsuffix.org
@ -285,27 +286,99 @@ class test_base(common.TransactionCase):
def test_60_read_group(self):
cr, uid = self.cr, self.uid
for user_data in [
{'name': 'Alice', 'login': 'alice', 'color': 1, 'function': 'Friend'},
{'name': 'Bob', 'login': 'bob', 'color': 2, 'function': 'Friend'},
{'name': 'Eve', 'login': 'eve', 'color': 3, 'function': 'Eavesdropper'},
{'name': 'Nab', 'login': 'nab', 'color': 2, 'function': '5$ Wrench'},
]:
self.res_users.create(cr, uid, user_data)
title_sir = self.res_partner_title.create(cr, uid, {'name': 'Sir', 'domain': 'contact'})
title_lady = self.res_partner_title.create(cr, uid, {'name': 'Lady', 'domain': 'contact'})
test_users = [
{'name': 'Alice', 'login': 'alice', 'color': 1, 'function': 'Friend', 'date': '2015-03-28', 'title': title_lady},
{'name': 'Alice', 'login': 'alice2', 'color': 0, 'function': 'Friend', 'date': '2015-01-28', 'title': title_lady},
{'name': 'Bob', 'login': 'bob', 'color': 2, 'function': 'Friend', 'date': '2015-03-02', 'title': title_sir},
{'name': 'Eve', 'login': 'eve', 'color': 3, 'function': 'Eavesdropper', 'date': '2015-03-20', 'title': title_lady},
{'name': 'Nab', 'login': 'nab', 'color': -3, 'function': '5$ Wrench', 'date': '2014-09-10', 'title': title_sir},
{'name': 'Nab', 'login': 'nab-she', 'color': 6, 'function': '5$ Wrench', 'date': '2014-01-02', 'title': title_lady},
]
ids = [self.res_users.create(cr, uid, u) for u in test_users]
domain = [('id', 'in', ids)]
groups_data = self.res_users.read_group(cr, uid, domain=[('login', 'in', ('alice', 'bob', 'eve'))], fields=['name', 'color', 'function'], groupby='function')
self.assertEqual(len(groups_data), 2, "Incorrect number of results when grouping on a field")
# group on local char field without domain and without active_test (-> empty WHERE clause)
groups_data = self.res_users.read_group(cr, uid, [], fields=['login'], groupby=['login'], orderby='login DESC', context={'active_test': False})
self.assertGreater(len(groups_data), 6, "Incorrect number of results when grouping on a field")
# group on local char field with limit
groups_data = self.res_users.read_group(cr, uid, domain, fields=['login'], groupby=['login'], orderby='login DESC', limit=3, offset=3)
self.assertEqual(len(groups_data), 3, "Incorrect number of results when grouping on a field with limit")
self.assertEqual(['bob', 'alice2', 'alice'], [g['login'] for g in groups_data], 'Result mismatch')
# group on inherited char field, aggregate on int field (second groupby ignored on purpose)
groups_data = self.res_users.read_group(cr, uid, domain, fields=['name', 'color', 'function'], groupby=['function', 'login'])
self.assertEqual(len(groups_data), 3, "Incorrect number of results when grouping on a field")
self.assertEqual(['5$ Wrench', 'Eavesdropper', 'Friend'], [g['function'] for g in groups_data], 'incorrect read_group order')
for group_data in groups_data:
self.assertIn('color', group_data, "Aggregated data for the column 'color' is not present in read_group return values")
self.assertEqual(group_data['color'], 3, "Incorrect sum for aggregated data for the column 'color'")
self.assertIn('color', group_data, "Aggregated data for the column 'color' is not present in read_group return values")
self.assertEqual(group_data['color'], 3, "Incorrect sum for aggregated data for the column 'color'")
groups_data = self.res_users.read_group(cr, uid, domain=[('login', 'in', ('alice', 'bob', 'eve'))], fields=['name', 'color'], groupby='name', orderby='name DESC, color asc')
self.assertEqual(len(groups_data), 3, "Incorrect number of results when grouping on a field")
self.assertEqual([user['name'] for user in groups_data], ['Eve', 'Bob', 'Alice'], 'Incorrect ordering of the list')
# group on inherited char field, reverse order
groups_data = self.res_users.read_group(cr, uid, domain, fields=['name', 'color'], groupby='name', orderby='name DESC')
self.assertEqual(['Nab', 'Eve', 'Bob', 'Alice'], [g['name'] for g in groups_data], 'Incorrect ordering of the list')
# group on int field, default ordering
groups_data = self.res_users.read_group(cr, uid, domain, fields=['color'], groupby='color')
self.assertEqual([-3, 0, 1, 2, 3, 6], [g['color'] for g in groups_data], 'Incorrect ordering of the list')
# multi group, second level is int field, should still be summed in first level grouping
groups_data = self.res_users.read_group(cr, uid, domain, fields=['name', 'color'], groupby=['name', 'color'], orderby='name DESC')
self.assertEqual(['Nab', 'Eve', 'Bob', 'Alice'], [g['name'] for g in groups_data], 'Incorrect ordering of the list')
self.assertEqual([3, 3, 2, 1], [g['color'] for g in groups_data], 'Incorrect ordering of the list')
# group on inherited char field, multiple orders with directions
groups_data = self.res_users.read_group(cr, uid, domain, fields=['name', 'color'], groupby='name', orderby='color DESC, name')
self.assertEqual(len(groups_data), 4, "Incorrect number of results when grouping on a field")
self.assertEqual(['Eve', 'Nab', 'Bob', 'Alice'], [g['name'] for g in groups_data], 'Incorrect ordering of the list')
self.assertEqual([1, 2, 1, 2], [g['name_count'] for g in groups_data], 'Incorrect number of results')
# group on inherited date column (res_partner.date) -> Year-Month, default ordering
groups_data = self.res_users.read_group(cr, uid, domain, fields=['function', 'color', 'date'], groupby=['date'])
self.assertEqual(len(groups_data), 4, "Incorrect number of results when grouping on a field")
self.assertEqual(['January 2014', 'September 2014', 'January 2015', 'March 2015'], [g['date'] for g in groups_data], 'Incorrect ordering of the list')
self.assertEqual([1, 1, 1, 3], [g['date_count'] for g in groups_data], 'Incorrect number of results')
# group on inherited date column (res_partner.date) -> Year-Month, custom order
groups_data = self.res_users.read_group(cr, uid, domain, fields=['function', 'color', 'date'], groupby=['date'], orderby='date DESC')
self.assertEqual(len(groups_data), 4, "Incorrect number of results when grouping on a field")
self.assertEqual(['March 2015', 'January 2015', 'September 2014', 'January 2014'], [g['date'] for g in groups_data], 'Incorrect ordering of the list')
self.assertEqual([3, 1, 1, 1], [g['date_count'] for g in groups_data], 'Incorrect number of results')
# group on inherited many2one (res_partner.title), default order
groups_data = self.res_users.read_group(cr, uid, domain, fields=['function', 'color', 'title'], groupby=['title'])
self.assertEqual(len(groups_data), 2, "Incorrect number of results when grouping on a field")
# m2o is returned as a (id, label) pair
self.assertEqual([(title_lady, 'Lady'), (title_sir, 'Sir')], [g['title'] for g in groups_data], 'Incorrect ordering of the list')
self.assertEqual([4, 2], [g['title_count'] for g in groups_data], 'Incorrect number of results')
self.assertEqual([10, -1], [g['color'] for g in groups_data], 'Incorrect aggregation of int column')
# group on inherited many2one (res_partner.title), reversed natural order
groups_data = self.res_users.read_group(cr, uid, domain, fields=['function', 'color', 'title'], groupby=['title'], orderby="title desc")
self.assertEqual(len(groups_data), 2, "Incorrect number of results when grouping on a field")
# m2o is returned as a (id, label) pair
self.assertEqual([(title_sir, 'Sir'), (title_lady, 'Lady')], [g['title'] for g in groups_data], 'Incorrect ordering of the list')
self.assertEqual([2, 4], [g['title_count'] for g in groups_data], 'Incorrect number of results')
self.assertEqual([-1, 10], [g['color'] for g in groups_data], 'Incorrect aggregation of int column')
# group on inherited many2one (res_partner.title), multiple orders with m2o in second position
groups_data = self.res_users.read_group(cr, uid, domain, fields=['function', 'color', 'title'], groupby=['title'], orderby="color desc, title desc")
self.assertEqual(len(groups_data), 2, "Incorrect number of results when grouping on a field")
# m2o is returned as a (id, label) pair
self.assertEqual([(title_lady, 'Lady'), (title_sir, 'Sir')], [g['title'] for g in groups_data], 'Incorrect ordering of the result')
self.assertEqual([4, 2], [g['title_count'] for g in groups_data], 'Incorrect number of results')
self.assertEqual([10, -1], [g['color'] for g in groups_data], 'Incorrect aggregation of int column')
# group on inherited many2one (res_partner.title), ordered by other inherited field (color)
groups_data = self.res_users.read_group(cr, uid, domain, fields=['function', 'color', 'title'], groupby=['title'], orderby='color')
self.assertEqual(len(groups_data), 2, "Incorrect number of results when grouping on a field")
# m2o is returned as a (id, label) pair
self.assertEqual([(title_sir, 'Sir'), (title_lady, 'Lady')], [g['title'] for g in groups_data], 'Incorrect ordering of the list')
self.assertEqual([2, 4], [g['title_count'] for g in groups_data], 'Incorrect number of results')
self.assertEqual([-1, 10], [g['color'] for g in groups_data], 'Incorrect aggregation of int column')
groups_data = self.res_users.read_group(cr, uid, domain=[('login', 'in', ('alice', 'bob', 'eve', 'nab'))], fields=['function', 'color'], groupby='function', orderby='color ASC')
self.assertEqual(len(groups_data), 3, "Incorrect number of results when grouping on a field")
self.assertEqual(groups_data, sorted(groups_data, key=lambda x: x['color']), 'Incorrect ordering of the list')
class test_partner_recursion(common.TransactionCase):

View File

@ -179,3 +179,51 @@ class TestPropertyField(common.TransactionCase):
self.partner.write(cr, alice, [partner_id], {'property_country': country_be})
self.assertEqual(self.partner.browse(cr, alice, partner_id).property_country.id, country_be, "Alice does not see the value he has set on the property field")
self.assertEqual(self.partner.browse(cr, bob, partner_id).property_country.id, country_fr, "Changes made by Alice have overwritten Bob's value")
class TestHtmlField(common.TransactionCase):
def setUp(self):
super(TestHtmlField, self).setUp()
self.partner = self.registry('res.partner')
def test_00_sanitize(self):
cr, uid, context = self.cr, self.uid, {}
old_columns = self.partner._columns
self.partner._columns = dict(old_columns)
self.partner._columns.update({
'comment': fields.html('Secure Html', sanitize=False),
})
some_ugly_html = """<p>Oops this should maybe be sanitized
% if object.some_field and not object.oriented:
<table>
% if object.other_field:
<tr>
${object.mako_thing}
<td>
</tr>
% endif
<tr>
%if object.dummy_field:
<p>Youpie</p>
%endif"""
pid = self.partner.create(cr, uid, {
'name': 'Raoul Poilvache',
'comment': some_ugly_html,
}, context=context)
partner = self.partner.browse(cr, uid, pid, context=context)
self.assertEqual(partner.comment, some_ugly_html, 'Error in HTML field: content was sanitized but field has sanitize=False')
self.partner._columns.update({
'comment': fields.html('Unsecure Html', sanitize=True),
})
self.partner.write(cr, uid, [pid], {
'comment': some_ugly_html,
}, context=context)
partner = self.partner.browse(cr, uid, pid, context=context)
# sanitize should have closed tags left open in the original html
self.assertIn('</table>', partner.comment, 'Error in HTML field: content does not seem to have been sanitized despise sanitize=True')
self.assertIn('</td>', partner.comment, 'Error in HTML field: content does not seem to have been sanitized despise sanitize=True')
self.partner._columns = old_columns

View File

@ -8,6 +8,7 @@
# OPENERP_DATABASE=yy PYTHONPATH=../:. unit2 test_ir_sequence
# This assume an existing database.
import psycopg2
import psycopg2.errorcodes
import unittest2
import openerp
@ -111,11 +112,11 @@ class test_ir_sequence_no_gap(unittest2.TestCase):
cr0 = cursor()
cr1 = cursor()
cr1._default_log_exceptions = False # Prevent logging a traceback
msg_re = '^could not obtain lock on row in relation "ir_sequence"$'
with self.assertRaisesRegexp(psycopg2.OperationalError, msg_re):
with self.assertRaises(psycopg2.OperationalError) as e:
n0 = registry('ir.sequence').next_by_code(cr0, ADMIN_USER_ID, 'test_sequence_type_2', {})
assert n0
n1 = registry('ir.sequence').next_by_code(cr1, ADMIN_USER_ID, 'test_sequence_type_2', {})
self.assertEqual(e.exception.pgcode, psycopg2.errorcodes.LOCK_NOT_AVAILABLE, msg="postgresql returned an incorrect errcode")
cr0.close()
cr1.close()

View File

@ -14,7 +14,8 @@ class TestQWebTField(common.TransactionCase):
self.engine = self.registry('ir.qweb')
def context(self, values):
return ir_qweb.QWebContext(self.cr, self.uid, values)
return ir_qweb.QWebContext(
self.cr, self.uid, values, context={'inherit_branding': True})
def test_trivial(self):
field = document.createElement('span')

View File

@ -1,6 +1,8 @@
# -*- encoding: utf-8 -*-
from functools import partial
import unittest2
from lxml import etree as ET
from lxml.builder import E
@ -8,6 +10,24 @@ from openerp.tests import common
Field = E.field
class ViewCase(common.TransactionCase):
def setUp(self):
super(ViewCase, self).setUp()
self.addTypeEqualityFunc(ET._Element, self.assertTreesEqual)
def assertTreesEqual(self, n1, n2, msg=None):
self.assertEqual(n1.tag, n2.tag)
self.assertEqual((n1.text or '').strip(), (n2.text or '').strip(), msg)
self.assertEqual((n1.tail or '').strip(), (n2.tail or '').strip(), msg)
# Because lxml uses ordereddicts in which order is important to
# equality (!?!?!?!)
self.assertEqual(dict(n1.attrib), dict(n2.attrib), msg)
for c1, c2 in zip(n1, n2):
self.assertTreesEqual(c1, c2, msg)
class TestNodeLocator(common.BaseCase):
"""
The node locator returns None when it can not find a node, and the first
@ -98,7 +118,7 @@ class TestNodeLocator(common.BaseCase):
E.foo(attr='1', version='3'))
self.assertIsNone(node)
class TestViewInheritance(common.TransactionCase):
class TestViewInheritance(ViewCase):
def arch_for(self, name, view_type='form', parent=None):
""" Generates a trivial view of the specified ``view_type``.
@ -206,7 +226,7 @@ class TestViewInheritance(common.TransactionCase):
self.View.default_view(
self.cr, self.uid, model=self.model, view_type='graph'))
class TestApplyInheritanceSpecs(common.TransactionCase):
class TestApplyInheritanceSpecs(ViewCase):
""" Applies a sequence of inheritance specification nodes to a base
architecture. IO state parameters (cr, uid, model, context) are used for
error reporting
@ -230,8 +250,8 @@ class TestApplyInheritanceSpecs(common.TransactionCase):
spec, None)
self.assertEqual(
ET.tostring(self.base_arch),
ET.tostring(E.form(Field(name="replacement"), string="Title")))
self.base_arch,
E.form(Field(name="replacement"), string="Title"))
def test_delete(self):
spec = Field(name="target", position="replace")
@ -241,8 +261,8 @@ class TestApplyInheritanceSpecs(common.TransactionCase):
spec, None)
self.assertEqual(
ET.tostring(self.base_arch),
ET.tostring(E.form(string="Title")))
self.base_arch,
E.form(string="Title"))
def test_insert_after(self):
spec = Field(
@ -254,12 +274,12 @@ class TestApplyInheritanceSpecs(common.TransactionCase):
spec, None)
self.assertEqual(
ET.tostring(self.base_arch),
ET.tostring(E.form(
self.base_arch,
E.form(
Field(name="target"),
Field(name="inserted"),
string="Title"
)))
))
def test_insert_before(self):
spec = Field(
@ -271,11 +291,11 @@ class TestApplyInheritanceSpecs(common.TransactionCase):
spec, None)
self.assertEqual(
ET.tostring(self.base_arch),
ET.tostring(E.form(
self.base_arch,
E.form(
Field(name="inserted"),
Field(name="target"),
string="Title")))
string="Title"))
def test_insert_inside(self):
default = Field(Field(name="inserted"), name="target")
@ -289,13 +309,13 @@ class TestApplyInheritanceSpecs(common.TransactionCase):
spec, None)
self.assertEqual(
ET.tostring(self.base_arch),
ET.tostring(E.form(
self.base_arch,
E.form(
Field(
Field(name="inserted"),
Field(name="inserted 2"),
name="target"),
string="Title")))
string="Title"))
def test_unpack_data(self):
spec = E.data(
@ -310,15 +330,15 @@ class TestApplyInheritanceSpecs(common.TransactionCase):
spec, None)
self.assertEqual(
ET.tostring(self.base_arch),
ET.tostring(E.form(
self.base_arch,
E.form(
Field(
Field(name="inserted 0"),
Field(name="inserted 1"),
Field(name="inserted 2"),
Field(name="inserted 3"),
name="target"),
string="Title")))
string="Title"))
def test_invalid_position(self):
spec = Field(
@ -350,18 +370,18 @@ class TestApplyInheritanceSpecs(common.TransactionCase):
self.base_arch,
spec, None)
class TestApplyInheritedArchs(common.TransactionCase):
class TestApplyInheritedArchs(ViewCase):
""" Applies a sequence of modificator archs to a base view
"""
class TestViewCombined(common.TransactionCase):
class TestViewCombined(ViewCase):
"""
Test fallback operations of View.read_combined:
* defaults mapping
* ?
"""
class TestNoModel(common.TransactionCase):
class TestNoModel(ViewCase):
def test_create_view_nomodel(self):
View = self.registry('ir.ui.view')
view_id = View.create(self.cr, self.uid, {
@ -411,13 +431,11 @@ class TestNoModel(common.TransactionCase):
'value': translated_text,
})
sarch = View.translate_qweb(self.cr, self.uid, None, self.arch, 'fr_FR')
self.text_para.text = translated_text
self.assertEqual(
ET.tostring(sarch, encoding='utf-8'),
ET.tostring(self.arch, encoding='utf-8'))
class TestTemplating(common.TransactionCase):
self.text_para.text = translated_text
self.assertEqual(sarch, self.arch)
class TestTemplating(ViewCase):
def setUp(self):
import openerp.modules
super(TestTemplating, self).setUp()
@ -473,7 +491,126 @@ class TestTemplating(common.TransactionCase):
second.get('data-oe-id'),
"second should come from the extension view")
class test_views(common.TransactionCase):
def test_branding_distribute_inner(self):
""" Checks that the branding is correctly distributed within a view
extension
"""
Views = self.registry('ir.ui.view')
id = Views.create(self.cr, self.uid, {
'name': "Base view",
'type': 'qweb',
'arch': """<root>
<item order="1"/>
</root>"""
})
id2 = Views.create(self.cr, self.uid, {
'name': "Extension",
'type': 'qweb',
'inherit_id': id,
'arch': """<xpath expr="//item" position="before">
<item order="2">
<content t-att-href="foo">bar</content>
</item>
</xpath>"""
})
arch_string = Views.read_combined(
self.cr, self.uid, id, fields=['arch'],
context={'inherit_branding': True})['arch']
arch = ET.fromstring(arch_string)
Views.distribute_branding(arch)
self.assertEqual(
arch,
E.root(
E.item(
E.content("bar", {
't-att-href': "foo",
'data-oe-model': 'ir.ui.view',
'data-oe-id': str(id2),
'data-oe-field': 'arch',
'data-oe-xpath': '/xpath/item/content[1]',
}), {
'order': '2',
'data-oe-source-id': str(id)
}),
E.item({
'order': '1',
'data-oe-model': 'ir.ui.view',
'data-oe-id': str(id),
'data-oe-field': 'arch',
'data-oe-xpath': '/root[1]/item[1]'
})
)
)
def test_esc_no_branding(self):
Views = self.registry('ir.ui.view')
id = Views.create(self.cr, self.uid, {
'name': "Base View",
'type': 'qweb',
'arch': """<root>
<item><span t-esc="foo"/></item>
</root>""",
})
arch_string = Views.read_combined(
self.cr, self.uid, id, fields=['arch'],
context={'inherit_branding': True})['arch']
arch = ET.fromstring(arch_string)
Views.distribute_branding(arch)
self.assertEqual(arch, E.root(E.item(E.span({'t-esc': "foo"}))))
def test_ignore_unbrand(self):
Views = self.registry('ir.ui.view')
id = Views.create(self.cr, self.uid, {
'name': "Base view",
'type': 'qweb',
'arch': """<root>
<item order="1" t-ignore="true">
<t t-esc="foo"/>
</item>
</root>"""
})
id2 = Views.create(self.cr, self.uid, {
'name': "Extension",
'type': 'qweb',
'inherit_id': id,
'arch': """<xpath expr="//item[@order='1']" position="inside">
<item order="2">
<content t-att-href="foo">bar</content>
</item>
</xpath>"""
})
arch_string = Views.read_combined(
self.cr, self.uid, id, fields=['arch'],
context={'inherit_branding': True})['arch']
arch = ET.fromstring(arch_string)
Views.distribute_branding(arch)
self.assertEqual(
arch,
E.root(
E.item(
{'t-ignore': 'true', 'order': '1'},
E.t({'t-esc': 'foo'}),
E.item(
{'order': '2', 'data-oe-source-id': str(id)},
E.content(
{'t-att-href': 'foo'},
"bar")
)
)
),
"t-ignore should apply to injected sub-view branding, not just to"
" the main view's"
)
class test_views(ViewCase):
def test_nonexistent_attribute_removal(self):
Views = self.registry('ir.ui.view')
@ -589,16 +726,17 @@ class test_views(common.TransactionCase):
})
self.assertEqual(view['type'], 'form')
self.assertEqual(
ET.tostring(ET.fromstring(
ET.fromstring(
view['arch'],
parser=ET.XMLParser(remove_blank_text=True)
)),
'<form string="Replacement title" version="7.0">'
'<p>Replacement data</p>'
'<footer thing="bob">'
'<button name="action_next" type="object" string="New button"/>'
'</footer>'
'</form>')
),
E.form(
E.p("Replacement data"),
E.footer(
E.button(name="action_next", type="object", string="New button"),
thing="bob"
),
string="Replacement title", version="7.0"))
def test_view_inheritance_divergent_models(self):
Views = self.registry('ir.ui.view')
@ -656,14 +794,13 @@ class test_views(common.TransactionCase):
})
self.assertEqual(view['type'], 'form')
self.assertEqual(
ET.tostring(ET.fromstring(
ET.fromstring(
view['arch'],
parser=ET.XMLParser(remove_blank_text=True)
)),
'<form string="Replacement title" version="7.0">'
'<p>Replacement data</p>'
'<footer>'
'<button name="action_next" type="object" string="New button"/>'
'</footer>'
'</form>')
),
E.form(
E.p("Replacement data"),
E.footer(
E.button(name="action_next", type="object", string="New button")),
string="Replacement title", version="7.0"
))

View File

@ -112,11 +112,14 @@ class TestCurrencyExport(TestExport):
'widget': 'monetary',
'display_currency': 'c2'
}
context = dict(inherit_branding=True)
converted = converter.to_html(
self.cr, self.uid, 'value', obj, options,
doc.createElement('span'),
{'field': 'obj.value', 'field-options': json.dumps(options)},
'', ir_qweb.QWebContext(self.cr, self.uid, {'obj': obj, 'c2': dest, }))
'', ir_qweb.QWebContext(self.cr, self.uid, {'obj': obj, 'c2': dest, }),
context=context,
)
return converted
def test_currency_post(self):

View File

@ -309,8 +309,9 @@ class test_m2o(CreatorCase):
def test_external_id(self):
integer_id = self.registry('export.integer').create(
self.cr, openerp.SUPERUSER_ID, {'value': 42})
# __export__.$class.$id
external_id = u'__export__.export_many2one_%d' % integer_id
# Expecting the m2o target model name in the external id,
# not this model's name
external_id = u'__export__.export_integer_%d' % integer_id
self.assertEqual(
self.export(integer_id, fields=['value/id']),
[[external_id]])

View File

@ -133,6 +133,10 @@ class WebRequest(object):
self.auth_method = None
self._cr_cm = None
self._cr = None
# prevents transaction commit, use when you catch an exception during handling
self._failed = None
# set db/uid trackers - they're cleaned up at the WSGI
# dispatching phase in openerp.service.wsgi_server.application
if self.db:
@ -180,10 +184,13 @@ class WebRequest(object):
_request_stack.pop()
if self._cr:
# Dont commit test cursors
if not openerp.tests.common.release_test_cursor(self.session_id):
if exc_type is None:
# Dont close test cursors
if not openerp.tests.common.release_test_cursor(self._cr):
if exc_type is None and not self._failed:
self._cr.commit()
else:
# just to be explicit - happens at close() anyway
self._cr.rollback()
self._cr.close()
# just to be sure no one tries to re-use the request
self.disable_db = True
@ -209,20 +216,19 @@ class WebRequest(object):
# Backward for 7.0
if self.endpoint.first_arg_is_req:
args = (request,) + args
# Correct exception handling and concurency retry
@service_model.check
def checked_call(___dbname, *a, **kw):
return self.endpoint(*a, **kw)
# FIXME: code and rollback management could be cleaned
try:
if self.db:
return checked_call(self.db, *args, **kwargs)
return self.endpoint(*args, **kwargs)
except Exception:
# The decorator can call us more than once if there is an database error. In this
# case, the request cursor is unusable. Rollback transaction to create a new one.
if self._cr:
self._cr.rollback()
raise
return self.endpoint(*a, **kw)
if self.db:
return checked_call(self.db, *args, **kwargs)
return self.endpoint(*args, **kwargs)
@property
def debug(self):
@ -362,21 +368,25 @@ class JsonRequest(WebRequest):
response['id'] = self.jsonrequest.get('id')
response["result"] = self._call_function(**self.params)
except AuthenticationError, e:
_logger.exception("Exception during JSON request handling.")
_logger.exception("JSON-RPC AuthenticationError in %s.", self.httprequest.path)
se = serialize_exception(e)
error = {
'code': 100,
'message': "OpenERP Session Invalid",
'data': se
}
self._failed = e # prevent tx commit
except Exception, e:
_logger.exception("Exception during JSON request handling.")
# Mute test cursor error for runbot
if not (openerp.tools.config['test_enable'] and isinstance(e, psycopg2.OperationalError)):
_logger.exception("JSON-RPC Exception in %s.", self.httprequest.path)
se = serialize_exception(e)
error = {
'code': 200,
'message': "OpenERP Server Error",
'data': se
}
self._failed = e # prevent tx commit
if error:
response["error"] = error
@ -733,7 +743,7 @@ class OpenERPSession(werkzeug.contrib.sessions.Session):
self.setdefault("uid", None)
self.setdefault("login", None)
self.setdefault("password", None)
self.setdefault("context", {'tz': "UTC", "uid": None})
self.setdefault("context", {})
def get_context(self):
"""
@ -1210,11 +1220,6 @@ class CommonController(Controller):
""" Method used by client APIs to contact OpenERP. """
return openerp.netsvc.dispatch_rpc(service, method, args)
@route('/gen_session_id', type='json', auth="none")
def gen_session_id(self):
nsession = root.session_store.new()
return nsession.sid
root = None
def wsgi_postload():

View File

@ -187,6 +187,10 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
# Yamel test
report.record_result(load_test(module_name, idref, mode))
# Python tests
ir_http = registry['ir.http']
if hasattr(ir_http, '_routing_map'):
# Force routing map to be rebuilt between each module test suite
del(ir_http._routing_map)
report.record_result(openerp.modules.module.run_unit_tests(module_name, cr.dbname))
processed_modules.append(package.name)

View File

@ -38,7 +38,6 @@ import openerp.release as release
from openerp.tools.safe_eval import safe_eval as eval
_logger = logging.getLogger(__name__)
_test_logger = logging.getLogger('openerp.tests')
# addons path as a list
ad_paths = []
@ -340,7 +339,8 @@ def get_test_modules(module):
# Use a custom stream object to log the test executions.
class TestStream(object):
def __init__(self):
def __init__(self, logger_name='openerp.tests'):
self.logger = logging.getLogger(logger_name)
self.r = re.compile(r'^-*$|^ *... *$|^ok$')
def flush(self):
pass
@ -352,7 +352,7 @@ class TestStream(object):
if not first:
c = '` ' + c
first = False
_test_logger.info(c)
self.logger.info(c)
current_test = None
@ -369,9 +369,9 @@ def run_unit_tests(module_name, dbname):
for m in mods:
tests = unwrap_suite(unittest2.TestLoader().loadTestsFromModule(m))
suite = unittest2.TestSuite(itertools.ifilter(runs_at_install, tests))
_logger.info('module %s: running test %s.', module_name, m.__name__)
_logger.info('running %s tests.', m.__name__)
result = unittest2.TextTestRunner(verbosity=2, stream=TestStream()).run(suite)
result = unittest2.TextTestRunner(verbosity=2, stream=TestStream(m.__name__)).run(suite)
if not result.wasSuccessful():
r = False

View File

@ -2,7 +2,7 @@
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2012 OpenERP SA (<http://www.openerp.com>)
# Copyright (C) 2004-2014 OpenERP SA (<http://www.openerp.com>)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -89,7 +89,13 @@ class ColoredFormatter(DBFormatter):
record.levelname = COLOR_PATTERN % (30 + fg_color, 40 + bg_color, record.levelname)
return DBFormatter.format(self, record)
_logger_init = False
def init_logger():
global _logger_init
if _logger_init:
return
_logger_init = True
from tools.translate import resetlocale
resetlocale()
@ -137,6 +143,8 @@ def init_logger():
formatter = DBFormatter(format)
handler.setFormatter(formatter)
logging.getLogger().addHandler(handler)
# Configure handlers
pseudo_config = PSEUDOCONFIG_MAPPER.get(tools.config['log_level'], [])
@ -147,11 +155,7 @@ def init_logger():
loggername, level = logconfig_item.split(':')
level = getattr(logging, level, logging.INFO)
logger = logging.getLogger(loggername)
logger.handlers = []
logger.setLevel(level)
logger.addHandler(handler)
if loggername != '':
logger.propagate = False
for logconfig_item in logging_configurations:
_logger.debug('logger level set: "%s"', logconfig_item)

View File

@ -236,15 +236,24 @@ class char(_column):
class text(_column):
_type = 'text'
class html(text):
_type = 'html'
_symbol_c = '%s'
def _symbol_f(x):
if x is None or x == False:
def _symbol_set_html(self, value):
if value is None or value is False:
return None
return html_sanitize(x)
_symbol_set = (_symbol_c, _symbol_f)
if not self._sanitize:
return value
return html_sanitize(value)
def __init__(self, string='unknown', sanitize=True, **args):
super(html, self).__init__(string=string, **args)
self._sanitize = sanitize
# symbol_set redefinition because of sanitize specific behavior
self._symbol_f = self._symbol_set_html
self._symbol_set = (self._symbol_c, self._symbol_f)
import __builtin__

View File

@ -75,7 +75,7 @@ _schema = logging.getLogger(__name__ + '.schema')
# List of etree._Element subclasses that we choose to ignore when parsing XML.
from openerp.tools import SKIPPED_ELEMENT_TYPES
regex_order = re.compile('^(([a-z0-9_]+|"[a-z0-9_]+")( *desc| *asc)?( *, *|))+$', re.I)
regex_order = re.compile('^( *([a-z0-9_]+|"[a-z0-9_]+")( *desc| *asc)?( *, *|))+$', re.I)
regex_object_name = re.compile(r'^[a-z0-9_.]+$')
AUTOINIT_RECALCULATE_STORED_FIELDS = 1000
@ -1124,7 +1124,7 @@ class BaseModel(object):
def _get_xml_id(self, cr, uid, r):
model_data = self.pool.get('ir.model.data')
data_ids = model_data.search(cr, uid, [('model', '=', r._table_name), ('res_id', '=', r['id'])])
data_ids = model_data.search(cr, uid, [('model', '=', r._model._name), ('res_id', '=', r['id'])])
if len(data_ids):
d = model_data.read(cr, uid, data_ids, ['name', 'module'])[0]
if d['module']:
@ -1134,13 +1134,13 @@ class BaseModel(object):
else:
postfix = 0
while True:
n = self._table+'_'+str(r['id']) + (postfix and ('_'+str(postfix)) or '' )
n = r._model._table+'_'+str(r['id']) + (postfix and ('_'+str(postfix)) or '' )
if not model_data.search(cr, uid, [('name', '=', n)]):
break
postfix += 1
model_data.create(cr, SUPERUSER_ID, {
'name': n,
'model': self._name,
'model': r._model._name,
'res_id': r['id'],
'module': '__export__',
})
@ -2185,36 +2185,42 @@ class BaseModel(object):
r['__fold'] = folded.get(r[groupby] and r[groupby][0], False)
return result
def _read_group_generate_order_by(self, orderby, aggregated_fields, groupby, query):
def _read_group_prepare(self, orderby, aggregated_fields, groupby, qualified_groupby_field, query, groupby_type=None):
"""
Generates the ORDER BY sql clause for the read group method. Adds the missing JOIN clause
Prepares the GROUP BY and ORDER BY terms for the read_group method. Adds the missing JOIN clause
to the query if order should be computed against m2o field.
:param orderby: the orderby definition in the form "%(field)s %(order)s"
:param aggregated_fields: list of aggregated fields in the query
:param groupby: the current groupby field name
:param query: the query object used to construct the query afterwards
:param qualified_groupby_field: the fully qualified SQL name for the grouped field
:param osv.Query query: the query under construction
:param groupby_type: the type of the grouped field
:return: (groupby_terms, orderby_terms)
"""
orderby_list = []
ob = []
for order_splits in orderby.split(','):
order_split = order_splits.split()
orderby_field = order_split[0]
fields = openerp.osv.fields
if isinstance(self._all_columns[orderby_field].column, (fields.date, fields.datetime)):
continue
orderby_dir = len(order_split) == 2 and order_split[1].upper() == 'ASC' and 'ASC' or 'DESC'
if orderby_field == groupby:
orderby_item = self._generate_order_by(order_splits, query).replace('ORDER BY ', '')
if orderby_item:
orderby_list.append(orderby_item)
ob += [obi.split()[0] for obi in orderby_item.split(',')]
elif orderby_field in aggregated_fields:
orderby_list.append('%s %s' % (orderby_field,orderby_dir))
orderby_terms = []
groupby_terms = [qualified_groupby_field] if groupby else []
if not orderby:
return groupby_terms, orderby_terms
if orderby_list:
return ' ORDER BY %s' % (','.join(orderby_list)), ob and ','.join(ob) or ''
else:
return '', ''
self._check_qorder(orderby)
for order_part in orderby.split(','):
order_split = order_part.split()
order_field = order_split[0]
if order_field == groupby:
if groupby_type == 'many2one':
order_clause = self._generate_order_by(order_part, query).replace('ORDER BY ', '')
if order_clause:
orderby_terms.append(order_clause)
groupby_terms += [order_term.split()[0] for order_term in order_clause.split(',')]
else:
orderby_terms.append(order_part)
elif order_field in aggregated_fields:
orderby_terms.append(order_part)
else:
# Cannot order by a field that will not appear in the results (needs to be grouped or aggregated)
_logger.warn('%s: read_group order by `%s` ignored, cannot sort on empty columns (not grouped/aggregated)',
self._name, order_part)
return groupby_terms, orderby_terms
def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False):
"""
@ -2275,9 +2281,9 @@ class BaseModel(object):
# TODO it seems fields_get can be replaced by _all_columns (no need for translation)
fget = self.fields_get(cr, uid, fields)
flist = ''
group_count = group_by = groupby
group_by_params = {}
select_terms = []
groupby_type = None
if groupby:
if fget.get(groupby):
groupby_type = fget[groupby]['type']
@ -2305,12 +2311,9 @@ class BaseModel(object):
timezone = context.get('tz', 'UTC')
qualified_groupby_field = "timezone('%s', timezone('UTC',%s))" % (timezone, qualified_groupby_field)
qualified_groupby_field = "date_trunc('%s', %s)" % (interval, qualified_groupby_field)
flist = "%s as %s " % (qualified_groupby_field, groupby)
elif groupby_type == 'boolean':
qualified_groupby_field = "coalesce(%s,false)" % qualified_groupby_field
flist = "%s as %s " % (qualified_groupby_field, groupby)
else:
flist = qualified_groupby_field
select_terms.append("%s as %s " % (qualified_groupby_field, groupby))
else:
# Don't allow arbitrary values, as this would be a SQL injection vector!
raise except_orm(_('Invalid group_by'),
@ -2318,34 +2321,48 @@ class BaseModel(object):
aggregated_fields = [
f for f in fields
if f not in ('id', 'sequence')
if f not in ('id', 'sequence', groupby)
if fget[f]['type'] in ('integer', 'float')
if (f in self._all_columns and getattr(self._all_columns[f].column, '_classic_write'))]
for f in aggregated_fields:
group_operator = fget[f].get('group_operator', 'sum')
if flist:
flist += ', '
qualified_field = self._inherits_join_calc(f, query)
flist += "%s(%s) AS %s" % (group_operator, qualified_field, f)
select_terms.append("%s(%s) AS %s" % (group_operator, qualified_field, f))
order = orderby or groupby
orderby_clause = ''
ob = ''
if order:
orderby_clause, ob = self._read_group_generate_order_by(order, aggregated_fields, groupby, query)
gb = groupby and (' GROUP BY ' + qualified_groupby_field) or ''
order = orderby or groupby or ''
groupby_terms, orderby_terms = self._read_group_prepare(order, aggregated_fields, groupby, qualified_groupby_field, query, groupby_type)
from_clause, where_clause, where_clause_params = query.get_sql()
where_clause = where_clause and ' WHERE ' + where_clause
limit_str = limit and ' limit %d' % limit or ''
offset_str = offset and ' offset %d' % offset or ''
if len(groupby_list) < 2 and context.get('group_by_no_leaf'):
group_count = '_'
cr.execute('SELECT min(%s.id) AS id, count(%s.id) AS %s_count' % (self._table, self._table, group_count) + (flist and ',') + flist + ' FROM ' + from_clause + where_clause + gb + (ob and ',') + ob + orderby_clause + limit_str + offset_str, where_clause_params)
alldata = {}
groupby = group_by
count_field = '_'
else:
count_field = groupby
prefix_terms = lambda prefix, terms: (prefix + " " + ",".join(terms)) if terms else ''
prefix_term = lambda prefix, term: ('%s %s' % (prefix, term)) if term else ''
query = """
SELECT min(%(table)s.id) AS id, count(%(table)s.id) AS %(count_field)s_count
%(extra_fields)s
FROM %(from)s
%(where)s
%(groupby)s
%(orderby)s
%(limit)s
%(offset)s
""" % {
'table': self._table,
'count_field': count_field,
'extra_fields': prefix_terms(',', select_terms),
'from': from_clause,
'where': prefix_term('WHERE', where_clause),
'groupby': prefix_terms('GROUP BY', groupby_terms),
'orderby': prefix_terms('ORDER BY', orderby_terms),
'limit': prefix_term('LIMIT', int(limit) if limit else None),
'offset': prefix_term('OFFSET', int(offset) if limit else None),
}
cr.execute(query, where_clause_params)
alldata = {}
fetched_data = cr.dictfetchall()
data_ids = []
@ -2356,8 +2373,6 @@ class BaseModel(object):
data_ids.append(r['id'])
del r['id']
if groupby:
data = self.read(cr, uid, data_ids, [groupby], context=context)
# restore order of the search as read() uses the default _order (this is only for groups, so the footprint of data should be small):
@ -2847,7 +2862,7 @@ class BaseModel(object):
msg = "Table '%s': dropping index for column '%s' of type '%s' as it is not required anymore"
_schema.debug(msg, self._table, k, f._type)
if isinstance(f, fields.many2one):
if isinstance(f, fields.many2one) or (isinstance(f, fields.function) and f._type == 'many2one' and f.store):
dest_model = self.pool[f._obj]
if dest_model._table != 'ir_actions':
self._m2o_fix_foreign_key(cr, self._table, k, dest_model, f.ondelete)
@ -2882,7 +2897,7 @@ class BaseModel(object):
todo_end.append((order, self._update_store, (f, k)))
# and add constraints if needed
if isinstance(f, fields.many2one):
if isinstance(f, fields.many2one) or (isinstance(f, fields.function) and f._type == 'many2one' and f.store):
if f._obj not in self.pool:
raise except_orm('Programming Error', 'There is no reference available for %s' % (f._obj,))
dest_model = self.pool[f._obj]

View File

@ -1,12 +1,15 @@
# -*- coding: utf-8 -*-
import base64
import contextlib
from contextlib import closing
from functools import wraps
import logging
import os
import shutil
import threading
import traceback
from contextlib import contextmanager, closing
import tempfile
import zipfile
import psycopg2
import openerp
from openerp import SUPERUSER_ID
@ -28,7 +31,8 @@ def _initialize_db(id, db_name, demo, lang, user_password):
self_actions[id]['progress'] = 0
db = openerp.sql_db.db_connect(db_name)
with closing(db.cursor()) as cr:
openerp.modules.db.initialize(cr) # TODO this should be removed as it is done by RegistryManager.new().
# TODO this should be removed as it is done by RegistryManager.new().
openerp.modules.db.initialize(cr)
openerp.tools.config['lang'] = lang
cr.commit()
@ -55,14 +59,13 @@ def _initialize_db(id, db_name, demo, lang, user_password):
self_actions[id]['traceback'] = traceback.format_exc()
def dispatch(method, params):
if method in [ 'create', 'get_progress', 'drop', 'dump',
'restore', 'rename',
'change_admin_password', 'migrate_databases',
'create_database', 'duplicate_database' ]:
if method in ['create', 'get_progress', 'drop', 'dump', 'restore', 'rename',
'change_admin_password', 'migrate_databases',
'create_database', 'duplicate_database']:
passwd = params[0]
params = params[1:]
security.check_super(passwd)
elif method in [ 'db_exist', 'list', 'list_lang', 'server_version' ]:
elif method in ['db_exist', 'list', 'list_lang', 'server_version']:
# params = params
# No security check for these methods
pass
@ -78,9 +81,9 @@ def _create_empty_database(name):
cr.execute("SELECT datname FROM pg_database WHERE datname = %s",
(name,))
if cr.fetchall():
raise openerp.exceptions.Warning(" %s database already exists!" % name )
raise openerp.exceptions.Warning("database %r already exists!" % (name,))
else:
cr.autocommit(True) # avoid transaction block
cr.autocommit(True) # avoid transaction block
cr.execute("""CREATE DATABASE "%s" ENCODING 'unicode' TEMPLATE "%s" """ % (name, chosen_template))
def exp_create(db_name, demo, lang, user_password='admin'):
@ -96,7 +99,7 @@ def exp_create(db_name, demo, lang, user_password='admin'):
_logger.info('CREATE DATABASE %s', db_name.lower())
create_thread = threading.Thread(target=_initialize_db,
args=(id, db_name, demo, lang, user_password))
args=(id, db_name, demo, lang, user_password))
create_thread.start()
self_actions[id]['thread'] = create_thread
return id
@ -121,14 +124,14 @@ def exp_duplicate_database(db_original_name, db_name):
openerp.sql_db.close_db(db_original_name)
db = openerp.sql_db.db_connect('postgres')
with closing(db.cursor()) as cr:
cr.autocommit(True) # avoid transaction block
cr.autocommit(True) # avoid transaction block
cr.execute("""CREATE DATABASE "%s" ENCODING 'unicode' TEMPLATE "%s" """ % (db_name, db_original_name))
return True
def exp_get_progress(id):
if self_actions[id]['thread'].isAlive():
# return openerp.modules.init_progress[db_name]
return min(self_actions[id].get('progress', 0),0.95), []
return min(self_actions[id].get('progress', 0), 0.95), []
else:
clean = self_actions[id]['clean']
if clean:
@ -140,9 +143,9 @@ def exp_get_progress(id):
self_actions.pop(id)
return 1.0, users
else:
e = self_actions[id]['exception'] # TODO this seems wrong: actions[id]['traceback'] is set, but not 'exception'.
self_actions.pop(id)
raise Exception, e
a = self_actions.pop(id)
exc, tb = a['exception'], a['traceback']
raise Exception, exc, tb
def exp_drop(db_name):
if db_name not in exp_list(True):
@ -152,18 +155,17 @@ def exp_drop(db_name):
db = openerp.sql_db.db_connect('postgres')
with closing(db.cursor()) as cr:
cr.autocommit(True) # avoid transaction block
cr.autocommit(True) # avoid transaction block
# Try to terminate all other connections that might prevent
# dropping the database
try:
# PostgreSQL 9.2 renamed pg_stat_activity.procpid to pid:
# http://www.postgresql.org/docs/9.2/static/release-9-2.html#AEN110389
pid_col = 'pid' if cr._cnx.server_version >= 90200 else 'procpid'
cr.execute("""SELECT pg_terminate_backend(%(pid_col)s)
FROM pg_stat_activity
WHERE datname = %%s AND
WHERE datname = %%s AND
%(pid_col)s != pg_backend_pid()""" % {'pid_col': pid_col},
(db_name,))
except Exception:
@ -178,94 +180,140 @@ def exp_drop(db_name):
_logger.info('DROP DB: %s', db_name)
return True
@contextlib.contextmanager
def _set_pg_password_in_environment():
def _set_pg_password_in_environment(func):
""" On systems where pg_restore/pg_dump require an explicit
password (i.e. when not connecting via unix sockets, and most
importantly on Windows), it is necessary to pass the PG user
password in the environment or in a special .pgpass file.
This context management method handles setting
This decorator handles setting
:envvar:`PGPASSWORD` if it is not already
set, and removing it afterwards.
See also http://www.postgresql.org/docs/8.4/static/libpq-envars.html
.. note:: This is not thread-safe, and should never be enabled for
SaaS (giving SaaS users the super-admin password is not a good idea
anyway)
"""
if os.environ.get('PGPASSWORD') or not openerp.tools.config['db_password']:
yield
else:
os.environ['PGPASSWORD'] = openerp.tools.config['db_password']
try:
yield
finally:
del os.environ['PGPASSWORD']
@wraps(func)
def wrapper(*args, **kwargs):
if os.environ.get('PGPASSWORD') or not openerp.tools.config['db_password']:
return func(*args, **kwargs)
else:
os.environ['PGPASSWORD'] = openerp.tools.config['db_password']
try:
return func(*args, **kwargs)
finally:
del os.environ['PGPASSWORD']
return wrapper
def exp_dump(db_name):
with _set_pg_password_in_environment():
cmd = ['pg_dump', '--format=c', '--no-owner']
with tempfile.TemporaryFile() as t:
dump_db(db_name, t)
t.seek(0)
return t.read().encode('base64')
@_set_pg_password_in_environment
def dump_db(db, stream):
"""Dump database `db` into file-like object `stream`"""
with openerp.tools.osutil.tempdir() as dump_dir:
registry = openerp.modules.registry.RegistryManager.get(db)
with registry.cursor() as cr:
filestore = registry['ir.attachment']._filestore(cr, SUPERUSER_ID)
if os.path.exists(filestore):
shutil.copytree(filestore, os.path.join(dump_dir, 'filestore'))
dump_file = os.path.join(dump_dir, 'dump.sql')
cmd = ['pg_dump', '--format=p', '--no-owner', '--file=' + dump_file]
if openerp.tools.config['db_user']:
cmd.append('--username=' + openerp.tools.config['db_user'])
if openerp.tools.config['db_host']:
cmd.append('--host=' + openerp.tools.config['db_host'])
if openerp.tools.config['db_port']:
cmd.append('--port=' + str(openerp.tools.config['db_port']))
cmd.append(db_name)
cmd.append(db)
stdin, stdout = openerp.tools.exec_pg_command_pipe(*tuple(cmd))
stdin.close()
data = stdout.read()
res = stdout.close()
if openerp.tools.exec_pg_command(*cmd):
_logger.error('DUMP DB: %s failed! Please verify the configuration of the database '
'password on the server. You may need to create a .pgpass file for '
'authentication, or specify `db_password` in the server configuration '
'file.', db)
raise Exception("Couldn't dump database")
if not data or res:
_logger.error(
'DUMP DB: %s failed! Please verify the configuration of the database password on the server. '
'You may need to create a .pgpass file for authentication, or specify `db_password` in the '
'server configuration file.\n %s', db_name, data)
raise Exception, "Couldn't dump database"
_logger.info('DUMP DB successful: %s', db_name)
openerp.tools.osutil.zip_dir(dump_dir, stream, include_dir=False)
return base64.encodestring(data)
_logger.info('DUMP DB successful: %s', db)
def exp_restore(db_name, data):
with _set_pg_password_in_environment():
if exp_db_exist(db_name):
_logger.warning('RESTORE DB: %s already exists', db_name)
raise Exception, "Database already exists"
def exp_restore(db_name, data, copy=False):
data_file = tempfile.NamedTemporaryFile(delete=False)
try:
data_file.write(data.decode('base64'))
data_file.close()
restore_db(db_name, data_file.name, copy=copy)
finally:
os.unlink(data_file.name)
return True
_create_empty_database(db_name)
@_set_pg_password_in_environment
def restore_db(db, dump_file, copy=False):
assert isinstance(db, basestring)
if exp_db_exist(db):
_logger.warning('RESTORE DB: %s already exists', db)
raise Exception("Database already exists")
cmd = ['pg_restore', '--no-owner']
_create_empty_database(db)
filestore_path = None
with openerp.tools.osutil.tempdir() as dump_dir:
if zipfile.is_zipfile(dump_file):
# v8 format
with zipfile.ZipFile(dump_file, 'r') as z:
# only extract known members!
filestore = [m for m in z.namelist() if m.startswith('filestore/')]
z.extractall(dump_dir, ['dump.sql'] + filestore)
if filestore:
filestore_path = os.path.join(dump_dir, 'filestore')
pg_cmd = 'psql'
pg_args = ['-q', '-f', os.path.join(dump_dir, 'dump.sql')]
else:
# <= 7.0 format (raw pg_dump output)
pg_cmd = 'pg_restore'
pg_args = ['--no-owner', dump_file]
args = []
if openerp.tools.config['db_user']:
cmd.append('--username=' + openerp.tools.config['db_user'])
args.append('--username=' + openerp.tools.config['db_user'])
if openerp.tools.config['db_host']:
cmd.append('--host=' + openerp.tools.config['db_host'])
args.append('--host=' + openerp.tools.config['db_host'])
if openerp.tools.config['db_port']:
cmd.append('--port=' + str(openerp.tools.config['db_port']))
cmd.append('--dbname=' + db_name)
args2 = tuple(cmd)
args.append('--port=' + str(openerp.tools.config['db_port']))
args.append('--dbname=' + db)
pg_args = args + pg_args
buf=base64.decodestring(data)
if os.name == "nt":
tmpfile = (os.environ['TMP'] or 'C:\\') + os.tmpnam()
file(tmpfile, 'wb').write(buf)
args2=list(args2)
args2.append(tmpfile)
args2=tuple(args2)
stdin, stdout = openerp.tools.exec_pg_command_pipe(*args2)
if not os.name == "nt":
stdin.write(base64.decodestring(data))
stdin.close()
res = stdout.close()
if res:
raise Exception, "Couldn't restore database"
_logger.info('RESTORE DB: %s', db_name)
if openerp.tools.exec_pg_command(pg_cmd, *pg_args):
raise Exception("Couldn't restore database")
return True
registry = openerp.modules.registry.RegistryManager.new(db)
with registry.cursor() as cr:
if copy:
# if it's a copy of a database, force generation of a new dbuuid
registry['ir.config_parameter'].init(cr, force=True)
if filestore_path:
filestore_dest = registry['ir.attachment']._filestore(cr, SUPERUSER_ID)
shutil.move(filestore_path, filestore_dest)
if openerp.tools.config['unaccent']:
try:
with cr.savepoint():
cr.execute("CREATE EXTENSION unaccent")
except psycopg2.Error:
pass
_logger.info('RESTORE DB: %s', db)
def exp_rename(old_name, new_name):
openerp.modules.registry.RegistryManager.delete(old_name)
@ -273,7 +321,7 @@ def exp_rename(old_name, new_name):
db = openerp.sql_db.db_connect('postgres')
with closing(db.cursor()) as cr:
cr.autocommit(True) # avoid transaction block
cr.autocommit(True) # avoid transaction block
try:
cr.execute('ALTER DATABASE "%s" RENAME TO "%s"' % (old_name, new_name))
_logger.info('RENAME DB: %s -> %s', old_name, new_name)
@ -282,6 +330,7 @@ def exp_rename(old_name, new_name):
raise Exception("Couldn't rename database %s to %s: %s" % (old_name, new_name, e))
return True
@openerp.tools.mute_logger('openerp.sql_db')
def exp_db_exist(db_name):
## Not True: in fact, check if connection to database is possible. The database may exists
return bool(openerp.sql_db.db_connect(db_name))

View File

@ -148,7 +148,7 @@ class Cursor(object):
def check(f):
@wraps(f)
def wrapper(self, *args, **kwargs):
if self.__closed:
if self._closed:
msg = 'Unable to use a closed cursor.'
if self.__closer:
msg += ' It was closed at %s, line %s' % self.__closer
@ -165,7 +165,7 @@ class Cursor(object):
self.sql_log = _logger.isEnabledFor(logging.DEBUG)
self.sql_log_count = 0
self.__closed = True # avoid the call of close() (by __del__) if an exception
self._closed = True # avoid the call of close() (by __del__) if an exception
# is raised by any of the following initialisations
self._pool = pool
self.dbname = dbname
@ -180,7 +180,7 @@ class Cursor(object):
self.__caller = frame_codeinfo(currentframe(),2)
else:
self.__caller = False
self.__closed = False # real initialisation value
self._closed = False # real initialisation value
self.autocommit(False)
self.__closer = False
@ -189,7 +189,7 @@ class Cursor(object):
self.cache = {}
def __del__(self):
if not self.__closed and not self._cnx.closed:
if not self._closed and not self._cnx.closed:
# Oops. 'self' has not been closed explicitly.
# The cursor will be deleted by the garbage collector,
# but the database connection is not put back into the connection
@ -302,7 +302,7 @@ class Cursor(object):
# collected as fast as they should). The problem is probably due in
# part because browse records keep a reference to the cursor.
del self._obj
self.__closed = True
self._closed = True
# Clean the underlying connection.
self._cnx.rollback()

View File

@ -15,9 +15,10 @@ import time
import unittest2
import urllib2
import xmlrpclib
from datetime import datetime, timedelta
import werkzeug
import openerp
_logger = logging.getLogger(__name__)
@ -48,10 +49,9 @@ def acquire_test_cursor(session_id):
cr._test_lock.acquire()
return cr
def release_test_cursor(session_id):
def release_test_cursor(cr):
if openerp.tools.config['test_enable']:
cr = HTTP_SESSION.get(session_id)
if cr:
if hasattr(cr, '_test_lock'):
cr._test_lock.release()
return True
return False
@ -212,7 +212,8 @@ class HttpCase(TransactionCase):
# OSError, and no errno/strerror/filename, only a pair of
# unnamed arguments (matching errno and strerror)
err, _ = e.args
if err == errno.EINTR: continue
if err == errno.EINTR:
continue
raise
if ready:
@ -224,24 +225,24 @@ class HttpCase(TransactionCase):
# process lines
if '\n' in buf:
line, buf = buf.split('\n', 1)
line = str(line)
if 'CoreText' in line:
continue
# relay everything from console.log, even 'ok' or 'error...' lines
_logger.info("phantomjs: %s", line)
if line == "ok":
break
if line.startswith("error"):
line_ = line[6:]
try: line_ = json.loads(line_)
except ValueError: pass
# when error occurs the execution stack may be sent as as JSON
try:
line_ = json.loads(line_)
except ValueError:
pass
self.fail(line_ or "phantomjs test failed")
try: line = json.loads(line)
except ValueError: pass
_logger.info("phantomjs: %s", line)
def phantom_run(self, cmd, timeout):
_logger.debug('executing `%s`', ' '.join(cmd))
_logger.info('phantom_run executing %s', ' '.join(cmd))
try:
phantom = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
except OSError:
@ -252,6 +253,7 @@ class HttpCase(TransactionCase):
# kill phantomjs if phantom.exit() wasn't called in the test
if phantom.poll() is None:
phantom.terminate()
_logger.info("phantom_run execution finished")
def phantom_jsfile(self, jsfile, timeout=30, **kw):
options = {

View File

@ -113,20 +113,22 @@ function PhantomTest() {
console.log('loaded', url, status);
// process ready
waitFor(function() {
console.log("waiting for: calling page evaluate");
console.log("PhantomTest.run: wait for condition: " + ready);
return self.page.evaluate(function (ready) {
console.log("waiting for", ready);
var r = false;
try {
console.log("waiting for: page evaluating ", ready);
console.log("page.evaluate eval expr:", ready);
r = !!eval(ready);
} catch(ex) { }
console.log("waiting for: returning from page evaluate");
} catch(ex) {
}
console.log("page.evaluate eval result:", r);
return r;
}, ready);
// run test
}, function() {
console.log("PhantomTest.run: condition statified, executing: " + code);
self.page.evaluate(function (code) { return eval(code); }, code);
console.log("PhantomTest.run: execution launched, waiting for console.log('ok')...");
});
}
});

View File

@ -32,7 +32,7 @@ from random import randint
# Image resizing
# ----------------------------------------
def image_resize_image(base64_source, size=(1024, 1024), encoding='base64', filetype='PNG', avoid_if_small=False):
def image_resize_image(base64_source, size=(1024, 1024), encoding='base64', filetype=None, avoid_if_small=False):
""" Function to resize an image. The image will be resized to the given
size, while keeping the aspect ratios, and holes in the image will be
filled with transparent background. The image will not be stretched if
@ -58,7 +58,8 @@ def image_resize_image(base64_source, size=(1024, 1024), encoding='base64', file
height mean an automatically computed value based respectivelly
on height or width of the source image.
:param encoding: the output encoding
:param filetype: the output filetype
:param filetype: the output filetype, by default the source image's
:type filetype: str, any PIL image format (supported for creation)
:param avoid_if_small: do not resize if image height and width
are smaller than the expected size.
"""
@ -68,6 +69,8 @@ def image_resize_image(base64_source, size=(1024, 1024), encoding='base64', file
return base64_source
image_stream = StringIO.StringIO(base64_source.decode(encoding))
image = Image.open(image_stream)
# store filetype here, as Image.new below will lose image.format
filetype = filetype or image.format
asked_width, asked_height = size
if asked_width is None:
@ -95,21 +98,21 @@ def image_resize_image(base64_source, size=(1024, 1024), encoding='base64', file
image.save(background_stream, filetype)
return background_stream.getvalue().encode(encoding)
def image_resize_image_big(base64_source, size=(1204, 1204), encoding='base64', filetype='PNG', avoid_if_small=True):
def image_resize_image_big(base64_source, size=(1204, 1024), encoding='base64', filetype=None, avoid_if_small=True):
""" Wrapper on image_resize_image, to resize images larger than the standard
'big' image size: 1024x1024px.
:param size, encoding, filetype, avoid_if_small: refer to image_resize_image
"""
return image_resize_image(base64_source, size, encoding, filetype, avoid_if_small)
def image_resize_image_medium(base64_source, size=(128, 128), encoding='base64', filetype='PNG', avoid_if_small=False):
def image_resize_image_medium(base64_source, size=(128, 128), encoding='base64', filetype=None, avoid_if_small=False):
""" Wrapper on image_resize_image, to resize to the standard 'medium'
image size: 180x180.
:param size, encoding, filetype, avoid_if_small: refer to image_resize_image
"""
return image_resize_image(base64_source, size, encoding, filetype, avoid_if_small)
def image_resize_image_small(base64_source, size=(64, 64), encoding='base64', filetype='PNG', avoid_if_small=False):
def image_resize_image_small(base64_source, size=(64, 64), encoding='base64', filetype=None, avoid_if_small=False):
""" Wrapper on image_resize_image, to resize to the standard 'small' image
size: 50x50.
:param size, encoding, filetype, avoid_if_small: refer to image_resize_image

View File

@ -23,8 +23,12 @@
Some functions related to the os and os.path module
"""
from contextlib import contextmanager
import os
from os.path import join as opj
import shutil
import tempfile
import zipfile
if os.name == 'nt':
import ctypes
@ -61,6 +65,30 @@ def walksymlinks(top, topdown=True, onerror=None):
if not topdown:
yield dirpath, dirnames, filenames
@contextmanager
def tempdir():
tmpdir = tempfile.mkdtemp()
try:
yield tmpdir
finally:
shutil.rmtree(tmpdir)
def zip_dir(path, stream, include_dir=True): # TODO add ignore list
path = os.path.normpath(path)
len_prefix = len(os.path.dirname(path)) if include_dir else len(path)
if len_prefix:
len_prefix += 1
with zipfile.ZipFile(stream, 'w', compression=zipfile.ZIP_DEFLATED, allowZip64=True) as zipf:
for dirpath, dirnames, filenames in os.walk(path):
for fname in filenames:
bname, ext = os.path.splitext(fname)
ext = ext or bname
if ext not in ['.pyc', '.pyo', '.swp', '.DS_Store']:
path = os.path.normpath(os.path.join(dirpath, fname))
if os.path.isfile(path):
zipf.write(path, path[len_prefix:])
if os.name != 'nt':
getppid = os.getppid

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
##############################################################################
# Copyright (C) 2004-2012 OpenERP s.a. (<http://www.openerp.com>).
# Copyright (C) 2004-2014 OpenERP s.a. (<http://www.openerp.com>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -225,9 +225,14 @@ def safe_eval(expr, globals_dict=None, locals_dict=None, mode="eval", nocopy=Fal
'False': False,
'None': None,
'str': str,
'unicode': unicode,
'globals': locals,
'locals': locals,
'bool': bool,
'int': int,
'float': float,
'long': long,
'enumerate': enumerate,
'dict': dict,
'list': list,
'tuple': tuple,
@ -235,15 +240,23 @@ def safe_eval(expr, globals_dict=None, locals_dict=None, mode="eval", nocopy=Fal
'abs': abs,
'min': min,
'max': max,
'sum': sum,
'reduce': reduce,
'filter': filter,
'round': round,
'len': len,
'set': set,
'repr': repr,
'int': int,
'float': float,
'set': set,
'all': all,
'any': any,
'ord': ord,
'chr': chr,
'cmp': cmp,
'divmod': divmod,
'isinstance': isinstance,
'range': range,
'xrange': xrange,
'zip': zip,
}
)
if locals_builtins: