[MERGE] merge with latest trunk
bzr revid: rma@tinyerp.com-20140411061647-m6iupt9567igzs4x
This commit is contained in:
commit
cbc86bb4ee
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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')")
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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 {})
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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 = ''
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue