[MERGE] merge with latest trunk

bzr revid: rma@tinyerp.com-20140411061647-m6iupt9567igzs4x
This commit is contained in:
Randhir Mayatra rma-openerp 2014-04-11 11:46:47 +05:30
commit cbc86bb4ee
108 changed files with 891 additions and 627 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-28 05:26+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 05:50+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:26+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 05:50+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:27+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 05:51+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:27+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 05:51+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:28+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 05:52+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:27+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 05:51+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:28+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 05:52+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:28+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 05:52+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:28+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 05:53+0000\n"
"X-Generator: Launchpad (build 16976)\n"
"X-Poedit-Language: Czech\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-28 05:28+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 05:53+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:30+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 05:55+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:30+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 05:55+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:39+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 06:08+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:36+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 06:04+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:38+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 06:08+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:39+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 06:10+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:39+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 06:09+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:40+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 06:10+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:39+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 06:08+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:41+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 06:12+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:40+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 06:11+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:41+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 06:12+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:38+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 06:07+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:29+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 05:53+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:27+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 05:51+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:34+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 06:00+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:41+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 06:12+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:29+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 05:54+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:29+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 05:54+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:39+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 06:09+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:30+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 05:55+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:30+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 05:56+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:31+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 05:56+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:31+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 05:56+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:35+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 06:03+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:31+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 05:56+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:27+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 05:51+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:31+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 05:57+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:31+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 05:57+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:32+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 05:57+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:32+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 05:58+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:29+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 05:54+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:32+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 05:58+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:33+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 05:58+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:33+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 05:59+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:33+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 05:58+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:33+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 05:59+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:34+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 05:59+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:34+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 06:00+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:28+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 05:53+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:39+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 06:09+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:34+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 06:00+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:34+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 06:01+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:38+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 06:07+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:35+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 06:01+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:35+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 06:02+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:36+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 06:03+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:36+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 06:04+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:27+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 05:50+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:35+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 06:02+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:41+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 06:12+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:36+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 06:04+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:37+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 06:05+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:37+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 06:05+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:37+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 06:05+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:37+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 06:06+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:37+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 06:06+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:37+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 06:07+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:40+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 06:11+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:38+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 06:07+0000\n"
"X-Generator: Launchpad (build 16976)\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-28 05:40+0000\n"
"X-Generator: Launchpad (build 16967)\n"
"X-Launchpad-Export-Date: 2014-04-09 06:10+0000\n"
"X-Generator: Launchpad (build 16976)\n"
#. module: base
#: model:ir.module.module,description:base.module_account_check_writing

View File

@ -104,7 +104,9 @@ class ir_actions_report_xml(osv.osv):
cr.execute("SELECT * FROM ir_act_report_xml WHERE report_name=%s", (name,))
r = cr.dictfetchone()
if r:
if r['report_rml'] or r['report_rml_content_data']:
if r['report_type'] in ['qweb-pdf', 'qweb-html']:
return r['report_name']
elif r['report_rml'] or r['report_rml_content_data']:
if r['parser']:
kwargs = { 'parser': operator.attrgetter(r['parser'])(openerp.addons) }
else:
@ -127,7 +129,11 @@ class ir_actions_report_xml(osv.osv):
Look up a report definition and render the report for the provided IDs.
"""
new_report = self._lookup_report(cr, name)
return new_report.create(cr, uid, res_ids, data, context)
# in order to use current yml test files with qweb reports
if isinstance(new_report, (str, unicode)):
return self.pool['report'].get_pdf(cr, uid, res_ids, new_report, data=data, context=context), 'pdf'
else:
return new_report.create(cr, uid, res_ids, data, context)
_name = 'ir.actions.report.xml'
_inherit = 'ir.actions.actions'
@ -944,7 +950,8 @@ class ir_actions_server(osv.osv):
'uid': uid,
'user': user,
'context': context,
'workflow': workflow
'workflow': workflow,
'Warning': openerp.exceptions.Warning,
}
def run(self, cr, uid, ids, context=None):

View File

@ -85,8 +85,8 @@ class ir_http(osv.AbstractModel):
return auth_method
def _handle_exception(self, exception):
# If handle exception return something different than None, it will be used as a response
raise
# If handle_exception returns something different than None, it will be used as a response
return request._handle_exception(exception)
def _dispatch(self):
# locate the controller method

View File

@ -241,7 +241,7 @@ class QWeb(orm.AbstractModel):
if attribute_name == "groups":
cr = qwebcontext.get('request') and qwebcontext['request'].cr or None
uid = qwebcontext.get('request') and qwebcontext['request'].uid or None
can_see = self.user_has_groups(cr, uid, groups=attribute_value)
can_see = self.user_has_groups(cr, uid, groups=attribute_value) if cr and uid else False
if not can_see:
return ''
continue
@ -332,7 +332,9 @@ class QWeb(orm.AbstractModel):
return self.render_element(element, template_attributes, generated_attributes, qwebcontext, inner)
def render_tag_esc(self, element, template_attributes, generated_attributes, qwebcontext):
inner = werkzeug.utils.escape(self.eval_str(template_attributes["esc"], qwebcontext))
options = json.loads(template_attributes.get('esc-options') or '{}')
widget = self.get_widget_for(options.get('widget', ''))
inner = widget.format(template_attributes['esc'], options, qwebcontext)
return self.render_element(element, template_attributes, generated_attributes, qwebcontext, inner)
def render_tag_foreach(self, element, template_attributes, generated_attributes, qwebcontext):
@ -419,6 +421,9 @@ class QWeb(orm.AbstractModel):
return self.pool.get('ir.qweb.field.' + field_type,
self.pool['ir.qweb.field'])
def get_widget_for(self, widget):
return self.pool.get('ir.qweb.widget.' + widget, self.pool['ir.qweb.widget'])
#--------------------------------------------------------------------
# QWeb Fields converters
#--------------------------------------------------------------------
@ -603,7 +608,7 @@ class DateTimeConverter(osv.AbstractModel):
if isinstance(value, basestring):
value = datetime.datetime.strptime(
value, openerp.tools.DEFAULT_SERVER_DATETIME_FORMAT)
value = column.context_timestamp(
value = fields.datetime.context_timestamp(
cr, uid, timestamp=value, context=context)
if options and 'format' in options:
@ -840,6 +845,38 @@ class Contact(orm.AbstractModel):
return HTMLSafe(html)
class QwebWidget(osv.AbstractModel):
_name = 'ir.qweb.widget'
def _format(self, inner, options, qwebcontext):
return self.pool['ir.qweb'].eval_str(inner, qwebcontext)
def format(self, inner, options, qwebcontext):
return werkzeug.utils.escape(self._format(inner, options, qwebcontext))
class QwebWidgetMonetary(osv.AbstractModel):
_name = 'ir.qweb.widget.monetary'
_inherit = 'ir.qweb.widget'
def _format(self, inner, options, qwebcontext):
inner = self.pool['ir.qweb'].eval(inner, qwebcontext)
display = self.pool['ir.qweb'].eval_object(options['display_currency'], qwebcontext)
precision = int(round(math.log10(display.rounding)))
fmt = "%.{0}f".format(-precision if precision < 0 else 0)
lang_code = qwebcontext.context.get('lang') or 'en_US'
formatted_amount = self.pool['res.lang'].format(
qwebcontext.cr, qwebcontext.uid, [lang_code], fmt, inner, grouping=True, monetary=True
)
pre = post = u''
if display.position == 'before':
pre = u'{symbol} '
else:
post = u' {symbol}'
return u'{pre}{0}{post}'.format(
formatted_amount, pre=pre, post=post
).format(symbol=display.symbol,)
class HTMLSafe(object):
""" HTMLSafe string wrapper, Werkzeug's escape() has special handling for
objects with a ``__html__`` methods but AFAIK does not provide any such

View File

@ -73,12 +73,18 @@ class view_custom(osv.osv):
class view(osv.osv):
_name = 'ir.ui.view'
def _get_model_data(self, cr, uid, ids, *args, **kwargs):
ir_model_data = self.pool.get('ir.model.data')
data_ids = ir_model_data.search(cr, uid, [('model', '=', self._name), ('res_id', 'in', ids)])
result = dict(zip(ids, data_ids))
def _get_model_data(self, cr, uid, ids, fname, args, context=None):
result = dict.fromkeys(ids, False)
IMD = self.pool['ir.model.data']
data_ids = IMD.search_read(cr, uid, [('res_id', 'in', ids), ('model', '=', 'ir.ui.view')], ['res_id'], context=context)
result.update(map(itemgetter('res_id', 'id'), data_ids))
return result
def _views_from_model_data(self, cr, uid, ids, context=None):
IMD = self.pool['ir.model.data']
data_ids = IMD.search_read(cr, uid, [('id', 'in', ids), ('model', '=', 'ir.ui.view')], ['res_id'], context=context)
return map(itemgetter('res_id'), data_ids)
_columns = {
'name': fields.char('View Name', required=True),
'model': fields.char('Object', select=True),
@ -97,7 +103,11 @@ class view(osv.osv):
'inherit_id': fields.many2one('ir.ui.view', 'Inherited View', ondelete='cascade', select=True),
'inherit_children_ids': fields.one2many('ir.ui.view','inherit_id', 'Inherit Views'),
'field_parent': fields.char('Child Field'),
'model_data_id': fields.function(_get_model_data, type='many2one', relation='ir.model.data', string="Model Data", store=True),
'model_data_id': fields.function(_get_model_data, type='many2one', relation='ir.model.data', string="Model Data",
store={
_name: (lambda s, c, u, i, ctx=None: i, None, 10),
'ir.model.data': (_views_from_model_data, ['model', 'res_id'], 10),
}),
'xml_id': fields.function(osv.osv.get_xml_id, type='char', size=128, string="External ID",
help="ID of the view defined in xml file"),
'groups_id': fields.many2many('res.groups', 'ir_ui_view_group_rel', 'view_id', 'group_id',

View File

@ -31,6 +31,7 @@ import shutil
import tempfile
import urllib
import urllib2
import urlparse
import zipfile
import zipimport
import lxml.html
@ -41,6 +42,7 @@ except ImportError:
from StringIO import StringIO # NOQA
import openerp
import openerp.exceptions
from openerp import modules, tools
from openerp.modules.db import create_categories
from openerp.modules import get_module_resource
@ -621,40 +623,14 @@ class module(osv.osv):
return res
def download(self, cr, uid, ids, download=True, context=None):
res = []
default_version = modules.adapt_version('1.0')
for mod in self.browse(cr, uid, ids, context=context):
if not mod.url:
continue
match = re.search('-([a-zA-Z0-9\._-]+)(\.zip)', mod.url, re.I)
version = default_version
if match:
version = match.group(1)
if parse_version(mod.installed_version) >= parse_version(version):
continue
res.append(mod.url)
if not download:
continue
zip_content = urllib.urlopen(mod.url).read()
fname = modules.get_module_path(str(mod.name) + '.zip', downloaded=True)
try:
with open(fname, 'wb') as fp:
fp.write(zip_content)
except Exception:
_logger.exception('Error when trying to create module '
'file %s', fname)
raise orm.except_orm(_('Error'), _('Can not create the module file:\n %s') % (fname,))
terp = self.get_module_info(mod.name)
self.write(cr, uid, mod.id, self.get_values_from_terp(terp))
cr.execute('DELETE FROM ir_module_module_dependency WHERE module_id = %s', (mod.id,))
self._update_dependencies(cr, uid, mod, terp.get('depends', []))
self._update_category(cr, uid, mod, terp.get('category', 'Uncategorized'))
# Import module
zimp = zipimport.zipimporter(fname)
zimp.load_module(mod.name)
return res
return []
def install_from_urls(self, cr, uid, urls, context=None):
if not self.pool['res.users'].has_group(cr, uid, 'base.group_system'):
raise openerp.exceptions.AccessDenied()
apps_server = urlparse.urlparse(self.get_apps_server(cr, uid, context=context))
OPENERP = 'openerp'
tmp = tempfile.mkdtemp()
_logger.debug('Install from url: %r', urls)
@ -663,6 +639,11 @@ class module(osv.osv):
for module_name, url in urls.items():
if not url:
continue # nothing to download, local version is already the last one
up = urlparse.urlparse(url)
if up.scheme != apps_server.scheme or up.netloc != apps_server.netloc:
raise openerp.exceptions.AccessDenied()
try:
_logger.info('Downloading module `%s` from OpenERP Apps', module_name)
content = urllib2.urlopen(url).read()
@ -727,8 +708,8 @@ class module(osv.osv):
finally:
shutil.rmtree(tmp)
def install_by_names(self, cr, uid, names, context=None):
raise NotImplementedError('# TODO')
def get_apps_server(self, cr, uid, context=None):
return tools.config.get('apps_server', 'https://apps.openerp.com/apps')
def _update_dependencies(self, cr, uid, mod_browse, depends=None):
if depends is None:

View File

@ -37,6 +37,23 @@ and Ubuntu distros, we have to override the search path, too.
"""
_logger = logging.getLogger(__name__)
# Alternatives for the [broken] builtin PDF fonts. Default order chosen to match
# the pre-v8 mapping from openerp.report.render.rml2pdf.customfonts.CustomTTFonts.
# Format: [ (BuiltinFontFamily, mode, [AlternativeFontName, ...]), ...]
BUILTIN_ALTERNATIVES = [
('Helvetica', "normal", ["DejaVuSans", "LiberationSans"]),
('Helvetica', "bold", ["DejaVuSans-Bold", "LiberationSans-Bold"]),
('Helvetica', 'italic', ["DejaVuSans-Oblique", "LiberationSans-Italic"]),
('Helvetica', 'bolditalic', ["DejaVuSans-BoldOblique", "LiberationSans-BoldItalic"]),
('Times', 'normal', ["LiberationSerif", "DejaVuSerif"]),
('Times', 'bold', ["LiberationSerif-Bold", "DejaVuSerif-Bold"]),
('Times', 'italic', ["LiberationSerif-Italic", "DejaVuSerif-Italic"]),
('Times', 'bolditalic', ["LiberationSerif-BoldItalic", "DejaVuSerif-BoldItalic"]),
('Courier', 'normal', ["FreeMono", "DejaVuSansMono"]),
('Courier', 'bold', ["FreeMonoBold", "DejaVuSansMono-Bold"]),
('Courier', 'italic', ["FreeMonoOblique", "DejaVuSansMono-Oblique"]),
('Courier', 'bolditalic', ["FreeMonoBoldOblique", "DejaVuSansMono-BoldOblique"]),
]
class res_font(osv.Model):
_name = "res.font"
@ -113,9 +130,32 @@ class res_font(osv.Model):
def _sync(self, cr, uid, context=None):
"""Set the customfonts.CustomTTFonts list to the content of the database"""
customfonts.CustomTTFonts = []
local_family_modes = set()
local_font_paths = {}
found_fonts_ids = self.search(cr, uid, [('path', '!=', '/dev/null')], context=context)
for font in self.browse(cr, uid, found_fonts_ids, context=None):
local_family_modes.add((font.family, font.mode))
local_font_paths[font.name] = font.path
customfonts.CustomTTFonts.append((font.family, font.name, font.path, font.mode))
# Attempt to remap the builtin fonts (Helvetica, Times, Courier) to better alternatives
# if available, because they only support a very small subset of unicode
# (missing 'č' for example)
for builtin_font_family, mode, alts in BUILTIN_ALTERNATIVES:
if (builtin_font_family, mode) not in local_family_modes:
# No local font exists with that name, try alternatives
for altern_font in alts:
if local_font_paths.get(altern_font):
altern_def = (builtin_font_family, altern_font,
local_font_paths[altern_font], mode)
customfonts.CustomTTFonts.append(altern_def)
_logger.debug("Builtin remapping %r", altern_def)
break
else:
_logger.warning("No local alternative found for builtin font `%s` (%s mode)."
"Consider installing the DejaVu fonts if you have problems "
"with unicode characters in RML reports",
builtin_font_family, mode)
return True
def clear_caches(self):

View File

@ -28,6 +28,7 @@ import openerp
from openerp import SUPERUSER_ID
from openerp import tools
from openerp.osv import osv, fields
from openerp.osv.expression import get_unaccent_wrapper
from openerp.tools.translate import _
class format_address(object):
@ -228,10 +229,10 @@ class res_partner(osv.osv, format_address):
_order = "display_name"
_columns = {
'name': fields.char('Name', size=128, required=True, select=True),
'display_name': fields.function(_display_name, type='char', string='Name', store=_display_name_store_triggers),
'display_name': fields.function(_display_name, type='char', string='Name', store=_display_name_store_triggers, select=True),
'date': fields.date('Date', select=1),
'title': fields.many2one('res.partner.title', 'Title'),
'parent_id': fields.many2one('res.partner', 'Related Company'),
'parent_id': fields.many2one('res.partner', 'Related Company', select=True),
'child_ids': fields.one2many('res.partner', 'parent_id', 'Contacts', domain=[('active','=',True)]), # force "active_test" domain to bypass _search() override
'ref': fields.char('Reference', size=64, select=1),
'lang': fields.selection(_lang_get, 'Language',
@ -629,10 +630,17 @@ class res_partner(osv.osv, format_address):
if operator in ('=ilike', '=like'):
operator = operator[1:]
query = ('SELECT id FROM res_partner ' +
where_str + '(email ' + operator + ''' %s
OR display_name ''' + operator + ''' %s)
ORDER BY display_name''')
unaccent = get_unaccent_wrapper(cr)
query = """SELECT id
FROM res_partner
{where} ({email} {operator} {percent}
OR {display_name} {operator} {percent})
ORDER BY {display_name}
""".format(where=where_str, operator=operator,
email=unaccent('email'),
display_name=unaccent('display_name'),
percent=unaccent('%s'))
where_clause_params += [search_name, search_name]
if limit:

View File

@ -438,7 +438,7 @@
<field model="res.country" name="country_id" search="[('name','=','China')]"/>
<field name="country_id" ref="base.cn"/>
<field name="street">89 Dong Lu Road</field>
<field name="email">míng@yourcompany.example.com</field>
<field name="email">ming@yourcompany.example.com</field>
<field name="phone">+86 215 069 5177</field>
<field name="website">http://www.míng.com</field>
</record>

View File

@ -375,7 +375,7 @@ class res_users(osv.osv):
if not password:
return False
user_id = False
cr = self.pool.db.cursor()
cr = self.pool.cursor()
try:
# autocommit: our single update request will be performed atomically.
# (In this way, there is no opportunity to have two transactions
@ -426,7 +426,7 @@ class res_users(osv.osv):
# Successfully logged in as admin!
# Attempt to guess the web base url...
if user_agent_env and user_agent_env.get('base_location'):
cr = self.pool.db.cursor()
cr = self.pool.cursor()
try:
base = user_agent_env['base_location']
ICP = self.pool['ir.config_parameter']
@ -447,7 +447,7 @@ class res_users(osv.osv):
raise openerp.exceptions.AccessDenied()
if self._uid_cache.get(db, {}).get(uid) == passwd:
return
cr = self.pool.db.cursor()
cr = self.pool.cursor()
try:
self.check_credentials(cr, uid, passwd)
if self._uid_cache.has_key(db):

View File

@ -63,8 +63,8 @@ openerp.base = function(instance) {
if (instance.base.apps_client) {
return check_client_available(instance.base.apps_client);
} else {
var ICP = new instance.web.Model('ir.config_parameter');
return ICP.call('get_param', ['apps.server', 'https://apps.openerp.com/apps']).then(function(u) {
var Mod = new instance.web.Model('ir.module.module');
return Mod.call('get_apps_server').then(function(u) {
var link = $(_.str.sprintf('<a href="%s"></a>', u))[0];
var host = _.str.sprintf('%s//%s', link.protocol, link.host);
var dbname = link.pathname;

View File

@ -21,7 +21,7 @@ class test_cr_execute(unittest2.TestCase):
"""
Try to use iterable but non-list or int params in query parameters.
"""
with registry().cursor(auto_commit=False) as cr:
with registry().cursor() as cr:
with self.assertRaises(ValueError):
cr.execute("SELECT id FROM res_users WHERE login=%s", 'admin')
with self.assertRaises(ValueError):

View File

@ -1,6 +1,7 @@
import unittest2
import openerp
from openerp.osv.expression import get_unaccent_wrapper
from openerp.osv.orm import BaseModel
import openerp.tests.common as common
@ -124,6 +125,7 @@ class test_expression(common.TransactionCase):
def test_20_auto_join(self):
registry, cr, uid = self.registry, self.cr, self.uid
unaccent = get_unaccent_wrapper(cr)
# Get models
partner_obj = registry('res.partner')
@ -180,8 +182,11 @@ class test_expression(common.TransactionCase):
sql_query = self.query_list[0].get_sql()
self.assertIn('res_partner_bank', sql_query[0],
"_auto_join off: ('bank_ids.name', 'like', '..') first query incorrect main table")
self.assertIn('"res_partner_bank"."name" like %s', sql_query[1],
expected = "%s::text like %s" % (unaccent('"res_partner_bank"."name"'), unaccent('%s'))
self.assertIn(expected, sql_query[1],
"_auto_join off: ('bank_ids.name', 'like', '..') first query incorrect where condition")
self.assertEqual(set(['%' + name_test + '%']), set(sql_query[2]),
"_auto_join off: ('bank_ids.name', 'like', '..') first query incorrect parameter")
sql_query = self.query_list[2].get_sql()
@ -217,8 +222,11 @@ class test_expression(common.TransactionCase):
"_auto_join on: ('bank_ids.name', 'like', '..') query incorrect main table")
self.assertIn('"res_partner_bank" as "res_partner__bank_ids"', sql_query[0],
"_auto_join on: ('bank_ids.name', 'like', '..') query incorrect join")
self.assertIn('"res_partner__bank_ids"."name" like %s', sql_query[1],
expected = "%s::text like %s" % (unaccent('"res_partner__bank_ids"."name"'), unaccent('%s'))
self.assertIn(expected, sql_query[1],
"_auto_join on: ('bank_ids.name', 'like', '..') query incorrect where condition")
self.assertIn('"res_partner"."id"="res_partner__bank_ids"."partner_id"', sql_query[1],
"_auto_join on: ('bank_ids.name', 'like', '..') query incorrect join condition")
self.assertEqual(set(['%' + name_test + '%']), set(sql_query[2]),
@ -296,8 +304,11 @@ class test_expression(common.TransactionCase):
sql_query = self.query_list[0].get_sql()
self.assertIn('"res_country"', sql_query[0],
"_auto_join on for state_id: ('state_id.country_id.code', 'like', '..') query 1 incorrect main table")
self.assertIn('"res_country"."code" like %s', sql_query[1],
expected = "%s::text like %s" % (unaccent('"res_country"."code"'), unaccent('%s'))
self.assertIn(expected, sql_query[1],
"_auto_join on for state_id: ('state_id.country_id.code', 'like', '..') query 1 incorrect where condition")
self.assertEqual(['%' + name_test + '%'], sql_query[2],
"_auto_join on for state_id: ('state_id.country_id.code', 'like', '..') query 1 incorrect parameter")
sql_query = self.query_list[1].get_sql()
@ -327,8 +338,11 @@ class test_expression(common.TransactionCase):
"_auto_join on for country_id: ('state_id.country_id.code', 'like', '..') query 1 incorrect main table")
self.assertIn('"res_country" as "res_country_state__country_id"', sql_query[0],
"_auto_join on for country_id: ('state_id.country_id.code', 'like', '..') query 1 incorrect join")
self.assertIn('"res_country_state__country_id"."code" like %s', sql_query[1],
expected = "%s::text like %s" % (unaccent('"res_country_state__country_id"."code"'), unaccent('%s'))
self.assertIn(expected, sql_query[1],
"_auto_join on for country_id: ('state_id.country_id.code', 'like', '..') query 1 incorrect where condition")
self.assertIn('"res_country_state"."country_id"="res_country_state__country_id"."id"', sql_query[1],
"_auto_join on for country_id: ('state_id.country_id.code', 'like', '..') query 1 incorrect join condition")
self.assertEqual(['%' + name_test + '%'], sql_query[2],
@ -358,8 +372,11 @@ class test_expression(common.TransactionCase):
"_auto_join on: ('state_id.country_id.code', 'like', '..') query incorrect join")
self.assertIn('"res_country" as "res_partner__state_id__country_id"', sql_query[0],
"_auto_join on: ('state_id.country_id.code', 'like', '..') query incorrect join")
self.assertIn('"res_partner__state_id__country_id"."code" like %s', sql_query[1],
expected = "%s::text like %s" % (unaccent('"res_partner__state_id__country_id"."code"'), unaccent('%s'))
self.assertIn(expected, sql_query[1],
"_auto_join on: ('state_id.country_id.code', 'like', '..') query incorrect where condition")
self.assertIn('"res_partner"."state_id"="res_partner__state_id"."id"', sql_query[1],
"_auto_join on: ('state_id.country_id.code', 'like', '..') query incorrect join condition")
self.assertIn('"res_partner__state_id"."country_id"="res_partner__state_id__country_id"."id"', sql_query[1],
@ -385,7 +402,9 @@ class test_expression(common.TransactionCase):
"_auto_join on one2many with domains incorrect result")
# Test produced queries that domains effectively present
sql_query = self.query_list[0].get_sql()
self.assertIn('"res_partner__child_ids__bank_ids"."acc_number" like %s', sql_query[1],
expected = "%s::text like %s" % (unaccent('"res_partner__child_ids__bank_ids"."acc_number"'), unaccent('%s'))
self.assertIn(expected, sql_query[1],
"_auto_join on one2many with domains incorrect result")
# TDE TODO: check first domain has a correct table name
self.assertIn('"res_partner__child_ids"."name" = %s', sql_query[1],
@ -447,6 +466,19 @@ class test_expression(common.TransactionCase):
domain = [('x', 'in', ['y', 'z']), ('a.v', '=', 'e'), '|', '|', ('a', '=', 'b'), '!', ('c', '>', 'd'), ('e', '!=', 'f'), ('g', '=', 'h')]
norm_domain = ['&', '&', '&'] + domain
assert norm_domain == expression.normalize_domain(domain), "Non-normalized domains should be properly normalized"
def test_translate_search(self):
Country = self.registry('res.country')
be = self.ref('base.be')
domains = [
[('name', '=', 'Belgium')],
[('name', 'ilike', 'Belgi')],
[('name', 'in', ['Belgium', 'Care Bears'])],
]
for domain in domains:
ids = Country.search(self.cr, self.uid, domain)
self.assertListEqual([be], ids)
if __name__ == '__main__':
unittest2.main()

View File

@ -21,7 +21,7 @@ def registry(model):
return openerp.modules.registry.RegistryManager.get(DB)[model]
def cursor():
return openerp.modules.registry.RegistryManager.get(DB).db.cursor()
return openerp.modules.registry.RegistryManager.get(DB).cursor()
def drop_sequence(code):

View File

@ -134,13 +134,17 @@ class TestORM(common.TransactionCase):
rg = self.partner.read_group(self.cr, self.uid, domain, ['date'], 'date' + ':' + interval)
result = {}
for r in rg:
result[r['date']] = set(self.partner.search(self.cr, self.uid, r['__domain']))
result[r['date:' + interval]] = set(self.partner.search(self.cr, self.uid, r['__domain']))
return result
self.assertEqual(len(read_group('day')), len(partners_by_day))
self.assertEqual(len(read_group('month')), len(partners_by_month))
self.assertEqual(len(read_group('year')), len(partners_by_year))
rg = self.partner.read_group(self.cr, self.uid, [('id', 'in', all_partners)],
['date'], ['date:month', 'date:day'], lazy=False)
self.assertEqual(len(rg), len(all_partners))
class TestInherits(common.TransactionCase):
""" test the behavior of the orm for models that use _inherits;

View File

@ -13,7 +13,7 @@ def registry(model):
return openerp.modules.registry.RegistryManager.get(DB)[model]
def cursor():
return openerp.modules.registry.RegistryManager.get(DB).db.cursor()
return openerp.modules.registry.RegistryManager.get(DB).cursor()
def get_module(module_name):
registry = openerp.modules.registry.RegistryManager.get(DB)

View File

@ -28,7 +28,7 @@ class ViewCase(common.TransactionCase):
self.assertTreesEqual(c1, c2, msg)
class TestNodeLocator(common.BaseCase):
class TestNodeLocator(common.TransactionCase):
"""
The node locator returns None when it can not find a node, and the first
match when it finds something (no jquery-style node sets)

View File

@ -29,6 +29,7 @@ GNU Public Licence.
(c) 2003-TODAY, Fabien Pinckaers - OpenERP SA
"""
import atexit
import logging
import os
import signal
@ -78,16 +79,23 @@ def report_configuration():
('database user', config['db_user'])]:
_logger.info("%s: %s", name, value)
def rm_pid_file():
config = openerp.tools.config
if not openerp.evented and os.path.exists(config['pidfile']):
os.unlink(config['pidfile'])
def setup_pid_file():
""" Create a file with the process id written in it.
This function assumes the configuration has been initialized.
"""
config = openerp.tools.config
if config['pidfile']:
if not openerp.evented and config['pidfile']:
with open(config['pidfile'], 'w') as fd:
pidtext = "%d" % (os.getpid())
fd.write(pidtext)
atexit.register(rm_pid_file)
def export_translation():
config = openerp.tools.config
@ -103,7 +111,7 @@ def export_translation():
fileformat = os.path.splitext(config["translate_out"])[-1][1:].lower()
buf = file(config["translate_out"], "w")
registry = openerp.modules.registry.RegistryManager.new(dbname)
cr = registry.db.cursor()
cr = registry.cursor()
openerp.tools.trans_export(config["language"],
config["translate_modules"] or ["all"], buf, fileformat, cr)
cr.close()
@ -117,7 +125,7 @@ def import_translation():
dbname = config['db_name']
registry = openerp.modules.registry.RegistryManager.new(dbname)
cr = registry.db.cursor()
cr = registry.cursor()
openerp.tools.trans_load( cr, config["translate_in"], config["language"],
context=context)
cr.commit()
@ -155,8 +163,6 @@ def main(args):
setup_pid_file()
rc = openerp.service.server.start(preload=preload, stop=stop)
if config['pidfile']:
os.unlink(config['pidfile'])
sys.exit(rc)
class Server(Command):

View File

@ -235,10 +235,7 @@ class WebRequest(object):
"""
# some magic to lazy create the cr
if not self._cr:
# Test cursors
self._cr = openerp.tests.common.acquire_test_cursor(self.session_id)
if not self._cr:
self._cr = self.registry.db.cursor()
self._cr = self.registry.cursor()
return self._cr
def __enter__(self):
@ -249,14 +246,9 @@ class WebRequest(object):
_request_stack.pop()
if self._cr:
# 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()
if exc_type is None and not self._failed:
self._cr.commit()
self._cr.close()
# just to be sure no one tries to re-use the request
self.disable_db = True
self.uid = None
@ -270,6 +262,13 @@ class WebRequest(object):
self.endpoint = endpoint
self.auth_method = auth
def _handle_exception(self, exception):
"""Called within an except block to allow converting exceptions
to abitrary responses. Anything returned (except None) will
be used as response."""
raise
def _call_function(self, *args, **kwargs):
request = self
if self.endpoint.routing['type'] != self._request_type:
@ -287,7 +286,7 @@ class WebRequest(object):
def checked_call(___dbname, *a, **kw):
# 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 and not openerp.tools.config['test_enable']:
if self._cr:
self._cr.rollback()
return self.endpoint(*a, **kw)
@ -421,39 +420,15 @@ class JsonRequest(WebRequest):
self.params = dict(self.jsonrequest.get("params", {}))
self.context = self.params.pop('context', dict(self.session.context))
def dispatch(self):
""" Calls the method asked for by the JSON-RPC2 or JSONP request
"""
if self.jsonp_handler:
return self.jsonp_handler()
response = {"jsonrpc": "2.0" }
error = None
try:
response['id'] = self.jsonrequest.get('id')
response["result"] = self._call_function(**self.params)
except AuthenticationError, e:
_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:
# 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
def _json_response(self, result=None, error=None):
response = {
'jsonrpc': '2.0',
'id': self.jsonrequest.get('id')
}
if error is not None:
response['error'] = error
if result is not None:
response['result'] = result
if self.jsonp:
# If we use jsonp, that's mean we are called from another host
@ -466,8 +441,36 @@ class JsonRequest(WebRequest):
mime = 'application/json'
body = simplejson.dumps(response)
r = Response(body, headers=[('Content-Type', mime), ('Content-Length', len(body))])
return r
return Response(
body, headers=[('Content-Type', mime),
('Content-Length', len(body))])
def _handle_exception(self, exception):
"""Called within an except block to allow converting exceptions
to abitrary responses. Anything returned (except None) will
be used as response."""
_logger.exception("Exception during JSON request handling.")
self._failed = exception # prevent tx commit
error = {
'code': 200,
'message': "OpenERP Server Error",
'data': serialize_exception(exception)
}
if isinstance(exception, AuthenticationError):
error['code'] = 100
error['message'] = "OpenERP Session Invalid"
return self._json_response(error=error)
def dispatch(self):
""" Calls the method asked for by the JSON-RPC2 or JSONP request
"""
if self.jsonp_handler:
return self.jsonp_handler()
try:
result = self._call_function(**self.params)
return self._json_response(result)
except Exception, e:
return self._handle_exception(e)
def serialize_exception(e):
tmp = {

View File

@ -58,7 +58,10 @@ class Registry(Mapping):
self._init_modules = set()
self.db_name = db_name
self.db = openerp.sql_db.db_connect(db_name)
self._db = openerp.sql_db.db_connect(db_name)
# special cursor for test mode; None means "normal" mode
self.test_cr = None
# Indicates that the registry is
self.ready = False
@ -75,7 +78,7 @@ class Registry(Mapping):
# Useful only in a multi-process context.
self._any_cache_cleared = False
cr = self.db.cursor()
cr = self.cursor()
has_unaccent = openerp.modules.db.has_unaccent(cr)
if openerp.tools.config['unaccent'] and not has_unaccent:
_logger.warning("The option --unaccent was given but no unaccent() function was found in database.")
@ -102,6 +105,10 @@ class Registry(Mapping):
""" Return the model with the given name or raise KeyError if it doesn't exist."""
return self.models[model_name]
def __call__(self, model_name):
""" Same as ``self[model_name]``. """
return self.models[model_name]
def do_parent_store(self, cr):
for o in self._init_parent:
self.get(o)._parent_store_compute(cr)
@ -183,27 +190,38 @@ class Registry(Mapping):
r, c)
return r, c
@contextmanager
def cursor(self, auto_commit=True):
cr = self.db.cursor()
try:
yield cr
if auto_commit:
cr.commit()
finally:
cr.close()
def enter_test_mode(self):
""" Enter the 'test' mode, where one cursor serves several requests. """
assert self.test_cr is None
self.test_cr = self._db.test_cursor()
RegistryManager.enter_test_mode()
class TestRLock(object):
def __init__(self):
self._lock = threading.RLock()
def leave_test_mode(self):
""" Leave the test mode. """
assert self.test_cr is not None
self.test_cr.close(force=True) # close the cursor for real
self.test_cr = None
RegistryManager.leave_test_mode()
def cursor(self):
""" Return a new cursor for the database. The cursor itself may be used
as a context manager to commit/rollback and close automatically.
"""
if self.test_cr is not None:
# While in test mode, we use one special cursor across requests. The
# test cursor uses a reentrant lock to serialize accesses. The lock
# is granted here by cursor(), and automatically released by the
# cursor itself in its method close().
self.test_cr.acquire()
return self.test_cr
return self._db.cursor()
class DummyRLock(object):
""" Dummy reentrant lock, to be used while running rpc and js tests """
def acquire(self):
if openerp.tools.config['test_enable']:
return
return self._lock.acquire()
pass
def release(self):
if openerp.tools.config['test_enable']:
return
return self._lock.release()
pass
def __enter__(self):
self.acquire()
def __exit__(self, type, value, traceback):
@ -219,12 +237,30 @@ class RegistryManager(object):
# Mapping between db name and model registry.
# Accessed through the methods below.
registries = {}
registries_lock = TestRLock()
_lock = threading.RLock()
_saved_lock = None
@classmethod
def lock(cls):
""" Return the current registry lock. """
return cls._lock
@classmethod
def enter_test_mode(cls):
""" Enter the 'test' mode, where the registry is no longer locked. """
assert cls._saved_lock is None
cls._lock, cls._saved_lock = DummyRLock(), cls._lock
@classmethod
def leave_test_mode(cls):
""" Leave the 'test' mode. """
assert cls._saved_lock is not None
cls._lock, cls._saved_lock = cls._saved_lock, None
@classmethod
def get(cls, db_name, force_demo=False, status=None, update_module=False):
""" Return a registry for a given database name."""
with cls.registries_lock:
with cls.lock():
try:
return cls.registries[db_name]
except KeyError:
@ -244,7 +280,7 @@ class RegistryManager(object):
"""
import openerp.modules
with cls.registries_lock:
with cls.lock():
registry = Registry(db_name)
# Initializing a registry will call general code which will in turn
@ -259,7 +295,7 @@ class RegistryManager(object):
registry.base_registry_signaling_sequence = seq_registry
registry.base_cache_signaling_sequence = seq_cache
# This should be a method on Registry
openerp.modules.load_modules(registry.db, force_demo, status, update_module)
openerp.modules.load_modules(registry._db, force_demo, status, update_module)
except Exception:
del cls.registries[db_name]
raise
@ -269,7 +305,7 @@ class RegistryManager(object):
# Yeah, crazy.
registry = cls.registries[db_name]
cr = registry.db.cursor()
cr = registry.cursor()
try:
registry.do_parent_store(cr)
cr.commit()
@ -286,7 +322,7 @@ class RegistryManager(object):
@classmethod
def delete(cls, db_name):
"""Delete the registry linked to a given database. """
with cls.registries_lock:
with cls.lock():
if db_name in cls.registries:
cls.registries[db_name].clear_caches()
del cls.registries[db_name]
@ -294,7 +330,7 @@ class RegistryManager(object):
@classmethod
def delete_all(cls):
"""Delete all the registries. """
with cls.registries_lock:
with cls.lock():
for db_name in cls.registries.keys():
cls.delete(db_name)
@ -309,7 +345,7 @@ class RegistryManager(object):
This method is given to spare you a ``RegistryManager.get(db_name)``
that would loads the given database if it was not already loaded.
"""
with cls.registries_lock:
with cls.lock():
if db_name in cls.registries:
cls.registries[db_name].clear_caches()
@ -325,7 +361,7 @@ class RegistryManager(object):
changed = False
if openerp.multi_process and db_name in cls.registries:
registry = cls.get(db_name)
cr = registry.db.cursor()
cr = registry.cursor()
try:
cr.execute("""
SELECT base_registry_signaling.last_value,
@ -371,7 +407,7 @@ class RegistryManager(object):
registry = cls.get(db_name)
if registry.any_cache_cleared():
_logger.info("At least one model cache has been cleared, signaling through the database.")
cr = registry.db.cursor()
cr = registry.cursor()
r = 1
try:
cr.execute("select nextval('base_cache_signaling')")
@ -386,7 +422,7 @@ class RegistryManager(object):
if openerp.multi_process and db_name in cls.registries:
_logger.info("Registry changed, signaling through the database")
registry = cls.get(db_name)
cr = registry.db.cursor()
cr = registry.cursor()
r = 1
try:
cr.execute("select nextval('base_registry_signaling')")

View File

@ -429,6 +429,10 @@ def select_distinct_from_where_not_null(cr, select_field, from_table):
cr.execute('SELECT distinct("%s") FROM "%s" where "%s" is not null' % (select_field, from_table, select_field))
return [r[0] for r in cr.fetchall()]
def get_unaccent_wrapper(cr):
if openerp.modules.registry.RegistryManager.get(cr.dbname).has_unaccent:
return lambda x: "unaccent(%s)" % (x,)
return lambda x: x
# --------------------------------------------------
# ExtendedLeaf class for managing leafs and contexts
@ -630,7 +634,7 @@ class expression(object):
:attr list expression: the domain expression, that will be normalized
and prepared
"""
self.has_unaccent = openerp.modules.registry.RegistryManager.get(cr.dbname).has_unaccent
self._unaccent = get_unaccent_wrapper(cr)
self.joins = []
self.root_model = table
@ -1012,10 +1016,10 @@ class expression(object):
else:
if field._type == 'datetime' and right and len(right) == 10:
if operator in ('>', '>=', '='):
right += ' 00:00:00'
elif operator in ('<', '<='):
if operator in ('>', '<='):
right += ' 23:59:59'
else:
right += ' 00:00:00'
push(create_substitution_leaf(leaf, (left, operator, right), working_model))
elif field.translate and right:
@ -1030,33 +1034,37 @@ class expression(object):
sql_operator = sql_operator[4:] if sql_operator[:3] == 'not' else '='
inselect_operator = 'not inselect'
subselect = '( SELECT res_id' \
' FROM ir_translation' \
' WHERE name = %s' \
' AND lang = %s' \
' AND type = %s'
instr = ' %s'
#Covering in,not in operators with operands (%s,%s) ,etc.
if sql_operator == 'in':
instr = ','.join(['%s'] * len(right))
subselect += ' AND value ' + sql_operator + ' ' + " (" + instr + ")" \
') UNION (' \
' SELECT id' \
' FROM "' + working_model._table + '"' \
' WHERE "' + left + '" ' + sql_operator + ' ' + " (" + instr + "))"
else:
subselect += ' AND value ' + sql_operator + instr + \
') UNION (' \
' SELECT id' \
' FROM "' + working_model._table + '"' \
' WHERE "' + left + '" ' + sql_operator + instr + ")"
unaccent = self._unaccent if sql_operator.endswith('like') else lambda x: x
params = [working_model._name + ',' + left,
context.get('lang', False) or 'en_US',
'model',
right,
right,
]
trans_left = unaccent('value')
quote_left = unaccent(_quote(left))
instr = unaccent('%s')
if sql_operator == 'in':
# params will be flatten by to_sql() => expand the placeholders
instr = '(%s)' % ', '.join(['%s'] * len(right))
subselect = """(SELECT res_id
FROM ir_translation
WHERE name = %s
AND lang = %s
AND type = %s
AND {trans_left} {operator} {right}
) UNION (
SELECT id
FROM "{table}"
WHERE {left} {operator} {right}
)
""".format(trans_left=trans_left, operator=sql_operator,
right=instr, table=working_model._table, left=quote_left)
params = (
working_model._name + ',' + left,
context.get('lang') or 'en_US',
'model',
right,
right,
)
push(create_substitution_leaf(leaf, ('id', inselect_operator, (subselect, params)), working_model))
else:
@ -1174,15 +1182,15 @@ class expression(object):
else:
need_wildcard = operator in ('like', 'ilike', 'not like', 'not ilike')
sql_operator = {'=like': 'like', '=ilike': 'ilike'}.get(operator, operator)
cast = '::text' if sql_operator.endswith('like') else ''
if left in model._columns:
format = need_wildcard and '%s' or model._columns[left]._symbol_set[0]
if self.has_unaccent and sql_operator in ('ilike', 'not ilike'):
query = '(unaccent(%s."%s") %s unaccent(%s))' % (table_alias, left, sql_operator, format)
else:
query = '(%s."%s" %s %s)' % (table_alias, left, sql_operator, format)
unaccent = self._unaccent if sql_operator.endswith('like') else lambda x: x
column = '%s.%s' % (table_alias, _quote(left))
query = '(%s%s %s %s)' % (unaccent(column), cast, sql_operator, unaccent(format))
elif left in MAGIC_COLUMNS:
query = "(%s.\"%s\" %s %%s)" % (table_alias, left, sql_operator)
query = "(%s.\"%s\"%s %s %%s)" % (table_alias, left, cast, sql_operator)
params = right
else: # Must not happen
raise ValueError("Invalid field %r in domain term %r" % (left, leaf))

View File

@ -648,7 +648,10 @@ class one2many(_column):
else:
cr.execute('update '+_table+' set '+self._fields_id+'=null where id=%s', (act[1],))
elif act[0] == 4:
cr.execute("select 1 from {0} where id=%s and {1}=%s".format(_table, self._fields_id), (act[1], id))
# table of the field (parent_model in case of inherit)
field_model = self._fields_id in obj.pool[self._obj]._columns and self._obj or obj.pool[self._obj]._all_columns[self._fields_id].parent_model
field_table = obj.pool[field_model]._table
cr.execute("select 1 from {0} where id=%s and {1}=%s".format(field_table, self._fields_id), (act[1], id))
if not cr.fetchone():
# Must use write() to recompute parent_store structure if needed and check access rules
obj.write(cr, user, [act[1]], {self._fields_id:id}, context=context or {})

View File

@ -55,7 +55,7 @@ import traceback
import types
import babel.dates
import dateutil.parser
import dateutil.relativedelta
import psycopg2
from lxml import etree
@ -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
@ -469,7 +469,12 @@ class browse_record(object):
else:
new_data[field_name] = browse_null()
elif field_column._type in ('one2many', 'many2many') and len(result_line[field_name]):
new_data[field_name] = self._list_class([browse_record(self._cr, self._uid, id, self._table.pool[field_column._obj], self._cache, context=self._context, list_class=self._list_class, fields_process=self._fields_process) for id in result_line[field_name]], self._context)
new_data[field_name] = self._list_class(
(browse_record(self._cr, self._uid, id, self._table.pool.get(field_column._obj),
self._cache, context=self._context, list_class=self._list_class,
fields_process=self._fields_process)
for id in result_line[field_name]),
context=self._context)
elif field_column._type == 'reference':
if result_line[field_name]:
if isinstance(result_line[field_name], browse_record):
@ -1911,7 +1916,7 @@ class BaseModel(object):
return result
def _view_look_dom_arch(self, cr, uid, node, view_id, context=None):
return self['ir.ui.view'].postprocess_and_fields(
return self.pool['ir.ui.view'].postprocess_and_fields(
cr, uid, self._name, node, view_id, context=context)
def search_count(self, cr, user, args, context=None):
@ -2123,7 +2128,7 @@ class BaseModel(object):
pass
def _read_group_fill_results(self, cr, uid, domain, groupby, groupby_list, aggregated_fields,
def _read_group_fill_results(self, cr, uid, domain, groupby, remaining_groupbys, aggregated_fields,
read_group_result, read_group_order=None, context=None):
"""Helper method for filling in empty groups for all possible values of
the field being grouped by"""
@ -2143,8 +2148,8 @@ class BaseModel(object):
result_template = dict.fromkeys(aggregated_fields, False)
result_template[groupby + '_count'] = 0
if groupby_list and len(groupby_list) > 1:
result_template['__context'] = {'group_by': groupby_list[1:]}
if remaining_groupbys:
result_template['__context'] = {'group_by': remaining_groupbys}
# Merge the left_side (current results as dicts) with the right_side (all
# possible values as m2o pairs). Both lists are supposed to be using the
@ -2193,35 +2198,39 @@ class BaseModel(object):
r['__fold'] = folded.get(r[groupby] and r[groupby][0], False)
return result
def _read_group_prepare(self, orderby, aggregated_fields, groupby, qualified_groupby_field, query, groupby_type=None):
def _read_group_prepare(self, orderby, aggregated_fields, annotated_groupbys, query):
"""
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 qualified_groupby_field: the fully qualified SQL name for the grouped field
:param annotated_groupbys: list of dictionaries returned by _read_group_process_groupby
These dictionaries contains the qualified name of each groupby
(fully qualified SQL name for the corresponding field),
and the (non raw) field name.
:param osv.Query query: the query under construction
:param groupby_type: the type of the grouped field
:return: (groupby_terms, orderby_terms)
"""
orderby_terms = []
groupby_terms = [qualified_groupby_field] if groupby else []
groupby_terms = [gb['qualified_field'] for gb in annotated_groupbys]
groupby_fields = [gb['groupby'] for gb in annotated_groupbys]
if not orderby:
return groupby_terms, orderby_terms
return groupby_terms, orderby_terms
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':
if order_field in groupby_fields:
if self._all_columns[order_field.split(':')[0]].column._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)
order = '"%s" %s' % (order_field, '' if len(order_split) == 1 else order_split[1])
orderby_terms.append(order)
elif order_field in aggregated_fields:
orderby_terms.append(order_part)
else:
@ -2230,7 +2239,100 @@ class BaseModel(object):
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):
def _read_group_process_groupby(self, gb, query, context):
"""
Helper method to collect important information about groupbys: raw
field name, type, time informations, qualified name, ...
"""
split = gb.split(':')
field_type = self._all_columns[split[0]].column._type
gb_function = split[1] if len(split) == 2 else None
temporal = field_type in ('date', 'datetime')
tz_convert = field_type == 'datetime' and context.get('tz') in pytz.all_timezones
qualified_field = self._inherits_join_calc(split[0], query)
if temporal:
display_formats = {
'day': 'dd MMM YYYY',
'week': "'W'w YYYY",
'month': 'MMMM YYYY',
'quarter': 'QQQ YYYY',
'year': 'YYYY'
}
time_intervals = {
'day': dateutil.relativedelta.relativedelta(days=1),
'week': datetime.timedelta(days=7),
'month': dateutil.relativedelta.relativedelta(months=1),
'quarter': dateutil.relativedelta.relativedelta(months=3),
'year': dateutil.relativedelta.relativedelta(years=1)
}
if tz_convert:
qualified_field = "timezone('%s', timezone('UTC',%s))" % (context.get('tz', 'UTC'), qualified_field)
qualified_field = "date_trunc('%s', %s)" % (gb_function or 'month', qualified_field)
if field_type == 'boolean':
qualified_field = "coalesce(%s,false)" % qualified_field
return {
'field': split[0],
'groupby': gb,
'type': field_type,
'display_format': display_formats[gb_function or 'month'] if temporal else None,
'interval': time_intervals[gb_function or 'month'] if temporal else None,
'tz_convert': tz_convert,
'qualified_field': qualified_field
}
def _read_group_prepare_data(self, key, value, groupby_dict, context):
"""
Helper method to sanitize the data received by read_group. The None
values are converted to False, and the date/datetime are formatted,
and corrected according to the timezones.
"""
value = False if value is None else value
gb = groupby_dict.get(key)
if gb and gb['type'] in ('date', 'datetime') and value:
if isinstance(value, basestring):
dt_format = DEFAULT_SERVER_DATETIME_FORMAT if gb['type'] == 'datetime' else DEFAULT_SERVER_DATE_FORMAT
value = datetime.datetime.strptime(value, dt_format)
if gb['tz_convert']:
value = pytz.timezone(context['tz']).localize(value)
return value
def _read_group_get_domain(self, groupby, value):
"""
Helper method to construct the domain corresponding to a groupby and
a given value. This is mostly relevant for date/datetime.
"""
if groupby['type'] in ('date', 'datetime') and value:
dt_format = DEFAULT_SERVER_DATETIME_FORMAT if groupby['type'] == 'datetime' else DEFAULT_SERVER_DATE_FORMAT
domain_dt_begin = value
domain_dt_end = value + groupby['interval']
if groupby['tz_convert']:
domain_dt_begin = domain_dt_begin.astimezone(pytz.utc)
domain_dt_end = domain_dt_end.astimezone(pytz.utc)
return [(groupby['field'], '>=', domain_dt_begin.strftime(dt_format)),
(groupby['field'], '<', domain_dt_end.strftime(dt_format))]
if groupby['type'] == 'many2one' and value:
value = value[0]
return [(groupby['field'], '=', value)]
def _read_group_format_result(self, data, annotated_groupbys, groupby, groupby_dict, domain, context):
"""
Helper method to format the data contained in the dictianary data by
adding the domain corresponding to its values, the groupbys in the
context and by properly formatting the date/datetime values.
"""
domain_group = [dom for gb in annotated_groupbys for dom in self._read_group_get_domain(gb, data[gb['groupby']])]
for k,v in data.iteritems():
gb = groupby_dict.get(k)
if gb and gb['type'] in ('date', 'datetime') and v:
data[k] = babel.dates.format_date(v, format=gb['display_format'], locale=context.get('lang', 'en_US'))
data['__domain'] = domain_group + domain
if len(groupby) - len(annotated_groupbys) >= 1:
data['__context'] = { 'group_by': groupby[len(annotated_groupbys):]}
del data['id']
return data
def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context={}, orderby=False, lazy=True):
"""
Get the list of records in list view grouped by the given ``groupby`` fields
@ -2250,6 +2352,9 @@ class BaseModel(object):
overriding the natural sort ordering of the
groups, see also :py:meth:`~osv.osv.osv.search`
(supported only for many2one fields currently)
:param bool lazy: if true, the results are only grouped by the first groupby and the
remaining groupbys are put in the __context key. If false, all the groupbys are
done in one call.
:return: list of dictionaries(one dictionary for each record) containing:
* the values of fields grouped by the fields in ``groupby`` argument
@ -2258,100 +2363,54 @@ class BaseModel(object):
:rtype: [{'field_name_1': value, ...]
:raise AccessError: * if user has no read rights on the requested object
* if user tries to bypass access rules for read on the requested object
"""
context = context or {}
self.check_access_rights(cr, uid, 'read')
if not fields:
fields = self._columns.keys()
query = self._where_calc(cr, uid, domain, context=context)
fields = fields or self._columns.keys()
groupby = [groupby] if isinstance(groupby, basestring) else groupby
groupby_list = groupby[:1] if lazy else groupby
annotated_groupbys = [self._read_group_process_groupby(gb, query, context)
for gb in groupby_list]
groupby_fields = [g['field'] for g in annotated_groupbys]
order = orderby or ','.join([g for g in groupby_list])
groupby_dict = {gb['groupby']: gb for gb in annotated_groupbys}
query = self._where_calc(cr, uid, domain, context=context)
self._apply_ir_rules(cr, uid, query, 'read', context=context)
# Take care of adding join(s) if groupby is an '_inherits'ed field
groupby_list = groupby
qualified_groupby_field = groupby
if groupby:
if isinstance(groupby, list):
groupby = groupby[0]
splitted_groupby = groupby.split(':')
if len(splitted_groupby) == 2:
groupby = splitted_groupby[0]
groupby_function = splitted_groupby[1]
else:
groupby_function = False
qualified_groupby_field = self._inherits_join_calc(groupby, query)
if groupby:
assert not groupby or groupby in fields, "Fields in 'groupby' must appear in the list of fields to read (perhaps it's missing in the list view?)"
groupby_def = self._columns.get(groupby) or (self._inherit_fields.get(groupby) and self._inherit_fields.get(groupby)[2])
for gb in groupby_fields:
assert gb in fields, "Fields in 'groupby' must appear in the list of fields to read (perhaps it's missing in the list view?)"
groupby_def = self._columns.get(gb) or (self._inherit_fields.get(gb) and self._inherit_fields.get(gb)[2])
assert groupby_def and groupby_def._classic_write, "Fields in 'groupby' must be regular database-persisted fields (no function or related fields), or function fields with store=True"
# TODO it seems fields_get can be replaced by _all_columns (no need for translation)
fget = self.fields_get(cr, uid, fields)
group_by_params = {}
select_terms = []
groupby_type = None
if groupby:
if fget.get(groupby):
groupby_type = fget[groupby]['type']
if groupby_type in ('date', 'datetime'):
if groupby_function:
interval = groupby_function
else:
interval = 'month'
if interval == 'day':
display_format = 'dd MMM YYYY'
elif interval == 'week':
display_format = "'W'w YYYY"
elif interval == 'month':
display_format = 'MMMM YYYY'
elif interval == 'quarter':
display_format = 'QQQ YYYY'
elif interval == 'year':
display_format = 'YYYY'
if groupby_type == 'datetime' and context.get('tz') in pytz.all_timezones:
# Convert groupby result to user TZ to avoid confusion!
# PostgreSQL is compatible with all pytz timezone names, so we can use them
# directly for conversion, starting with timestamps stored in UTC.
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)
elif groupby_type == 'boolean':
qualified_groupby_field = "coalesce(%s,false)" % qualified_groupby_field
select_terms.append("%s as %s " % (qualified_groupby_field, groupby))
else:
if not (gb in self._all_columns):
# Don't allow arbitrary values, as this would be a SQL injection vector!
raise except_orm(_('Invalid group_by'),
_('Invalid group_by specification: "%s".\nA group_by specification must be a list of valid fields.')%(groupby,))
_('Invalid group_by specification: "%s".\nA group_by specification must be a list of valid fields.')%(gb,))
aggregated_fields = [
f for f in fields
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')
qualified_field = self._inherits_join_calc(f, query)
select_terms.append("%s(%s) AS %s" % (group_operator, qualified_field, f))
if f not in ('id', 'sequence')
if f not in groupby_fields
if self._all_columns[f].column._type in ('integer', 'float')
if getattr(self._all_columns[f].column, '_classic_write')]
order = orderby or groupby or ''
groupby_terms, orderby_terms = self._read_group_prepare(order, aggregated_fields, groupby, qualified_groupby_field, query, groupby_type)
field_formatter = lambda f: (self._all_columns[f].column.group_operator or 'sum', self._inherits_join_calc(f, query), f)
select_terms = ["%s(%s) AS %s" % field_formatter(f) for f in aggregated_fields]
for gb in annotated_groupbys:
select_terms.append('%s as "%s" ' % (gb['qualified_field'], gb['groupby']))
groupby_terms, orderby_terms = self._read_group_prepare(order, aggregated_fields, annotated_groupbys, query)
from_clause, where_clause, where_clause_params = query.get_sql()
if len(groupby_list) < 2 and context.get('group_by_no_leaf'):
count_field = '_'
if lazy and (len(groupby_fields) >= 2 or not context.get('group_by_no_leaf')):
count_field = groupby_fields[0] if len(groupby_fields) >= 1 else '_'
else:
count_field = groupby
count_field = '_'
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
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
@ -2370,71 +2429,29 @@ class BaseModel(object):
'offset': prefix_term('OFFSET', int(offset) if limit else None),
}
cr.execute(query, where_clause_params)
alldata = {}
fetched_data = cr.dictfetchall()
data_ids = []
for r in fetched_data:
for fld, val in r.items():
if val is None: r[fld] = False
alldata[r['id']] = r
data_ids.append(r['id'])
del r['id']
if not groupby_fields:
return {r.pop('id'): r for r in fetched_data}
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):
data_dict = dict((d['id'], d[groupby] ) for d in data)
result = [{'id': i, groupby: data_dict[i]} for i in data_ids]
else:
result = [{'id': i} for i in data_ids]
for d in result:
if groupby:
d['__domain'] = [(groupby, '=', alldata[d['id']][groupby] or False)] + domain
if not isinstance(groupby_list, (str, unicode)):
if groupby or not context.get('group_by_no_leaf', False):
d['__context'] = {'group_by': groupby_list[1:]}
if groupby and groupby in fget:
groupby_type = fget[groupby]['type']
if d[groupby] and groupby_type in ('date', 'datetime'):
groupby_datetime = alldata[d['id']][groupby]
if isinstance(groupby_datetime, basestring):
_default = datetime.datetime(1970, 1, 1) # force starts of month
groupby_datetime = dateutil.parser.parse(groupby_datetime, default=_default)
tz_convert = groupby_type == 'datetime' and context.get('tz') in pytz.all_timezones
if tz_convert:
groupby_datetime = pytz.timezone(context['tz']).localize(groupby_datetime)
d[groupby] = babel.dates.format_date(
groupby_datetime, format=display_format, locale=context.get('lang', 'en_US'))
domain_dt_begin = groupby_datetime
if interval == 'quarter':
domain_dt_end = groupby_datetime + dateutil.relativedelta.relativedelta(months=3)
elif interval == 'month':
domain_dt_end = groupby_datetime + dateutil.relativedelta.relativedelta(months=1)
elif interval == 'week':
domain_dt_end = groupby_datetime + datetime.timedelta(days=7)
elif interval == 'day':
domain_dt_end = groupby_datetime + datetime.timedelta(days=1)
else:
domain_dt_end = groupby_datetime + dateutil.relativedelta.relativedelta(years=1)
if tz_convert:
# the time boundaries were all computed in the apparent TZ of the user,
# so we need to convert them to UTC to have proper server-side values.
domain_dt_begin = domain_dt_begin.astimezone(pytz.utc)
domain_dt_end = domain_dt_end.astimezone(pytz.utc)
dt_format = DEFAULT_SERVER_DATETIME_FORMAT if groupby_type == 'datetime' else DEFAULT_SERVER_DATE_FORMAT
d['__domain'] = [(groupby, '>=', domain_dt_begin.strftime(dt_format)),
(groupby, '<', domain_dt_end.strftime(dt_format))] + domain
del alldata[d['id']][groupby]
d.update(alldata[d['id']])
del d['id']
if groupby and groupby in self._group_by_full:
result = self._read_group_fill_results(cr, uid, domain, groupby, groupby_list,
aggregated_fields, result, read_group_order=order,
context=context)
many2onefields = [gb['field'] for gb in annotated_groupbys if gb['type'] == 'many2one']
if many2onefields:
data_ids = [r['id'] for r in fetched_data]
many2onefields = list(set(many2onefields))
data_dict = {d['id']: d for d in self.read(cr, uid, data_ids, many2onefields, context=context)}
for d in fetched_data:
d.update(data_dict[d['id']])
data = map(lambda r: {k: self._read_group_prepare_data(k,v, groupby_dict, context) for k,v in r.iteritems()}, fetched_data)
result = [self._read_group_format_result(d, annotated_groupbys, groupby, groupby_dict, domain, context) for d in data]
if lazy and groupby_fields[0] in self._group_by_full:
# Right now, read_group only fill results in lazy mode (by default).
# If you need to have the empty groups in 'eager' mode, then the
# method _read_group_fill_results need to be completely reimplemented
# in a sane way
result = self._read_group_fill_results(cr, uid, domain, groupby_fields[0], groupby[len(annotated_groupbys):],
aggregated_fields, result, read_group_order=order,
context=context)
return result
def _inherits_join_add(self, current_model, parent_model_name, query):
@ -4250,7 +4267,7 @@ class BaseModel(object):
if isinstance(select, (int, long)):
return browse_record(cr, uid, select, self, cache, context=context, list_class=self._list_class, fields_process=fields_process)
elif isinstance(select, list):
return self._list_class([browse_record(cr, uid, id, self, cache, context=context, list_class=self._list_class, fields_process=fields_process) for id in select], context=context)
return self._list_class((browse_record(cr, uid, id, self, cache, context=context, list_class=self._list_class, fields_process=fields_process) for id in select), context=context)
else:
return browse_null()

View File

@ -36,7 +36,7 @@ def get_db_and_pool(db_name, force_demo=False, status=None, update_module=False)
assert openerp.conf.deprecation.openerp_pooler
_logger.warning('openerp.pooler.get_db_and_pool() is deprecated.')
registry = RegistryManager.get(db_name, force_demo, status, update_module)
return registry.db, registry
return registry._db, registry
def restart_pool(db_name, force_demo=False, status=None, update_module=False):
@ -44,7 +44,7 @@ def restart_pool(db_name, force_demo=False, status=None, update_module=False):
_logger.warning('openerp.pooler.restart_pool() is deprecated.')
assert openerp.conf.deprecation.openerp_pooler
registry = RegistryManager.new(db_name, force_demo, status, update_module)
return registry.db, registry
return registry._db, registry
def get_db(db_name):
"""Return a database connection. The corresponding registry is initialized."""

View File

@ -88,50 +88,30 @@ def _open_image(filename, path=None):
class NumberedCanvas(canvas.Canvas):
def __init__(self, *args, **kwargs):
canvas.Canvas.__init__(self, *args, **kwargs)
self._codes = []
self._flag=False
self._pageCount=0
self._currentPage =0
self._pageCounter=0
self.pages={}
self._saved_page_states = []
def showPage(self):
self._currentPage +=1
if not self._flag:
self._pageCount += 1
else:
self.pages.update({self._currentPage:self._pageCount})
self._codes.append({'code': self._code, 'stack': self._codeStack})
self._saved_page_states.append(dict(self.__dict__))
self._startPage()
self._flag=False
def pageCount(self):
if self.pages.get(self._pageCounter,False):
self._pageNumber=0
self._pageCounter +=1
key=self._pageCounter
if not self.pages.get(key,False):
while not self.pages.get(key,False):
key += 1
def save(self):
"""add page info to each page (page x of y)"""
for state in self._saved_page_states:
self.__dict__.update(state)
self.draw_page_number()
canvas.Canvas.showPage(self)
canvas.Canvas.save(self)
def draw_page_number(self):
page_count = len(self._saved_page_states)
self.setFont("Helvetica", 8)
self.drawRightString((self._pagesize[0]-30), (self._pagesize[1]-40),
" %(this)i / %(total)i" % {
'this': self._pageNumber+1,
'total': self.pages.get(key,False),
'total': page_count,
}
)
def save(self):
"""add page info to each page (page x of y)"""
# reset page counter
self._pageNumber = 0
for code in self._codes:
self._code = code['code']
self._codeStack = code['stack']
self.pageCount()
canvas.Canvas.showPage(self)
# self.restoreState()
self._doc.SaveToFile(self._filename, self)
class PageCount(platypus.Flowable):
def __init__(self, story_count=0):
@ -303,6 +283,9 @@ class _rml_doc(object):
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
if mode:
mode = mode.lower()
if fontname not in pdfmetrics._fonts:
pdfmetrics.registerFont(TTFont(fontname, filename))
if mode == 'all':
@ -310,14 +293,14 @@ class _rml_doc(object):
addMapping(face, 0, 1, fontname) #italic
addMapping(face, 1, 0, fontname) #bold
addMapping(face, 1, 1, fontname) #italic and bold
elif (mode== 'normal') or (mode == 'regular') or (mode == 'book'):
addMapping(face, 0, 0, fontname) #normal
elif mode == 'italic':
elif mode in ['italic', 'oblique']:
addMapping(face, 0, 1, fontname) #italic
elif mode == 'bold':
addMapping(face, 1, 0, fontname) #bold
elif mode == 'bolditalic':
elif mode in ('bolditalic', 'bold italic','boldoblique', 'bold oblique'):
addMapping(face, 1, 1, fontname) #italic and bold
else:
addMapping(face, 0, 0, fontname) #normal
def _textual_image(self, node):
rc = ''

View File

@ -161,21 +161,10 @@ def execute_cr(cr, uid, obj, method, *args, **kw):
def execute_kw(db, uid, obj, method, args, kw=None):
return execute(db, uid, obj, method, *args, **kw or {})
@contextmanager
def closing_cr_and_commit(cr):
try:
yield cr
cr.commit()
except Exception:
cr.rollback()
raise
finally:
cr.close()
@check
def execute(db, uid, obj, method, *args, **kw):
threading.currentThread().dbname = db
with closing_cr_and_commit(openerp.registry(db).db.cursor()) as cr:
with openerp.registry(db).cursor() as cr:
if method.startswith('_'):
raise except_orm('Access Denied', 'Private methods (such as %s) cannot be called remotely.' % (method,))
res = execute_cr(cr, uid, obj, method, *args, **kw)
@ -190,7 +179,7 @@ def exec_workflow_cr(cr, uid, obj, signal, *args):
@check
def exec_workflow(db, uid, obj, signal, *args):
with closing_cr_and_commit(openerp.registry(db).db.cursor()) as cr:
with openerp.registry(db).cursor() as cr:
return exec_workflow_cr(cr, uid, obj, signal, *args)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -49,7 +49,7 @@ def exp_render_report(db, uid, object, ids, datas=None, context=None):
self_reports[id] = {'uid': uid, 'result': False, 'state': False, 'exception': None}
cr = openerp.registry(db).db.cursor()
cr = openerp.registry(db).cursor()
try:
result, format = openerp.report.render_report(cr, uid, ids, object, datas, context)
if not result:
@ -87,7 +87,7 @@ def exp_report(db, uid, object, ids, datas=None, context=None):
self_reports[id] = {'uid': uid, 'result': False, 'state': False, 'exception': None}
def go(id, uid, ids, datas, context):
cr = openerp.registry(db).db.cursor()
cr = openerp.registry(db).cursor()
try:
result, format = openerp.report.render_report(cr, uid, ids, object, datas, context)
if not result:

View File

@ -62,6 +62,14 @@ class BaseWSGIServerNoBind(werkzeug.serving.BaseWSGIServer):
# dont listen as we use PreforkServer#socket
pass
class RequestHandler(werkzeug.serving.WSGIRequestHandler):
def setup(self):
# flag the current thread as handling a http request
super(RequestHandler, self).setup()
me = threading.currentThread()
me.name = 'openerp.service.http.request.%s' % (me.ident,)
# _reexec() should set LISTEN_* to avoid connection refused during reload time. It
# should also work with systemd socket activation. This is currently untested
# and not yet used.
@ -71,6 +79,10 @@ class ThreadedWSGIServerReloadable(werkzeug.serving.ThreadedWSGIServer):
given by the environement, this is used by autoreload to keep the listen
socket open when a reload happens.
"""
def __init__(self, host, port, app):
super(ThreadedWSGIServerReloadable, self).__init__(host, port, app,
handler=RequestHandler)
def server_bind(self):
envfd = os.environ.get('LISTEN_FDS')
if envfd and os.environ.get('LISTEN_PID') == str(os.getpid()):
@ -440,7 +452,7 @@ class PreforkServer(CommonServer):
sys.exit(0)
def long_polling_spawn(self):
nargs = stripped_sys_argv('--pidfile', '--workers')
nargs = stripped_sys_argv()
cmd = nargs[0]
cmd = os.path.join(os.path.dirname(cmd), "openerp-gevent")
nargs[0] = cmd
@ -834,8 +846,13 @@ def _reexec(updated_modules=None):
def load_test_file_yml(registry, test_file):
with registry.cursor() as cr:
openerp.tools.convert_yaml_import(cr, 'base', file(test_file), 'test', {}, 'test', True)
cr.rollback()
openerp.tools.convert_yaml_import(cr, 'base', file(test_file), 'test', {}, 'init')
if config['test_commit']:
_logger.info('test %s has been commited', test_file)
cr.commit()
else:
_logger.info('test %s has been rollbacked', test_file)
cr.rollback()
def load_test_file_py(registry, test_file):
# Locate python module based on its filename and run the tests
@ -887,10 +904,10 @@ def start(preload=None, stop=False):
"""
global server
load_server_wide_modules()
if config['workers']:
server = PreforkServer(openerp.service.wsgi_server.application)
elif openerp.evented:
if openerp.evented:
server = GeventServer(openerp.service.wsgi_server.application)
elif config['workers']:
server = PreforkServer(openerp.service.wsgi_server.application)
else:
server = ThreadedServer(openerp.service.wsgi_server.application)

View File

@ -167,7 +167,7 @@ class Cursor(object):
self.sql_log_count = 0
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.__pool = pool
self.dbname = dbname
# Whether to enable snapshot isolation level for this cursor.
@ -313,7 +313,7 @@ class Cursor(object):
chosen_template = tools.config['db_template']
templates_list = tuple(set(['template0', 'template1', 'postgres', chosen_template]))
keep_in_pool = self.dbname not in templates_list
self._pool.give_back(self._cnx, keep_in_pool=keep_in_pool)
self.__pool.give_back(self._cnx, keep_in_pool=keep_in_pool)
@check
def autocommit(self, on):
@ -347,6 +347,23 @@ class Cursor(object):
"""
return self._cnx.rollback()
def __enter__(self):
""" Using the cursor as a contextmanager automatically commits and
closes it::
with cr:
cr.execute(...)
# cr is committed if no failure occurred
# cr is closed in any case
"""
return self
def __exit__(self, exc_type, exc_value, traceback):
if exc_type is None:
self.commit()
self.close()
@contextmanager
@check
def savepoint(self):
@ -364,6 +381,42 @@ class Cursor(object):
def __getattr__(self, name):
return getattr(self._obj, name)
class TestCursor(Cursor):
""" A cursor to be used for tests. It keeps the transaction open across
several requests, and simulates committing, rolling back, and closing.
"""
def __init__(self, *args, **kwargs):
super(TestCursor, self).__init__(*args, **kwargs)
# in order to simulate commit and rollback, the cursor maintains a
# savepoint at its last commit
self.execute("SAVEPOINT test_cursor")
# we use a lock to serialize concurrent requests
self._lock = threading.RLock()
def acquire(self):
self._lock.acquire()
def release(self):
self._lock.release()
def close(self, force=False):
if force:
super(TestCursor, self).close()
elif not self._closed:
self.rollback() # for stuff that has not been committed
self.release()
def autocommit(self, on):
_logger.debug("TestCursor.autocommit(%r) does nothing", on)
def commit(self):
self.execute("RELEASE SAVEPOINT test_cursor")
self.execute("SAVEPOINT test_cursor")
def rollback(self):
self.execute("ROLLBACK TO SAVEPOINT test_cursor")
self.execute("SAVEPOINT test_cursor")
class PsycoConnection(psycopg2.extensions.connection):
pass
@ -484,12 +537,17 @@ class Connection(object):
def __init__(self, pool, dbname):
self.dbname = dbname
self._pool = pool
self.__pool = pool
def cursor(self, serialized=True):
cursor_type = serialized and 'serialized ' or ''
_logger.debug('create %scursor to %r', cursor_type, self.dbname)
return Cursor(self._pool, self.dbname, serialized=serialized)
return Cursor(self.__pool, self.dbname, serialized=serialized)
def test_cursor(self, serialized=True):
cursor_type = serialized and 'serialized ' or ''
_logger.debug('create test %scursor to %r', cursor_type, self.dbname)
return TestCursor(self.__pool, self.dbname, serialized=serialized)
# serialized_cursor is deprecated - cursors are serialized by default
serialized_cursor = cursor

View File

@ -20,6 +20,7 @@ from datetime import datetime, timedelta
import werkzeug
import openerp
from openerp.modules.registry import RegistryManager
_logger = logging.getLogger(__name__)
@ -37,25 +38,6 @@ if not DB and hasattr(threading.current_thread(), 'dbname'):
# Useless constant, tests are aware of the content of demo data
ADMIN_USER_ID = openerp.SUPERUSER_ID
# Magic session_id, unfortunately we have to serialize access to the cursors to
# serialize requests. We first tried to duplicate the database for each tests
# but this proved too slow. Any idea to improve this is welcome.
HTTP_SESSION = {}
def acquire_test_cursor(session_id):
if openerp.tools.config['test_enable']:
cr = HTTP_SESSION.get(session_id)
if cr:
cr._test_lock.acquire()
return cr
def release_test_cursor(cr):
if openerp.tools.config['test_enable']:
if hasattr(cr, '_test_lock'):
cr._test_lock.release()
return True
return False
def at_install(flag):
""" Sets the at-install state of a test, the flag is a boolean specifying
whether the test should (``True``) or should not (``False``) run during
@ -67,6 +49,7 @@ def at_install(flag):
obj.at_install = flag
return obj
return decorator
def post_install(flag):
""" Sets the post-install state of a test. The flag is a boolean
specifying whether the test should or should not run after a set of
@ -83,18 +66,13 @@ class BaseCase(unittest2.TestCase):
"""
Subclass of TestCase for common OpenERP-specific code.
This class is abstract and expects self.cr and self.uid to be initialized by subclasses.
This class is abstract and expects self.registry, self.cr and self.uid to be
initialized by subclasses.
"""
@classmethod
def cursor(self):
return openerp.modules.registry.RegistryManager.get(DB).db.cursor()
return self.registry.cursor()
@classmethod
def registry(self, model):
return openerp.modules.registry.RegistryManager.get(DB)[model]
@classmethod
def ref(self, xid):
""" Returns database ID corresponding to a given identifier.
@ -106,7 +84,6 @@ class BaseCase(unittest2.TestCase):
_, id = self.registry('ir.model.data').get_object_reference(self.cr, self.uid, module, xid)
return id
@classmethod
def browse_ref(self, xid):
""" Returns a browsable record for the given identifier.
@ -125,10 +102,9 @@ class TransactionCase(BaseCase):
"""
def setUp(self):
# Store cr and uid in class variables, to allow ref() and browse_ref to be BaseCase @classmethods
# and still access them
TransactionCase.cr = self.cursor()
TransactionCase.uid = openerp.SUPERUSER_ID
self.registry = RegistryManager.get(DB)
self.cr = self.cursor()
self.uid = openerp.SUPERUSER_ID
def tearDown(self):
self.cr.rollback()
@ -143,7 +119,8 @@ class SingleTransactionCase(BaseCase):
@classmethod
def setUpClass(cls):
cls.cr = cls.cursor()
cls.registry = RegistryManager.get(DB)
cls.cr = cls.registry.cursor()
cls.uid = openerp.SUPERUSER_ID
@classmethod
@ -166,16 +143,15 @@ class HttpCase(TransactionCase):
def setUp(self):
super(HttpCase, self).setUp()
self.registry.enter_test_mode()
# setup a magic session_id that will be rollbacked
self.session = openerp.http.root.session_store.new()
self.session_id = self.session.sid
self.session.db = DB
openerp.http.root.session_store.save(self.session)
self.cr._test_lock = threading.RLock()
HTTP_SESSION[self.session_id] = self.cr
def tearDown(self):
del HTTP_SESSION[self.session_id]
self.registry.leave_test_mode()
super(HttpCase, self).tearDown()
def url_open(self, url, data=None, timeout=10):
@ -253,8 +229,18 @@ class HttpCase(TransactionCase):
# kill phantomjs if phantom.exit() wasn't called in the test
if phantom.poll() is None:
phantom.terminate()
self._wait_remaining_requests()
_logger.info("phantom_run execution finished")
def _wait_remaining_requests(self):
for thread in threading.enumerate():
if thread.name.startswith('openerp.service.http.request.'):
while thread.isAlive():
# Need a busyloop here as thread.join() masks signals
# and would prevent the forced shutdown.
thread.join(0.05)
time.sleep(0.05)
def phantom_jsfile(self, jsfile, timeout=60, **kw):
options = {
'timeout' : timeout,

Some files were not shown because too many files have changed in this diff Show More