[MERGE] merge from trunk
bzr revid: ged@openerp.com-20140319101615-9a9vb0uj0sg58nrd
This commit is contained in:
commit
31645ea1cf
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 05:58+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:41+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 05:58+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:41+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 05:58+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:42+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -13,8 +13,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 05:58+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:42+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -13,8 +13,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 05:59+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:43+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 05:59+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:43+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -13,8 +13,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 05:59+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:43+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -13,8 +13,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 05:59+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:43+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -13,8 +13,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:00+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:44+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
"X-Poedit-Language: Czech\n"
|
||||
|
||||
#. module: base
|
||||
|
@ -64,7 +64,7 @@ msgstr "Zobrazení architektury"
|
|||
#. module: base
|
||||
#: model:ir.module.module,summary:base.module_sale_stock
|
||||
msgid "Quotation, Sale Orders, Delivery & Invoicing Control"
|
||||
msgstr "Nabídky, zakázky, řízení dopravy a fakturace"
|
||||
msgstr "Nabídky, zakázky, řízení dodávek a fakturace"
|
||||
|
||||
#. module: base
|
||||
#: selection:ir.sequence,implementation:0
|
||||
|
@ -87,8 +87,7 @@ msgid ""
|
|||
"Helps you manage your projects and tasks by tracking them, generating "
|
||||
"plannings, etc..."
|
||||
msgstr ""
|
||||
"Pomůže vám spravovat projekty a úkoly jejich sledováním, generováním plánů, "
|
||||
"apod."
|
||||
"Pomůže vám řídit projekty a úkoly jejich sledováním, generováním plánů, aj."
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,summary:base.module_point_of_sale
|
||||
|
@ -158,7 +157,7 @@ msgstr ""
|
|||
#. module: base
|
||||
#: help:res.partner,employee:0
|
||||
msgid "Check this box if this contact is an Employee."
|
||||
msgstr "Zaškrtněte toto pole, pokud je kontakt zaměstnanec."
|
||||
msgstr "Zaškrtněte toto pole, pokud je kontakt zaměstnancem."
|
||||
|
||||
#. module: base
|
||||
#: help:ir.model.fields,domain:0
|
||||
|
@ -11056,7 +11055,7 @@ msgstr "Úkoly"
|
|||
#. module: base
|
||||
#: model:ir.module.module,shortdesc:base.module_product_visible_discount
|
||||
msgid "Prices Visible Discounts"
|
||||
msgstr ""
|
||||
msgstr "Zobrazení slevy u ceny"
|
||||
|
||||
#. module: base
|
||||
#: field:ir.attachment,datas:0
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:00+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:44+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:02+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:46+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -12,8 +12,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:03+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:46+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
"X-Poedit-Country: GREECE\n"
|
||||
"X-Poedit-Language: Greek\n"
|
||||
"X-Poedit-SourceCharset: utf-8\n"
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:13+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:55+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -13,8 +13,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:10+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:53+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:13+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:55+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:14+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:56+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -13,8 +13,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:14+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:56+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:15+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:57+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
"Language: \n"
|
||||
|
||||
#. module: base
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:13+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:56+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -13,8 +13,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:16+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:57+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:15+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:57+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:16+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:58+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:13+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:55+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -13,8 +13,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:01+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:44+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 05:59+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:42+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -9,8 +9,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:07+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:50+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
"X-Poedit-Country: IRAN, ISLAMIC REPUBLIC OF\n"
|
||||
"X-Poedit-Language: Persian\n"
|
||||
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:16+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:58+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -13,8 +13,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:01+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:45+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -13,8 +13,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:02+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:45+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:14+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:56+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:03+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:46+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:03+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:46+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:03+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:47+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:04+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:47+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -13,8 +13,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:09+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:52+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
"Language: hr\n"
|
||||
|
||||
#. module: base
|
||||
|
|
|
@ -13,8 +13,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:04+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:47+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -9,8 +9,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 05:58+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:42+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:05+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:48+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
@ -211,6 +211,15 @@ msgid ""
|
|||
"revenue\n"
|
||||
"reports."
|
||||
msgstr ""
|
||||
"\n"
|
||||
"Bentuk tagihan anda dari entri Expenses, Timesheet.\n"
|
||||
"========================================================\n"
|
||||
"\n"
|
||||
"Modul untuk menerbitkan tagihan berbasis biaya (sdm, biaya, dsb).\n"
|
||||
"\n"
|
||||
"Anda dapat mendefinisikan daftar harga pada akun analitik, membuat laporan "
|
||||
"anggaran \n"
|
||||
"penghasilan."
|
||||
|
||||
#. module: base
|
||||
#: code:addons/base/ir/ir_sequence.py:134
|
||||
|
@ -299,6 +308,12 @@ msgid ""
|
|||
"\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
"Lokalisasi skema akunting dan pajak Chili.\n"
|
||||
"==============================================\n"
|
||||
"Plan contable chileno e impuestos de acuerdo a disposiciones vigentes\n"
|
||||
"\n"
|
||||
" "
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,shortdesc:base.module_sale
|
||||
|
@ -398,6 +413,15 @@ msgid ""
|
|||
"invoices from picking, OpenERP is able to add and compute the shipping "
|
||||
"line.\n"
|
||||
msgstr ""
|
||||
"\n"
|
||||
"Mengizinkan anda untuk menambahkan metode deliveri pada order penjualan dan "
|
||||
"pengambilan.\n"
|
||||
"==============================================================\n"
|
||||
"\n"
|
||||
"Anda dapat menentukan carrier sendiri dan metode antar untuk harga tersebut. "
|
||||
"Saat membuat\n"
|
||||
"tagihan dari pengantaran, OpenERP dapat menambah dan menghitung biaya "
|
||||
"pengiriman.\n"
|
||||
|
||||
#. module: base
|
||||
#: code:addons/base/ir/ir_filters.py:80
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:04+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:47+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -13,8 +13,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:05+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:48+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:05+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:48+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
@ -411,7 +411,7 @@ msgstr "与信限度"
|
|||
#: field:ir.model.data,date_update:0
|
||||
#: field:ir.model.relation,date_update:0
|
||||
msgid "Update Date"
|
||||
msgstr "日付の更新"
|
||||
msgstr "更新日"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,shortdesc:base.module_base_action_rule
|
||||
|
@ -4582,7 +4582,7 @@ msgstr "スペイン語(ホンジュラス)/ Español (HN)"
|
|||
#. module: base
|
||||
#: view:ir.sequence.type:0
|
||||
msgid "Sequence Type"
|
||||
msgstr "順序タイプ"
|
||||
msgstr "付番タイプ"
|
||||
|
||||
#. module: base
|
||||
#: view:base.language.export:0
|
||||
|
@ -5141,7 +5141,7 @@ msgstr "銀行口座タイプ"
|
|||
#. module: base
|
||||
#: help:ir.sequence,suffix:0
|
||||
msgid "Suffix value of the record for the sequence"
|
||||
msgstr "順序のためのレコードのサフィックス値"
|
||||
msgstr "この付番定義でレコードに適用するサフィックス値"
|
||||
|
||||
#. module: base
|
||||
#: help:ir.mail_server,smtp_user:0
|
||||
|
@ -6140,7 +6140,7 @@ msgstr "ショートカット"
|
|||
#. module: base
|
||||
#: field:ir.model.data,date_init:0
|
||||
msgid "Init Date"
|
||||
msgstr "整数データ"
|
||||
msgstr "作成日"
|
||||
|
||||
#. module: base
|
||||
#: selection:base.language.install,lang:0
|
||||
|
@ -7630,7 +7630,7 @@ msgstr ""
|
|||
#. module: base
|
||||
#: help:ir.sequence,number_next:0
|
||||
msgid "Next number of this sequence"
|
||||
msgstr "この順序の次の番号"
|
||||
msgstr "この付番定義の次の番号"
|
||||
|
||||
#. module: base
|
||||
#: view:res.partner:0
|
||||
|
@ -10135,7 +10135,7 @@ msgstr "発注分析計画"
|
|||
#: model:ir.actions.act_window,name:base.ir_sequence_type
|
||||
#: model:ir.ui.menu,name:base.menu_ir_sequence_type
|
||||
msgid "Sequence Codes"
|
||||
msgstr "順序コード"
|
||||
msgstr "付番コード"
|
||||
|
||||
#. module: base
|
||||
#: selection:base.language.install,lang:0
|
||||
|
@ -10790,7 +10790,7 @@ msgstr "国の状態"
|
|||
#. module: base
|
||||
#: model:ir.ui.menu,name:base.next_id_5
|
||||
msgid "Sequences & Identifiers"
|
||||
msgstr "順序と識別子"
|
||||
msgstr "付番定義とID"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_l10n_th
|
||||
|
@ -10925,7 +10925,7 @@ msgstr "連絡先が会社の場合はチェックしてください。チェッ
|
|||
#. module: base
|
||||
#: view:ir.sequence.type:0
|
||||
msgid "Sequences Type"
|
||||
msgstr "順序タイプ"
|
||||
msgstr "付番タイプ"
|
||||
|
||||
#. module: base
|
||||
#: view:res.partner:0
|
||||
|
@ -11282,7 +11282,7 @@ msgstr ""
|
|||
#: view:ir.sequence:0
|
||||
#: model:ir.ui.menu,name:base.menu_ir_sequence_form
|
||||
msgid "Sequences"
|
||||
msgstr "順序"
|
||||
msgstr "付番"
|
||||
|
||||
#. module: base
|
||||
#: help:res.lang,code:0
|
||||
|
@ -11724,7 +11724,7 @@ msgstr "月:%(month)s"
|
|||
#: field:multi_company.default,sequence:0
|
||||
#: field:res.partner.bank,sequence:0
|
||||
msgid "Sequence"
|
||||
msgstr "順序"
|
||||
msgstr "付番"
|
||||
|
||||
#. module: base
|
||||
#: model:res.country,name:base.tn
|
||||
|
@ -12190,7 +12190,7 @@ msgstr "ハード島とマクドナルド諸島"
|
|||
msgid ""
|
||||
"External Key/Identifier that can be used for data integration with third-"
|
||||
"party systems"
|
||||
msgstr "サードパーティのシステムとのデータ統合のために利用する外部キー / 識別子"
|
||||
msgstr "サードパーティシステムとのデータ統合のために利用する外部キー / ID"
|
||||
|
||||
#. module: base
|
||||
#: field:ir.actions.act_window,view_id:0
|
||||
|
@ -12519,7 +12519,7 @@ msgstr "CRMポータル"
|
|||
#. module: base
|
||||
#: model:ir.ui.menu,name:base.next_id_4
|
||||
msgid "Low Level Objects"
|
||||
msgstr "低レベルのオブジェクト"
|
||||
msgstr "低レベルオブジェクト"
|
||||
|
||||
#. module: base
|
||||
#: help:ir.values,model:0
|
||||
|
@ -12596,7 +12596,7 @@ msgstr ""
|
|||
#: view:ir.model.data:0
|
||||
#: model:ir.ui.menu,name:base.ir_model_data_menu
|
||||
msgid "External Identifiers"
|
||||
msgstr "外部識別子"
|
||||
msgstr "外部ID"
|
||||
|
||||
#. module: base
|
||||
#: selection:base.language.install,lang:0
|
||||
|
@ -13614,7 +13614,7 @@ msgstr ""
|
|||
#: view:ir.model.data:0
|
||||
#: field:ir.model.data,name:0
|
||||
msgid "External Identifier"
|
||||
msgstr "外部識別子"
|
||||
msgstr "外部ID"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_audittrail
|
||||
|
@ -14048,9 +14048,7 @@ msgid ""
|
|||
"Two sequence object implementations are offered: Standard and 'No gap'. The "
|
||||
"later is slower than the former but forbids any gap in the sequence (while "
|
||||
"they are possible in the former)."
|
||||
msgstr ""
|
||||
"2つのシーケンスオブジェクトの実装が提供されます:標準と\"ギャップなし\"です。後者は前者よりも遅いですが、どんな順序のギャップを禁じます(前者は可能で"
|
||||
"す)。"
|
||||
msgstr "「標準」と「ギャップなし」の2種類の付番定義が可能です。後者の場合、付番ごとにギャップを設けることができません。"
|
||||
|
||||
#. module: base
|
||||
#: model:res.country,name:base.gn
|
||||
|
@ -14249,7 +14247,7 @@ msgstr "かつ"
|
|||
#: help:ir.values,res_id:0
|
||||
msgid ""
|
||||
"Database identifier of the record to which this applies. 0 = for all records"
|
||||
msgstr "これを適用するためのレコードのデータベース識別子。0 = 全てのレコードのため"
|
||||
msgstr ""
|
||||
|
||||
#. module: base
|
||||
#: field:ir.model.fields,relation:0
|
||||
|
@ -14903,7 +14901,7 @@ msgstr "クック諸島"
|
|||
#. module: base
|
||||
#: field:ir.model.data,noupdate:0
|
||||
msgid "Non Updatable"
|
||||
msgstr "更新可能でない"
|
||||
msgstr "更新不可"
|
||||
|
||||
#. module: base
|
||||
#: selection:base.language.install,lang:0
|
||||
|
@ -15311,7 +15309,7 @@ msgstr ""
|
|||
#. module: base
|
||||
#: help:ir.sequence,prefix:0
|
||||
msgid "Prefix value of the record for the sequence"
|
||||
msgstr "順序のためのレコードのプレフィックス値"
|
||||
msgstr "この付番定義でレコードに適用するプレフィックス値"
|
||||
|
||||
#. module: base
|
||||
#: model:res.country,name:base.sc
|
||||
|
@ -15353,7 +15351,7 @@ msgstr ""
|
|||
#. module: base
|
||||
#: field:ir.model.data,complete_name:0
|
||||
msgid "Complete ID"
|
||||
msgstr "完了ID"
|
||||
msgstr "完全ID"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_stock
|
||||
|
@ -15447,7 +15445,7 @@ msgstr "製造プロセスを管理し、それらのプロセスのレポート
|
|||
#. module: base
|
||||
#: help:ir.sequence,number_increment:0
|
||||
msgid "The next number of the sequence will be incremented by this number"
|
||||
msgstr "順序の次の番号はこの数値だけ増加します。"
|
||||
msgstr "付番毎にこの数値分だけ番号が増加します。"
|
||||
|
||||
#. module: base
|
||||
#: selection:workflow.activity,kind:0
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:02+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:45+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:05+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:48+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:06+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:49+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -13,8 +13,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:06+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:49+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:06+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:49+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:07+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:49+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -13,8 +13,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:07+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:50+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:07+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:50+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -13,8 +13,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:00+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:44+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
@ -13429,7 +13429,7 @@ msgstr ""
|
|||
#. module: base
|
||||
#: selection:res.currency,position:0
|
||||
msgid "Before Amount"
|
||||
msgstr "Voormalig bedrag"
|
||||
msgstr "Voor bedrag"
|
||||
|
||||
#. module: base
|
||||
#: field:res.request,act_from:0
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:14+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:56+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -13,8 +13,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:51+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -13,8 +13,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:51+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:12+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:55+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -13,8 +13,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:08+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:51+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -13,8 +13,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:09+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:51+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:09+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:52+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -13,8 +13,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:10+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:52+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -13,8 +13,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 05:58+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:41+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:09+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:52+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:16+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:58+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -13,8 +13,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:10+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:53+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:11+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:53+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -13,8 +13,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:11+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:53+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -13,8 +13,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:11+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:54+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -13,8 +13,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:11+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:54+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:12+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:54+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:12+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:54+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -13,8 +13,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:15+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:57+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -14,8 +14,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:12+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:54+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -13,8 +13,8 @@ msgstr ""
|
|||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-04 06:14+0000\n"
|
||||
"X-Generator: Launchpad (build 16948)\n"
|
||||
"X-Launchpad-Export-Date: 2014-03-12 04:57+0000\n"
|
||||
"X-Generator: Launchpad (build 16963)\n"
|
||||
|
||||
#. module: base
|
||||
#: model:ir.module.module,description:base.module_account_check_writing
|
||||
|
|
|
@ -77,7 +77,9 @@ class ir_http(osv.AbstractModel):
|
|||
# what if error in security.check()
|
||||
# -> res_users.check()
|
||||
# -> res_users.check_credentials()
|
||||
except Exception:
|
||||
except (openerp.exceptions.AccessDenied, openerp.http.SessionExpiredException):
|
||||
# All other exceptions mean undetermined status (e.g. connection pool full),
|
||||
# let them bubble up
|
||||
request.session.logout()
|
||||
getattr(self, "_auth_method_%s" % auth_method)()
|
||||
return auth_method
|
||||
|
|
|
@ -33,7 +33,7 @@ from openerp.osv.orm import Model, browse_null
|
|||
from openerp.tools.safe_eval import safe_eval as eval
|
||||
from openerp.tools import config
|
||||
from openerp.tools.translate import _
|
||||
from openerp.osv.orm import except_orm, browse_record
|
||||
from openerp.osv.orm import except_orm, browse_record, MAGIC_COLUMNS
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -302,6 +302,8 @@ class ir_model_fields(osv.osv):
|
|||
|
||||
def _drop_column(self, cr, uid, ids, context=None):
|
||||
for field in self.browse(cr, uid, ids, context):
|
||||
if field.name in MAGIC_COLUMNS:
|
||||
continue
|
||||
model = self.pool[field.model]
|
||||
cr.execute('select relkind from pg_class where relname=%s', (model._table,))
|
||||
result = cr.fetchone()
|
||||
|
@ -1117,6 +1119,10 @@ class ir_model_data(osv.osv):
|
|||
# Don't remove the LOG_ACCESS_COLUMNS unless _log_access
|
||||
# has been turned off on the model.
|
||||
field = self.pool[model].browse(cr, uid, [res_id], context=context)[0]
|
||||
if not field.exists():
|
||||
_logger.info('Deleting orphan external_ids %s', external_ids)
|
||||
self.unlink(cr, uid, external_ids)
|
||||
continue
|
||||
if field.name in openerp.osv.orm.LOG_ACCESS_COLUMNS and self.pool[field.model]._log_access:
|
||||
continue
|
||||
if field.name == 'id':
|
||||
|
|
|
@ -32,7 +32,7 @@ class QWebException(Exception):
|
|||
class QWebTemplateNotFound(QWebException):
|
||||
pass
|
||||
|
||||
def convert_to_qweb_exception(etype=None, **kw):
|
||||
def raise_qweb_exception(etype=None, **kw):
|
||||
if etype is None:
|
||||
etype = QWebException
|
||||
orig_type, original, tb = sys.exc_info()
|
||||
|
@ -43,7 +43,7 @@ def convert_to_qweb_exception(etype=None, **kw):
|
|||
e.qweb[k] = v
|
||||
# Will use `raise foo from bar` in python 3 and rename cause to __cause__
|
||||
e.qweb['cause'] = original
|
||||
return e
|
||||
raise
|
||||
|
||||
class QWebContext(dict):
|
||||
def __init__(self, cr, uid, data, loader=None, templates=None, context=None):
|
||||
|
@ -166,7 +166,7 @@ class QWeb(orm.AbstractModel):
|
|||
try:
|
||||
xml_doc = qwebcontext.loader(name)
|
||||
except ValueError:
|
||||
raise convert_to_qweb_exception(QWebTemplateNotFound, message="Loader could not find template %r" % name, template=origin_template)
|
||||
raise_qweb_exception(QWebTemplateNotFound, message="Loader could not find template %r" % name, template=origin_template)
|
||||
self.load_document(xml_doc, qwebcontext=qwebcontext)
|
||||
|
||||
if name in qwebcontext.templates:
|
||||
|
@ -179,7 +179,7 @@ class QWeb(orm.AbstractModel):
|
|||
return qwebcontext.safe_eval(expr)
|
||||
except Exception:
|
||||
template = qwebcontext.get('__template__')
|
||||
raise convert_to_qweb_exception(message="Could not evaluate expression %r" % expr, expression=expr, template=template)
|
||||
raise_qweb_exception(message="Could not evaluate expression %r" % expr, expression=expr, template=template)
|
||||
|
||||
def eval_object(self, expr, qwebcontext):
|
||||
return self.eval(expr, qwebcontext)
|
||||
|
@ -207,7 +207,7 @@ class QWeb(orm.AbstractModel):
|
|||
return str(expr % qwebcontext)
|
||||
except Exception:
|
||||
template = qwebcontext.get('__template__')
|
||||
raise convert_to_qweb_exception(message="Format error for expression %r" % expr, expression=expr, template=template)
|
||||
raise_qweb_exception(message="Format error for expression %r" % expr, expression=expr, template=template)
|
||||
|
||||
def eval_bool(self, expr, qwebcontext):
|
||||
return int(bool(self.eval(expr, qwebcontext)))
|
||||
|
@ -292,7 +292,7 @@ class QWeb(orm.AbstractModel):
|
|||
raise
|
||||
except Exception:
|
||||
template = qwebcontext.get('__template__')
|
||||
raise convert_to_qweb_exception(message="Could not render element %r" % element.nodeName, node=element, template=template)
|
||||
raise_qweb_exception(message="Could not render element %r" % element.nodeName, node=element, template=template)
|
||||
name = str(element.nodeName)
|
||||
inner = "".join(g_inner)
|
||||
trim = template_attributes.get("trim", 0)
|
||||
|
@ -488,7 +488,6 @@ class FieldConverter(osv.AbstractModel):
|
|||
A default configuration key is ``widget`` which can override the
|
||||
field's own ``_type``.
|
||||
"""
|
||||
content = None
|
||||
try:
|
||||
content = self.record_to_html(
|
||||
cr, uid, field_name, record,
|
||||
|
@ -503,12 +502,14 @@ class FieldConverter(osv.AbstractModel):
|
|||
field_name, record._model._name, exc_info=True)
|
||||
content = None
|
||||
|
||||
g_att += ''.join(
|
||||
' %s="%s"' % (name, werkzeug.utils.escape(value))
|
||||
for name, value in self.attributes(
|
||||
cr, uid, field_name, record, options,
|
||||
source_element, g_att, t_att, qweb_context)
|
||||
)
|
||||
if context and context.get('inherit_branding'):
|
||||
# add branding attributes
|
||||
g_att += ''.join(
|
||||
' %s="%s"' % (name, werkzeug.utils.escape(value))
|
||||
for name, value in self.attributes(
|
||||
cr, uid, field_name, record, options,
|
||||
source_element, g_att, t_att, qweb_context)
|
||||
)
|
||||
|
||||
return self.render_element(cr, uid, source_element, t_att, g_att,
|
||||
qweb_context, content)
|
||||
|
@ -611,6 +612,9 @@ class DateTimeConverter(osv.AbstractModel):
|
|||
strftime_pattern = (u"%s %s" % (lang.date_format, lang.time_format))
|
||||
pattern = openerp.tools.posix_to_ldml(strftime_pattern, locale=locale)
|
||||
|
||||
if options and options.get('hide_seconds'):
|
||||
pattern = pattern.replace(":ss", "").replace(":s", "")
|
||||
|
||||
return babel.dates.format_datetime(value, format=pattern, locale=locale)
|
||||
|
||||
class TextConverter(osv.AbstractModel):
|
||||
|
|
|
@ -728,9 +728,26 @@ class view(osv.osv):
|
|||
def clear_cache(self):
|
||||
self.read_template.clear_cache(self)
|
||||
|
||||
def _contains_branded(self, node):
|
||||
return node.tag == 't'\
|
||||
or 't-raw' in node.attrib\
|
||||
or any(self.is_node_branded(child) for child in node.iterdescendants())
|
||||
|
||||
def _pop_view_branding(self, element):
|
||||
distributed_branding = dict(
|
||||
(attribute, element.attrib.pop(attribute))
|
||||
for attribute in MOVABLE_BRANDING
|
||||
if element.get(attribute))
|
||||
return distributed_branding
|
||||
|
||||
def distribute_branding(self, e, branding=None, parent_xpath='',
|
||||
index_map=misc.ConstantMapping(1)):
|
||||
if e.get('t-ignore') or e.tag == 'head':
|
||||
# remove any view branding possibly injected by inheritance
|
||||
attrs = set(MOVABLE_BRANDING)
|
||||
for descendant in e.iterdescendants(tag=etree.Element):
|
||||
if not attrs.intersection(descendant.attrib): continue
|
||||
self._pop_view_branding(descendant)
|
||||
# TODO: find a better name and check if we have a string to boolean helper
|
||||
return
|
||||
|
||||
|
@ -742,15 +759,15 @@ class view(osv.osv):
|
|||
e.set('data-oe-xpath', node_path)
|
||||
if not e.get('data-oe-model'): return
|
||||
|
||||
# if a branded element contains branded elements distribute own
|
||||
# branding to children unless it's t-raw, then just remove branding
|
||||
# on current element
|
||||
if e.tag == 't' or 't-raw' in e.attrib or \
|
||||
any(self.is_node_branded(child) for child in e.iterdescendants()):
|
||||
distributed_branding = dict(
|
||||
(attribute, e.attrib.pop(attribute))
|
||||
for attribute in MOVABLE_BRANDING
|
||||
if e.get(attribute))
|
||||
if set(('t-esc', 't-escf', 't-raw', 't-rawf')).intersection(e.attrib):
|
||||
# nodes which fully generate their content and have no reason to
|
||||
# be branded because they can not sensibly be edited
|
||||
self._pop_view_branding(e)
|
||||
elif self._contains_branded(e):
|
||||
# if a branded element contains branded elements distribute own
|
||||
# branding to children unless it's t-raw, then just remove branding
|
||||
# on current element
|
||||
distributed_branding = self._pop_view_branding(e)
|
||||
|
||||
if 't-raw' not in e.attrib:
|
||||
# TODO: collections.Counter if remove p2.6 compat
|
||||
|
@ -760,11 +777,12 @@ class view(osv.osv):
|
|||
if child.get('data-oe-xpath'):
|
||||
# injected by view inheritance, skip otherwise
|
||||
# generated xpath is incorrect
|
||||
continue
|
||||
indexes[child.tag] += 1
|
||||
self.distribute_branding(child, distributed_branding,
|
||||
parent_xpath=node_path,
|
||||
index_map=indexes)
|
||||
self.distribute_branding(child)
|
||||
else:
|
||||
indexes[child.tag] += 1
|
||||
self.distribute_branding(
|
||||
child, distributed_branding,
|
||||
parent_xpath=node_path, index_map=indexes)
|
||||
|
||||
def is_node_branded(self, node):
|
||||
""" Finds out whether a node is branded or qweb-active (bears a
|
||||
|
|
|
@ -208,20 +208,19 @@ class res_company(osv.osv):
|
|||
res['value'] = {'currency_id': currency_id}
|
||||
return res
|
||||
|
||||
def _search(self, cr, uid, args, offset=0, limit=None, order=None,
|
||||
context=None, count=False, access_rights_uid=None):
|
||||
def name_search(self, cr, uid, name='', args=None, operator='ilike', context=None, limit=100):
|
||||
if context is None:
|
||||
context = {}
|
||||
if context.get('user_preference'):
|
||||
if context.pop('user_preference', None):
|
||||
# We browse as superuser. Otherwise, the user would be able to
|
||||
# select only the currently visible companies (according to rules,
|
||||
# which are probably to allow to see the child companies) even if
|
||||
# she belongs to some other companies.
|
||||
user = self.pool.get('res.users').browse(cr, SUPERUSER_ID, uid, context=context)
|
||||
cmp_ids = list(set([user.company_id.id] + [cmp.id for cmp in user.company_ids]))
|
||||
return cmp_ids
|
||||
return super(res_company, self)._search(cr, uid, args, offset=offset, limit=limit, order=order,
|
||||
context=context, count=count, access_rights_uid=access_rights_uid)
|
||||
uid = SUPERUSER_ID
|
||||
args = (args or []) + [('id', 'in', cmp_ids)]
|
||||
return super(res_company, self).name_search(cr, uid, name=name, args=args, operator=operator, context=context, limit=limit)
|
||||
|
||||
def _company_default_get(self, cr, uid, object=False, field=False, context=None):
|
||||
"""
|
||||
|
|
|
@ -350,6 +350,7 @@ class res_partner(osv.osv, format_address):
|
|||
def copy(self, cr, uid, id, default=None, context=None):
|
||||
if default is None:
|
||||
default = {}
|
||||
default['user_ids'] = False
|
||||
name = self.read(cr, uid, [id], ['name'], context)[0]['name']
|
||||
default.update({'name': _('%s (copy)') % name})
|
||||
return super(res_partner, self).copy(cr, uid, id, default, context)
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
||||
# Copyright (C) 2010-2013 OpenERP s.a. (<http://openerp.com>).
|
||||
# Copyright (C) 2010-2014 OpenERP s.a. (<http://openerp.com>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
|
@ -169,8 +169,9 @@ class res_users(osv.osv):
|
|||
}
|
||||
|
||||
def on_change_login(self, cr, uid, ids, login, context=None):
|
||||
v = {'email': login} if tools.single_email_re.match(login) else {}
|
||||
return {'value': v}
|
||||
if login and tools.single_email_re.match(login):
|
||||
return {'value': {'email': login}}
|
||||
return {}
|
||||
|
||||
def onchange_state(self, cr, uid, ids, state_id, context=None):
|
||||
partner_ids = [user.partner_id.id for user in self.browse(cr, uid, ids, context=context)]
|
||||
|
@ -899,18 +900,22 @@ class change_password_wizard(osv.TransientModel):
|
|||
}))
|
||||
return {'user_ids': res}
|
||||
|
||||
|
||||
def change_password_button(self, cr, uid, id, context=None):
|
||||
wizard = self.browse(cr, uid, id, context=context)[0]
|
||||
user_ids = []
|
||||
for user in wizard.user_ids:
|
||||
user_ids.append(user.id)
|
||||
self.pool.get('change.password.user').change_password_button(cr, uid, user_ids, context=context)
|
||||
need_reload = any(uid == user.user_id.id for user in wizard.user_ids)
|
||||
line_ids = [user.id for user in wizard.user_ids]
|
||||
|
||||
self.pool.get('change.password.user').change_password_button(cr, uid, line_ids, context=context)
|
||||
# don't keep temporary password copies in the database longer than necessary
|
||||
self.pool.get('change.password.user').unlink(cr, uid, user_ids)
|
||||
return {
|
||||
'type': 'ir.actions.act_window_close',
|
||||
}
|
||||
self.pool.get('change.password.user').write(cr, uid, line_ids, {'new_passwd': False}, context=context)
|
||||
|
||||
if need_reload:
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'reload'
|
||||
}
|
||||
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
class change_password_user(osv.TransientModel):
|
||||
"""
|
||||
|
|
|
@ -282,7 +282,7 @@
|
|||
<group name="preferences" col="4">
|
||||
<field name="lang" readonly="0"/>
|
||||
<field name="tz" readonly="0"/>
|
||||
<field name="company_id" widget="selection" readonly="0"
|
||||
<field name="company_id" options="{'no_create': True}" readonly="0"
|
||||
groups="base.group_multi_company"/>
|
||||
</group>
|
||||
<group string="Email Preferences">
|
||||
|
|
|
@ -9,6 +9,7 @@ class test_base(common.TransactionCase):
|
|||
super(test_base,self).setUp()
|
||||
self.res_partner = self.registry('res.partner')
|
||||
self.res_users = self.registry('res.users')
|
||||
self.res_partner_title = self.registry('res.partner.title')
|
||||
|
||||
# samples use effective TLDs from the Mozilla public suffix
|
||||
# list at http://publicsuffix.org
|
||||
|
@ -285,27 +286,99 @@ class test_base(common.TransactionCase):
|
|||
|
||||
def test_60_read_group(self):
|
||||
cr, uid = self.cr, self.uid
|
||||
for user_data in [
|
||||
{'name': 'Alice', 'login': 'alice', 'color': 1, 'function': 'Friend'},
|
||||
{'name': 'Bob', 'login': 'bob', 'color': 2, 'function': 'Friend'},
|
||||
{'name': 'Eve', 'login': 'eve', 'color': 3, 'function': 'Eavesdropper'},
|
||||
{'name': 'Nab', 'login': 'nab', 'color': 2, 'function': '5$ Wrench'},
|
||||
]:
|
||||
self.res_users.create(cr, uid, user_data)
|
||||
title_sir = self.res_partner_title.create(cr, uid, {'name': 'Sir', 'domain': 'contact'})
|
||||
title_lady = self.res_partner_title.create(cr, uid, {'name': 'Lady', 'domain': 'contact'})
|
||||
test_users = [
|
||||
{'name': 'Alice', 'login': 'alice', 'color': 1, 'function': 'Friend', 'date': '2015-03-28', 'title': title_lady},
|
||||
{'name': 'Alice', 'login': 'alice2', 'color': 0, 'function': 'Friend', 'date': '2015-01-28', 'title': title_lady},
|
||||
{'name': 'Bob', 'login': 'bob', 'color': 2, 'function': 'Friend', 'date': '2015-03-02', 'title': title_sir},
|
||||
{'name': 'Eve', 'login': 'eve', 'color': 3, 'function': 'Eavesdropper', 'date': '2015-03-20', 'title': title_lady},
|
||||
{'name': 'Nab', 'login': 'nab', 'color': -3, 'function': '5$ Wrench', 'date': '2014-09-10', 'title': title_sir},
|
||||
{'name': 'Nab', 'login': 'nab-she', 'color': 6, 'function': '5$ Wrench', 'date': '2014-01-02', 'title': title_lady},
|
||||
]
|
||||
ids = [self.res_users.create(cr, uid, u) for u in test_users]
|
||||
domain = [('id', 'in', ids)]
|
||||
|
||||
groups_data = self.res_users.read_group(cr, uid, domain=[('login', 'in', ('alice', 'bob', 'eve'))], fields=['name', 'color', 'function'], groupby='function')
|
||||
self.assertEqual(len(groups_data), 2, "Incorrect number of results when grouping on a field")
|
||||
# group on local char field without domain and without active_test (-> empty WHERE clause)
|
||||
groups_data = self.res_users.read_group(cr, uid, [], fields=['login'], groupby=['login'], orderby='login DESC', context={'active_test': False})
|
||||
self.assertGreater(len(groups_data), 6, "Incorrect number of results when grouping on a field")
|
||||
|
||||
# group on local char field with limit
|
||||
groups_data = self.res_users.read_group(cr, uid, domain, fields=['login'], groupby=['login'], orderby='login DESC', limit=3, offset=3)
|
||||
self.assertEqual(len(groups_data), 3, "Incorrect number of results when grouping on a field with limit")
|
||||
self.assertEqual(['bob', 'alice2', 'alice'], [g['login'] for g in groups_data], 'Result mismatch')
|
||||
|
||||
# group on inherited char field, aggregate on int field (second groupby ignored on purpose)
|
||||
groups_data = self.res_users.read_group(cr, uid, domain, fields=['name', 'color', 'function'], groupby=['function', 'login'])
|
||||
self.assertEqual(len(groups_data), 3, "Incorrect number of results when grouping on a field")
|
||||
self.assertEqual(['5$ Wrench', 'Eavesdropper', 'Friend'], [g['function'] for g in groups_data], 'incorrect read_group order')
|
||||
for group_data in groups_data:
|
||||
self.assertIn('color', group_data, "Aggregated data for the column 'color' is not present in read_group return values")
|
||||
self.assertEqual(group_data['color'], 3, "Incorrect sum for aggregated data for the column 'color'")
|
||||
self.assertIn('color', group_data, "Aggregated data for the column 'color' is not present in read_group return values")
|
||||
self.assertEqual(group_data['color'], 3, "Incorrect sum for aggregated data for the column 'color'")
|
||||
|
||||
groups_data = self.res_users.read_group(cr, uid, domain=[('login', 'in', ('alice', 'bob', 'eve'))], fields=['name', 'color'], groupby='name', orderby='name DESC, color asc')
|
||||
self.assertEqual(len(groups_data), 3, "Incorrect number of results when grouping on a field")
|
||||
self.assertEqual([user['name'] for user in groups_data], ['Eve', 'Bob', 'Alice'], 'Incorrect ordering of the list')
|
||||
# group on inherited char field, reverse order
|
||||
groups_data = self.res_users.read_group(cr, uid, domain, fields=['name', 'color'], groupby='name', orderby='name DESC')
|
||||
self.assertEqual(['Nab', 'Eve', 'Bob', 'Alice'], [g['name'] for g in groups_data], 'Incorrect ordering of the list')
|
||||
|
||||
# group on int field, default ordering
|
||||
groups_data = self.res_users.read_group(cr, uid, domain, fields=['color'], groupby='color')
|
||||
self.assertEqual([-3, 0, 1, 2, 3, 6], [g['color'] for g in groups_data], 'Incorrect ordering of the list')
|
||||
|
||||
# multi group, second level is int field, should still be summed in first level grouping
|
||||
groups_data = self.res_users.read_group(cr, uid, domain, fields=['name', 'color'], groupby=['name', 'color'], orderby='name DESC')
|
||||
self.assertEqual(['Nab', 'Eve', 'Bob', 'Alice'], [g['name'] for g in groups_data], 'Incorrect ordering of the list')
|
||||
self.assertEqual([3, 3, 2, 1], [g['color'] for g in groups_data], 'Incorrect ordering of the list')
|
||||
|
||||
# group on inherited char field, multiple orders with directions
|
||||
groups_data = self.res_users.read_group(cr, uid, domain, fields=['name', 'color'], groupby='name', orderby='color DESC, name')
|
||||
self.assertEqual(len(groups_data), 4, "Incorrect number of results when grouping on a field")
|
||||
self.assertEqual(['Eve', 'Nab', 'Bob', 'Alice'], [g['name'] for g in groups_data], 'Incorrect ordering of the list')
|
||||
self.assertEqual([1, 2, 1, 2], [g['name_count'] for g in groups_data], 'Incorrect number of results')
|
||||
|
||||
# group on inherited date column (res_partner.date) -> Year-Month, default ordering
|
||||
groups_data = self.res_users.read_group(cr, uid, domain, fields=['function', 'color', 'date'], groupby=['date'])
|
||||
self.assertEqual(len(groups_data), 4, "Incorrect number of results when grouping on a field")
|
||||
self.assertEqual(['January 2014', 'September 2014', 'January 2015', 'March 2015'], [g['date'] for g in groups_data], 'Incorrect ordering of the list')
|
||||
self.assertEqual([1, 1, 1, 3], [g['date_count'] for g in groups_data], 'Incorrect number of results')
|
||||
|
||||
# group on inherited date column (res_partner.date) -> Year-Month, custom order
|
||||
groups_data = self.res_users.read_group(cr, uid, domain, fields=['function', 'color', 'date'], groupby=['date'], orderby='date DESC')
|
||||
self.assertEqual(len(groups_data), 4, "Incorrect number of results when grouping on a field")
|
||||
self.assertEqual(['March 2015', 'January 2015', 'September 2014', 'January 2014'], [g['date'] for g in groups_data], 'Incorrect ordering of the list')
|
||||
self.assertEqual([3, 1, 1, 1], [g['date_count'] for g in groups_data], 'Incorrect number of results')
|
||||
|
||||
# group on inherited many2one (res_partner.title), default order
|
||||
groups_data = self.res_users.read_group(cr, uid, domain, fields=['function', 'color', 'title'], groupby=['title'])
|
||||
self.assertEqual(len(groups_data), 2, "Incorrect number of results when grouping on a field")
|
||||
# m2o is returned as a (id, label) pair
|
||||
self.assertEqual([(title_lady, 'Lady'), (title_sir, 'Sir')], [g['title'] for g in groups_data], 'Incorrect ordering of the list')
|
||||
self.assertEqual([4, 2], [g['title_count'] for g in groups_data], 'Incorrect number of results')
|
||||
self.assertEqual([10, -1], [g['color'] for g in groups_data], 'Incorrect aggregation of int column')
|
||||
|
||||
# group on inherited many2one (res_partner.title), reversed natural order
|
||||
groups_data = self.res_users.read_group(cr, uid, domain, fields=['function', 'color', 'title'], groupby=['title'], orderby="title desc")
|
||||
self.assertEqual(len(groups_data), 2, "Incorrect number of results when grouping on a field")
|
||||
# m2o is returned as a (id, label) pair
|
||||
self.assertEqual([(title_sir, 'Sir'), (title_lady, 'Lady')], [g['title'] for g in groups_data], 'Incorrect ordering of the list')
|
||||
self.assertEqual([2, 4], [g['title_count'] for g in groups_data], 'Incorrect number of results')
|
||||
self.assertEqual([-1, 10], [g['color'] for g in groups_data], 'Incorrect aggregation of int column')
|
||||
|
||||
# group on inherited many2one (res_partner.title), multiple orders with m2o in second position
|
||||
groups_data = self.res_users.read_group(cr, uid, domain, fields=['function', 'color', 'title'], groupby=['title'], orderby="color desc, title desc")
|
||||
self.assertEqual(len(groups_data), 2, "Incorrect number of results when grouping on a field")
|
||||
# m2o is returned as a (id, label) pair
|
||||
self.assertEqual([(title_lady, 'Lady'), (title_sir, 'Sir')], [g['title'] for g in groups_data], 'Incorrect ordering of the result')
|
||||
self.assertEqual([4, 2], [g['title_count'] for g in groups_data], 'Incorrect number of results')
|
||||
self.assertEqual([10, -1], [g['color'] for g in groups_data], 'Incorrect aggregation of int column')
|
||||
|
||||
# group on inherited many2one (res_partner.title), ordered by other inherited field (color)
|
||||
groups_data = self.res_users.read_group(cr, uid, domain, fields=['function', 'color', 'title'], groupby=['title'], orderby='color')
|
||||
self.assertEqual(len(groups_data), 2, "Incorrect number of results when grouping on a field")
|
||||
# m2o is returned as a (id, label) pair
|
||||
self.assertEqual([(title_sir, 'Sir'), (title_lady, 'Lady')], [g['title'] for g in groups_data], 'Incorrect ordering of the list')
|
||||
self.assertEqual([2, 4], [g['title_count'] for g in groups_data], 'Incorrect number of results')
|
||||
self.assertEqual([-1, 10], [g['color'] for g in groups_data], 'Incorrect aggregation of int column')
|
||||
|
||||
groups_data = self.res_users.read_group(cr, uid, domain=[('login', 'in', ('alice', 'bob', 'eve', 'nab'))], fields=['function', 'color'], groupby='function', orderby='color ASC')
|
||||
self.assertEqual(len(groups_data), 3, "Incorrect number of results when grouping on a field")
|
||||
self.assertEqual(groups_data, sorted(groups_data, key=lambda x: x['color']), 'Incorrect ordering of the list')
|
||||
|
||||
class test_partner_recursion(common.TransactionCase):
|
||||
|
||||
|
|
|
@ -179,3 +179,51 @@ class TestPropertyField(common.TransactionCase):
|
|||
self.partner.write(cr, alice, [partner_id], {'property_country': country_be})
|
||||
self.assertEqual(self.partner.browse(cr, alice, partner_id).property_country.id, country_be, "Alice does not see the value he has set on the property field")
|
||||
self.assertEqual(self.partner.browse(cr, bob, partner_id).property_country.id, country_fr, "Changes made by Alice have overwritten Bob's value")
|
||||
|
||||
|
||||
class TestHtmlField(common.TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestHtmlField, self).setUp()
|
||||
self.partner = self.registry('res.partner')
|
||||
|
||||
def test_00_sanitize(self):
|
||||
cr, uid, context = self.cr, self.uid, {}
|
||||
old_columns = self.partner._columns
|
||||
self.partner._columns = dict(old_columns)
|
||||
self.partner._columns.update({
|
||||
'comment': fields.html('Secure Html', sanitize=False),
|
||||
})
|
||||
some_ugly_html = """<p>Oops this should maybe be sanitized
|
||||
% if object.some_field and not object.oriented:
|
||||
<table>
|
||||
% if object.other_field:
|
||||
<tr>
|
||||
${object.mako_thing}
|
||||
<td>
|
||||
</tr>
|
||||
% endif
|
||||
<tr>
|
||||
%if object.dummy_field:
|
||||
<p>Youpie</p>
|
||||
%endif"""
|
||||
|
||||
pid = self.partner.create(cr, uid, {
|
||||
'name': 'Raoul Poilvache',
|
||||
'comment': some_ugly_html,
|
||||
}, context=context)
|
||||
partner = self.partner.browse(cr, uid, pid, context=context)
|
||||
self.assertEqual(partner.comment, some_ugly_html, 'Error in HTML field: content was sanitized but field has sanitize=False')
|
||||
|
||||
self.partner._columns.update({
|
||||
'comment': fields.html('Unsecure Html', sanitize=True),
|
||||
})
|
||||
self.partner.write(cr, uid, [pid], {
|
||||
'comment': some_ugly_html,
|
||||
}, context=context)
|
||||
partner = self.partner.browse(cr, uid, pid, context=context)
|
||||
# sanitize should have closed tags left open in the original html
|
||||
self.assertIn('</table>', partner.comment, 'Error in HTML field: content does not seem to have been sanitized despise sanitize=True')
|
||||
self.assertIn('</td>', partner.comment, 'Error in HTML field: content does not seem to have been sanitized despise sanitize=True')
|
||||
|
||||
self.partner._columns = old_columns
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
# OPENERP_DATABASE=yy PYTHONPATH=../:. unit2 test_ir_sequence
|
||||
# This assume an existing database.
|
||||
import psycopg2
|
||||
import psycopg2.errorcodes
|
||||
import unittest2
|
||||
|
||||
import openerp
|
||||
|
@ -111,11 +112,11 @@ class test_ir_sequence_no_gap(unittest2.TestCase):
|
|||
cr0 = cursor()
|
||||
cr1 = cursor()
|
||||
cr1._default_log_exceptions = False # Prevent logging a traceback
|
||||
msg_re = '^could not obtain lock on row in relation "ir_sequence"$'
|
||||
with self.assertRaisesRegexp(psycopg2.OperationalError, msg_re):
|
||||
with self.assertRaises(psycopg2.OperationalError) as e:
|
||||
n0 = registry('ir.sequence').next_by_code(cr0, ADMIN_USER_ID, 'test_sequence_type_2', {})
|
||||
assert n0
|
||||
n1 = registry('ir.sequence').next_by_code(cr1, ADMIN_USER_ID, 'test_sequence_type_2', {})
|
||||
self.assertEqual(e.exception.pgcode, psycopg2.errorcodes.LOCK_NOT_AVAILABLE, msg="postgresql returned an incorrect errcode")
|
||||
cr0.close()
|
||||
cr1.close()
|
||||
|
||||
|
|
|
@ -14,7 +14,8 @@ class TestQWebTField(common.TransactionCase):
|
|||
self.engine = self.registry('ir.qweb')
|
||||
|
||||
def context(self, values):
|
||||
return ir_qweb.QWebContext(self.cr, self.uid, values)
|
||||
return ir_qweb.QWebContext(
|
||||
self.cr, self.uid, values, context={'inherit_branding': True})
|
||||
|
||||
def test_trivial(self):
|
||||
field = document.createElement('span')
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
from functools import partial
|
||||
|
||||
import unittest2
|
||||
|
||||
from lxml import etree as ET
|
||||
from lxml.builder import E
|
||||
|
||||
|
@ -8,6 +10,24 @@ from openerp.tests import common
|
|||
|
||||
Field = E.field
|
||||
|
||||
class ViewCase(common.TransactionCase):
|
||||
def setUp(self):
|
||||
super(ViewCase, self).setUp()
|
||||
self.addTypeEqualityFunc(ET._Element, self.assertTreesEqual)
|
||||
|
||||
def assertTreesEqual(self, n1, n2, msg=None):
|
||||
self.assertEqual(n1.tag, n2.tag)
|
||||
self.assertEqual((n1.text or '').strip(), (n2.text or '').strip(), msg)
|
||||
self.assertEqual((n1.tail or '').strip(), (n2.tail or '').strip(), msg)
|
||||
|
||||
# Because lxml uses ordereddicts in which order is important to
|
||||
# equality (!?!?!?!)
|
||||
self.assertEqual(dict(n1.attrib), dict(n2.attrib), msg)
|
||||
|
||||
for c1, c2 in zip(n1, n2):
|
||||
self.assertTreesEqual(c1, c2, msg)
|
||||
|
||||
|
||||
class TestNodeLocator(common.BaseCase):
|
||||
"""
|
||||
The node locator returns None when it can not find a node, and the first
|
||||
|
@ -98,7 +118,7 @@ class TestNodeLocator(common.BaseCase):
|
|||
E.foo(attr='1', version='3'))
|
||||
self.assertIsNone(node)
|
||||
|
||||
class TestViewInheritance(common.TransactionCase):
|
||||
class TestViewInheritance(ViewCase):
|
||||
def arch_for(self, name, view_type='form', parent=None):
|
||||
""" Generates a trivial view of the specified ``view_type``.
|
||||
|
||||
|
@ -206,7 +226,7 @@ class TestViewInheritance(common.TransactionCase):
|
|||
self.View.default_view(
|
||||
self.cr, self.uid, model=self.model, view_type='graph'))
|
||||
|
||||
class TestApplyInheritanceSpecs(common.TransactionCase):
|
||||
class TestApplyInheritanceSpecs(ViewCase):
|
||||
""" Applies a sequence of inheritance specification nodes to a base
|
||||
architecture. IO state parameters (cr, uid, model, context) are used for
|
||||
error reporting
|
||||
|
@ -230,8 +250,8 @@ class TestApplyInheritanceSpecs(common.TransactionCase):
|
|||
spec, None)
|
||||
|
||||
self.assertEqual(
|
||||
ET.tostring(self.base_arch),
|
||||
ET.tostring(E.form(Field(name="replacement"), string="Title")))
|
||||
self.base_arch,
|
||||
E.form(Field(name="replacement"), string="Title"))
|
||||
|
||||
def test_delete(self):
|
||||
spec = Field(name="target", position="replace")
|
||||
|
@ -241,8 +261,8 @@ class TestApplyInheritanceSpecs(common.TransactionCase):
|
|||
spec, None)
|
||||
|
||||
self.assertEqual(
|
||||
ET.tostring(self.base_arch),
|
||||
ET.tostring(E.form(string="Title")))
|
||||
self.base_arch,
|
||||
E.form(string="Title"))
|
||||
|
||||
def test_insert_after(self):
|
||||
spec = Field(
|
||||
|
@ -254,12 +274,12 @@ class TestApplyInheritanceSpecs(common.TransactionCase):
|
|||
spec, None)
|
||||
|
||||
self.assertEqual(
|
||||
ET.tostring(self.base_arch),
|
||||
ET.tostring(E.form(
|
||||
self.base_arch,
|
||||
E.form(
|
||||
Field(name="target"),
|
||||
Field(name="inserted"),
|
||||
string="Title"
|
||||
)))
|
||||
))
|
||||
|
||||
def test_insert_before(self):
|
||||
spec = Field(
|
||||
|
@ -271,11 +291,11 @@ class TestApplyInheritanceSpecs(common.TransactionCase):
|
|||
spec, None)
|
||||
|
||||
self.assertEqual(
|
||||
ET.tostring(self.base_arch),
|
||||
ET.tostring(E.form(
|
||||
self.base_arch,
|
||||
E.form(
|
||||
Field(name="inserted"),
|
||||
Field(name="target"),
|
||||
string="Title")))
|
||||
string="Title"))
|
||||
|
||||
def test_insert_inside(self):
|
||||
default = Field(Field(name="inserted"), name="target")
|
||||
|
@ -289,13 +309,13 @@ class TestApplyInheritanceSpecs(common.TransactionCase):
|
|||
spec, None)
|
||||
|
||||
self.assertEqual(
|
||||
ET.tostring(self.base_arch),
|
||||
ET.tostring(E.form(
|
||||
self.base_arch,
|
||||
E.form(
|
||||
Field(
|
||||
Field(name="inserted"),
|
||||
Field(name="inserted 2"),
|
||||
name="target"),
|
||||
string="Title")))
|
||||
string="Title"))
|
||||
|
||||
def test_unpack_data(self):
|
||||
spec = E.data(
|
||||
|
@ -310,15 +330,15 @@ class TestApplyInheritanceSpecs(common.TransactionCase):
|
|||
spec, None)
|
||||
|
||||
self.assertEqual(
|
||||
ET.tostring(self.base_arch),
|
||||
ET.tostring(E.form(
|
||||
self.base_arch,
|
||||
E.form(
|
||||
Field(
|
||||
Field(name="inserted 0"),
|
||||
Field(name="inserted 1"),
|
||||
Field(name="inserted 2"),
|
||||
Field(name="inserted 3"),
|
||||
name="target"),
|
||||
string="Title")))
|
||||
string="Title"))
|
||||
|
||||
def test_invalid_position(self):
|
||||
spec = Field(
|
||||
|
@ -350,18 +370,18 @@ class TestApplyInheritanceSpecs(common.TransactionCase):
|
|||
self.base_arch,
|
||||
spec, None)
|
||||
|
||||
class TestApplyInheritedArchs(common.TransactionCase):
|
||||
class TestApplyInheritedArchs(ViewCase):
|
||||
""" Applies a sequence of modificator archs to a base view
|
||||
"""
|
||||
|
||||
class TestViewCombined(common.TransactionCase):
|
||||
class TestViewCombined(ViewCase):
|
||||
"""
|
||||
Test fallback operations of View.read_combined:
|
||||
* defaults mapping
|
||||
* ?
|
||||
"""
|
||||
|
||||
class TestNoModel(common.TransactionCase):
|
||||
class TestNoModel(ViewCase):
|
||||
def test_create_view_nomodel(self):
|
||||
View = self.registry('ir.ui.view')
|
||||
view_id = View.create(self.cr, self.uid, {
|
||||
|
@ -411,13 +431,11 @@ class TestNoModel(common.TransactionCase):
|
|||
'value': translated_text,
|
||||
})
|
||||
sarch = View.translate_qweb(self.cr, self.uid, None, self.arch, 'fr_FR')
|
||||
|
||||
self.text_para.text = translated_text
|
||||
self.assertEqual(
|
||||
ET.tostring(sarch, encoding='utf-8'),
|
||||
ET.tostring(self.arch, encoding='utf-8'))
|
||||
|
||||
class TestTemplating(common.TransactionCase):
|
||||
self.text_para.text = translated_text
|
||||
self.assertEqual(sarch, self.arch)
|
||||
|
||||
class TestTemplating(ViewCase):
|
||||
def setUp(self):
|
||||
import openerp.modules
|
||||
super(TestTemplating, self).setUp()
|
||||
|
@ -473,7 +491,126 @@ class TestTemplating(common.TransactionCase):
|
|||
second.get('data-oe-id'),
|
||||
"second should come from the extension view")
|
||||
|
||||
class test_views(common.TransactionCase):
|
||||
def test_branding_distribute_inner(self):
|
||||
""" Checks that the branding is correctly distributed within a view
|
||||
extension
|
||||
"""
|
||||
Views = self.registry('ir.ui.view')
|
||||
id = Views.create(self.cr, self.uid, {
|
||||
'name': "Base view",
|
||||
'type': 'qweb',
|
||||
'arch': """<root>
|
||||
<item order="1"/>
|
||||
</root>"""
|
||||
})
|
||||
id2 = Views.create(self.cr, self.uid, {
|
||||
'name': "Extension",
|
||||
'type': 'qweb',
|
||||
'inherit_id': id,
|
||||
'arch': """<xpath expr="//item" position="before">
|
||||
<item order="2">
|
||||
<content t-att-href="foo">bar</content>
|
||||
</item>
|
||||
</xpath>"""
|
||||
})
|
||||
|
||||
arch_string = Views.read_combined(
|
||||
self.cr, self.uid, id, fields=['arch'],
|
||||
context={'inherit_branding': True})['arch']
|
||||
|
||||
arch = ET.fromstring(arch_string)
|
||||
Views.distribute_branding(arch)
|
||||
|
||||
self.assertEqual(
|
||||
arch,
|
||||
E.root(
|
||||
E.item(
|
||||
E.content("bar", {
|
||||
't-att-href': "foo",
|
||||
'data-oe-model': 'ir.ui.view',
|
||||
'data-oe-id': str(id2),
|
||||
'data-oe-field': 'arch',
|
||||
'data-oe-xpath': '/xpath/item/content[1]',
|
||||
}), {
|
||||
'order': '2',
|
||||
'data-oe-source-id': str(id)
|
||||
}),
|
||||
E.item({
|
||||
'order': '1',
|
||||
'data-oe-model': 'ir.ui.view',
|
||||
'data-oe-id': str(id),
|
||||
'data-oe-field': 'arch',
|
||||
'data-oe-xpath': '/root[1]/item[1]'
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
def test_esc_no_branding(self):
|
||||
Views = self.registry('ir.ui.view')
|
||||
id = Views.create(self.cr, self.uid, {
|
||||
'name': "Base View",
|
||||
'type': 'qweb',
|
||||
'arch': """<root>
|
||||
<item><span t-esc="foo"/></item>
|
||||
</root>""",
|
||||
})
|
||||
|
||||
arch_string = Views.read_combined(
|
||||
self.cr, self.uid, id, fields=['arch'],
|
||||
context={'inherit_branding': True})['arch']
|
||||
arch = ET.fromstring(arch_string)
|
||||
Views.distribute_branding(arch)
|
||||
|
||||
self.assertEqual(arch, E.root(E.item(E.span({'t-esc': "foo"}))))
|
||||
|
||||
def test_ignore_unbrand(self):
|
||||
Views = self.registry('ir.ui.view')
|
||||
id = Views.create(self.cr, self.uid, {
|
||||
'name': "Base view",
|
||||
'type': 'qweb',
|
||||
'arch': """<root>
|
||||
<item order="1" t-ignore="true">
|
||||
<t t-esc="foo"/>
|
||||
</item>
|
||||
</root>"""
|
||||
})
|
||||
id2 = Views.create(self.cr, self.uid, {
|
||||
'name': "Extension",
|
||||
'type': 'qweb',
|
||||
'inherit_id': id,
|
||||
'arch': """<xpath expr="//item[@order='1']" position="inside">
|
||||
<item order="2">
|
||||
<content t-att-href="foo">bar</content>
|
||||
</item>
|
||||
</xpath>"""
|
||||
})
|
||||
|
||||
arch_string = Views.read_combined(
|
||||
self.cr, self.uid, id, fields=['arch'],
|
||||
context={'inherit_branding': True})['arch']
|
||||
|
||||
arch = ET.fromstring(arch_string)
|
||||
Views.distribute_branding(arch)
|
||||
|
||||
self.assertEqual(
|
||||
arch,
|
||||
E.root(
|
||||
E.item(
|
||||
{'t-ignore': 'true', 'order': '1'},
|
||||
E.t({'t-esc': 'foo'}),
|
||||
E.item(
|
||||
{'order': '2', 'data-oe-source-id': str(id)},
|
||||
E.content(
|
||||
{'t-att-href': 'foo'},
|
||||
"bar")
|
||||
)
|
||||
)
|
||||
),
|
||||
"t-ignore should apply to injected sub-view branding, not just to"
|
||||
" the main view's"
|
||||
)
|
||||
|
||||
class test_views(ViewCase):
|
||||
|
||||
def test_nonexistent_attribute_removal(self):
|
||||
Views = self.registry('ir.ui.view')
|
||||
|
@ -589,16 +726,17 @@ class test_views(common.TransactionCase):
|
|||
})
|
||||
self.assertEqual(view['type'], 'form')
|
||||
self.assertEqual(
|
||||
ET.tostring(ET.fromstring(
|
||||
ET.fromstring(
|
||||
view['arch'],
|
||||
parser=ET.XMLParser(remove_blank_text=True)
|
||||
)),
|
||||
'<form string="Replacement title" version="7.0">'
|
||||
'<p>Replacement data</p>'
|
||||
'<footer thing="bob">'
|
||||
'<button name="action_next" type="object" string="New button"/>'
|
||||
'</footer>'
|
||||
'</form>')
|
||||
),
|
||||
E.form(
|
||||
E.p("Replacement data"),
|
||||
E.footer(
|
||||
E.button(name="action_next", type="object", string="New button"),
|
||||
thing="bob"
|
||||
),
|
||||
string="Replacement title", version="7.0"))
|
||||
|
||||
def test_view_inheritance_divergent_models(self):
|
||||
Views = self.registry('ir.ui.view')
|
||||
|
@ -656,14 +794,13 @@ class test_views(common.TransactionCase):
|
|||
})
|
||||
self.assertEqual(view['type'], 'form')
|
||||
self.assertEqual(
|
||||
ET.tostring(ET.fromstring(
|
||||
ET.fromstring(
|
||||
view['arch'],
|
||||
parser=ET.XMLParser(remove_blank_text=True)
|
||||
)),
|
||||
'<form string="Replacement title" version="7.0">'
|
||||
'<p>Replacement data</p>'
|
||||
'<footer>'
|
||||
'<button name="action_next" type="object" string="New button"/>'
|
||||
'</footer>'
|
||||
'</form>')
|
||||
|
||||
),
|
||||
E.form(
|
||||
E.p("Replacement data"),
|
||||
E.footer(
|
||||
E.button(name="action_next", type="object", string="New button")),
|
||||
string="Replacement title", version="7.0"
|
||||
))
|
||||
|
|
|
@ -112,11 +112,14 @@ class TestCurrencyExport(TestExport):
|
|||
'widget': 'monetary',
|
||||
'display_currency': 'c2'
|
||||
}
|
||||
context = dict(inherit_branding=True)
|
||||
converted = converter.to_html(
|
||||
self.cr, self.uid, 'value', obj, options,
|
||||
doc.createElement('span'),
|
||||
{'field': 'obj.value', 'field-options': json.dumps(options)},
|
||||
'', ir_qweb.QWebContext(self.cr, self.uid, {'obj': obj, 'c2': dest, }))
|
||||
'', ir_qweb.QWebContext(self.cr, self.uid, {'obj': obj, 'c2': dest, }),
|
||||
context=context,
|
||||
)
|
||||
return converted
|
||||
|
||||
def test_currency_post(self):
|
||||
|
|
|
@ -309,8 +309,9 @@ class test_m2o(CreatorCase):
|
|||
def test_external_id(self):
|
||||
integer_id = self.registry('export.integer').create(
|
||||
self.cr, openerp.SUPERUSER_ID, {'value': 42})
|
||||
# __export__.$class.$id
|
||||
external_id = u'__export__.export_many2one_%d' % integer_id
|
||||
# Expecting the m2o target model name in the external id,
|
||||
# not this model's name
|
||||
external_id = u'__export__.export_integer_%d' % integer_id
|
||||
self.assertEqual(
|
||||
self.export(integer_id, fields=['value/id']),
|
||||
[[external_id]])
|
||||
|
|
|
@ -133,6 +133,10 @@ class WebRequest(object):
|
|||
self.auth_method = None
|
||||
self._cr_cm = None
|
||||
self._cr = None
|
||||
|
||||
# prevents transaction commit, use when you catch an exception during handling
|
||||
self._failed = None
|
||||
|
||||
# set db/uid trackers - they're cleaned up at the WSGI
|
||||
# dispatching phase in openerp.service.wsgi_server.application
|
||||
if self.db:
|
||||
|
@ -180,10 +184,13 @@ class WebRequest(object):
|
|||
_request_stack.pop()
|
||||
|
||||
if self._cr:
|
||||
# Dont commit test cursors
|
||||
if not openerp.tests.common.release_test_cursor(self.session_id):
|
||||
if exc_type is None:
|
||||
# Dont close test cursors
|
||||
if not openerp.tests.common.release_test_cursor(self._cr):
|
||||
if exc_type is None and not self._failed:
|
||||
self._cr.commit()
|
||||
else:
|
||||
# just to be explicit - happens at close() anyway
|
||||
self._cr.rollback()
|
||||
self._cr.close()
|
||||
# just to be sure no one tries to re-use the request
|
||||
self.disable_db = True
|
||||
|
@ -209,20 +216,19 @@ class WebRequest(object):
|
|||
# Backward for 7.0
|
||||
if self.endpoint.first_arg_is_req:
|
||||
args = (request,) + args
|
||||
|
||||
# Correct exception handling and concurency retry
|
||||
@service_model.check
|
||||
def checked_call(___dbname, *a, **kw):
|
||||
return self.endpoint(*a, **kw)
|
||||
|
||||
# FIXME: code and rollback management could be cleaned
|
||||
try:
|
||||
if self.db:
|
||||
return checked_call(self.db, *args, **kwargs)
|
||||
return self.endpoint(*args, **kwargs)
|
||||
except Exception:
|
||||
# The decorator can call us more than once if there is an database error. In this
|
||||
# case, the request cursor is unusable. Rollback transaction to create a new one.
|
||||
if self._cr:
|
||||
self._cr.rollback()
|
||||
raise
|
||||
return self.endpoint(*a, **kw)
|
||||
|
||||
if self.db:
|
||||
return checked_call(self.db, *args, **kwargs)
|
||||
return self.endpoint(*args, **kwargs)
|
||||
|
||||
@property
|
||||
def debug(self):
|
||||
|
@ -362,21 +368,25 @@ class JsonRequest(WebRequest):
|
|||
response['id'] = self.jsonrequest.get('id')
|
||||
response["result"] = self._call_function(**self.params)
|
||||
except AuthenticationError, e:
|
||||
_logger.exception("Exception during JSON request handling.")
|
||||
_logger.exception("JSON-RPC AuthenticationError in %s.", self.httprequest.path)
|
||||
se = serialize_exception(e)
|
||||
error = {
|
||||
'code': 100,
|
||||
'message': "OpenERP Session Invalid",
|
||||
'data': se
|
||||
}
|
||||
self._failed = e # prevent tx commit
|
||||
except Exception, e:
|
||||
_logger.exception("Exception during JSON request handling.")
|
||||
# Mute test cursor error for runbot
|
||||
if not (openerp.tools.config['test_enable'] and isinstance(e, psycopg2.OperationalError)):
|
||||
_logger.exception("JSON-RPC Exception in %s.", self.httprequest.path)
|
||||
se = serialize_exception(e)
|
||||
error = {
|
||||
'code': 200,
|
||||
'message': "OpenERP Server Error",
|
||||
'data': se
|
||||
}
|
||||
self._failed = e # prevent tx commit
|
||||
if error:
|
||||
response["error"] = error
|
||||
|
||||
|
@ -733,7 +743,7 @@ class OpenERPSession(werkzeug.contrib.sessions.Session):
|
|||
self.setdefault("uid", None)
|
||||
self.setdefault("login", None)
|
||||
self.setdefault("password", None)
|
||||
self.setdefault("context", {'tz': "UTC", "uid": None})
|
||||
self.setdefault("context", {})
|
||||
|
||||
def get_context(self):
|
||||
"""
|
||||
|
@ -1210,11 +1220,6 @@ class CommonController(Controller):
|
|||
""" Method used by client APIs to contact OpenERP. """
|
||||
return openerp.netsvc.dispatch_rpc(service, method, args)
|
||||
|
||||
@route('/gen_session_id', type='json', auth="none")
|
||||
def gen_session_id(self):
|
||||
nsession = root.session_store.new()
|
||||
return nsession.sid
|
||||
|
||||
root = None
|
||||
|
||||
def wsgi_postload():
|
||||
|
|
|
@ -187,6 +187,10 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
|
|||
# Yamel test
|
||||
report.record_result(load_test(module_name, idref, mode))
|
||||
# Python tests
|
||||
ir_http = registry['ir.http']
|
||||
if hasattr(ir_http, '_routing_map'):
|
||||
# Force routing map to be rebuilt between each module test suite
|
||||
del(ir_http._routing_map)
|
||||
report.record_result(openerp.modules.module.run_unit_tests(module_name, cr.dbname))
|
||||
|
||||
processed_modules.append(package.name)
|
||||
|
|
|
@ -38,7 +38,6 @@ import openerp.release as release
|
|||
from openerp.tools.safe_eval import safe_eval as eval
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
_test_logger = logging.getLogger('openerp.tests')
|
||||
|
||||
# addons path as a list
|
||||
ad_paths = []
|
||||
|
@ -340,7 +339,8 @@ def get_test_modules(module):
|
|||
|
||||
# Use a custom stream object to log the test executions.
|
||||
class TestStream(object):
|
||||
def __init__(self):
|
||||
def __init__(self, logger_name='openerp.tests'):
|
||||
self.logger = logging.getLogger(logger_name)
|
||||
self.r = re.compile(r'^-*$|^ *... *$|^ok$')
|
||||
def flush(self):
|
||||
pass
|
||||
|
@ -352,7 +352,7 @@ class TestStream(object):
|
|||
if not first:
|
||||
c = '` ' + c
|
||||
first = False
|
||||
_test_logger.info(c)
|
||||
self.logger.info(c)
|
||||
|
||||
current_test = None
|
||||
|
||||
|
@ -369,9 +369,9 @@ def run_unit_tests(module_name, dbname):
|
|||
for m in mods:
|
||||
tests = unwrap_suite(unittest2.TestLoader().loadTestsFromModule(m))
|
||||
suite = unittest2.TestSuite(itertools.ifilter(runs_at_install, tests))
|
||||
_logger.info('module %s: running test %s.', module_name, m.__name__)
|
||||
_logger.info('running %s tests.', m.__name__)
|
||||
|
||||
result = unittest2.TextTestRunner(verbosity=2, stream=TestStream()).run(suite)
|
||||
result = unittest2.TextTestRunner(verbosity=2, stream=TestStream(m.__name__)).run(suite)
|
||||
|
||||
if not result.wasSuccessful():
|
||||
r = False
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2012 OpenERP SA (<http://www.openerp.com>)
|
||||
# Copyright (C) 2004-2014 OpenERP SA (<http://www.openerp.com>)
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
|
@ -89,7 +89,13 @@ class ColoredFormatter(DBFormatter):
|
|||
record.levelname = COLOR_PATTERN % (30 + fg_color, 40 + bg_color, record.levelname)
|
||||
return DBFormatter.format(self, record)
|
||||
|
||||
_logger_init = False
|
||||
def init_logger():
|
||||
global _logger_init
|
||||
if _logger_init:
|
||||
return
|
||||
_logger_init = True
|
||||
|
||||
from tools.translate import resetlocale
|
||||
resetlocale()
|
||||
|
||||
|
@ -137,6 +143,8 @@ def init_logger():
|
|||
formatter = DBFormatter(format)
|
||||
handler.setFormatter(formatter)
|
||||
|
||||
logging.getLogger().addHandler(handler)
|
||||
|
||||
# Configure handlers
|
||||
pseudo_config = PSEUDOCONFIG_MAPPER.get(tools.config['log_level'], [])
|
||||
|
||||
|
@ -147,11 +155,7 @@ def init_logger():
|
|||
loggername, level = logconfig_item.split(':')
|
||||
level = getattr(logging, level, logging.INFO)
|
||||
logger = logging.getLogger(loggername)
|
||||
logger.handlers = []
|
||||
logger.setLevel(level)
|
||||
logger.addHandler(handler)
|
||||
if loggername != '':
|
||||
logger.propagate = False
|
||||
|
||||
for logconfig_item in logging_configurations:
|
||||
_logger.debug('logger level set: "%s"', logconfig_item)
|
||||
|
|
|
@ -236,15 +236,24 @@ class char(_column):
|
|||
class text(_column):
|
||||
_type = 'text'
|
||||
|
||||
|
||||
class html(text):
|
||||
_type = 'html'
|
||||
_symbol_c = '%s'
|
||||
def _symbol_f(x):
|
||||
if x is None or x == False:
|
||||
|
||||
def _symbol_set_html(self, value):
|
||||
if value is None or value is False:
|
||||
return None
|
||||
return html_sanitize(x)
|
||||
|
||||
_symbol_set = (_symbol_c, _symbol_f)
|
||||
if not self._sanitize:
|
||||
return value
|
||||
return html_sanitize(value)
|
||||
|
||||
def __init__(self, string='unknown', sanitize=True, **args):
|
||||
super(html, self).__init__(string=string, **args)
|
||||
self._sanitize = sanitize
|
||||
# symbol_set redefinition because of sanitize specific behavior
|
||||
self._symbol_f = self._symbol_set_html
|
||||
self._symbol_set = (self._symbol_c, self._symbol_f)
|
||||
|
||||
import __builtin__
|
||||
|
||||
|
|
|
@ -75,7 +75,7 @@ _schema = logging.getLogger(__name__ + '.schema')
|
|||
# List of etree._Element subclasses that we choose to ignore when parsing XML.
|
||||
from openerp.tools import SKIPPED_ELEMENT_TYPES
|
||||
|
||||
regex_order = re.compile('^(([a-z0-9_]+|"[a-z0-9_]+")( *desc| *asc)?( *, *|))+$', re.I)
|
||||
regex_order = re.compile('^( *([a-z0-9_]+|"[a-z0-9_]+")( *desc| *asc)?( *, *|))+$', re.I)
|
||||
regex_object_name = re.compile(r'^[a-z0-9_.]+$')
|
||||
|
||||
AUTOINIT_RECALCULATE_STORED_FIELDS = 1000
|
||||
|
@ -1124,7 +1124,7 @@ class BaseModel(object):
|
|||
|
||||
def _get_xml_id(self, cr, uid, r):
|
||||
model_data = self.pool.get('ir.model.data')
|
||||
data_ids = model_data.search(cr, uid, [('model', '=', r._table_name), ('res_id', '=', r['id'])])
|
||||
data_ids = model_data.search(cr, uid, [('model', '=', r._model._name), ('res_id', '=', r['id'])])
|
||||
if len(data_ids):
|
||||
d = model_data.read(cr, uid, data_ids, ['name', 'module'])[0]
|
||||
if d['module']:
|
||||
|
@ -1134,13 +1134,13 @@ class BaseModel(object):
|
|||
else:
|
||||
postfix = 0
|
||||
while True:
|
||||
n = self._table+'_'+str(r['id']) + (postfix and ('_'+str(postfix)) or '' )
|
||||
n = r._model._table+'_'+str(r['id']) + (postfix and ('_'+str(postfix)) or '' )
|
||||
if not model_data.search(cr, uid, [('name', '=', n)]):
|
||||
break
|
||||
postfix += 1
|
||||
model_data.create(cr, SUPERUSER_ID, {
|
||||
'name': n,
|
||||
'model': self._name,
|
||||
'model': r._model._name,
|
||||
'res_id': r['id'],
|
||||
'module': '__export__',
|
||||
})
|
||||
|
@ -2185,36 +2185,42 @@ class BaseModel(object):
|
|||
r['__fold'] = folded.get(r[groupby] and r[groupby][0], False)
|
||||
return result
|
||||
|
||||
def _read_group_generate_order_by(self, orderby, aggregated_fields, groupby, query):
|
||||
def _read_group_prepare(self, orderby, aggregated_fields, groupby, qualified_groupby_field, query, groupby_type=None):
|
||||
"""
|
||||
Generates the ORDER BY sql clause for the read group method. Adds the missing JOIN clause
|
||||
Prepares the GROUP BY and ORDER BY terms for the read_group method. Adds the missing JOIN clause
|
||||
to the query if order should be computed against m2o field.
|
||||
:param orderby: the orderby definition in the form "%(field)s %(order)s"
|
||||
:param aggregated_fields: list of aggregated fields in the query
|
||||
:param groupby: the current groupby field name
|
||||
:param query: the query object used to construct the query afterwards
|
||||
:param qualified_groupby_field: the fully qualified SQL name for the grouped field
|
||||
:param osv.Query query: the query under construction
|
||||
:param groupby_type: the type of the grouped field
|
||||
:return: (groupby_terms, orderby_terms)
|
||||
"""
|
||||
orderby_list = []
|
||||
ob = []
|
||||
for order_splits in orderby.split(','):
|
||||
order_split = order_splits.split()
|
||||
orderby_field = order_split[0]
|
||||
fields = openerp.osv.fields
|
||||
if isinstance(self._all_columns[orderby_field].column, (fields.date, fields.datetime)):
|
||||
continue
|
||||
orderby_dir = len(order_split) == 2 and order_split[1].upper() == 'ASC' and 'ASC' or 'DESC'
|
||||
if orderby_field == groupby:
|
||||
orderby_item = self._generate_order_by(order_splits, query).replace('ORDER BY ', '')
|
||||
if orderby_item:
|
||||
orderby_list.append(orderby_item)
|
||||
ob += [obi.split()[0] for obi in orderby_item.split(',')]
|
||||
elif orderby_field in aggregated_fields:
|
||||
orderby_list.append('%s %s' % (orderby_field,orderby_dir))
|
||||
orderby_terms = []
|
||||
groupby_terms = [qualified_groupby_field] if groupby else []
|
||||
if not orderby:
|
||||
return groupby_terms, orderby_terms
|
||||
|
||||
if orderby_list:
|
||||
return ' ORDER BY %s' % (','.join(orderby_list)), ob and ','.join(ob) or ''
|
||||
else:
|
||||
return '', ''
|
||||
self._check_qorder(orderby)
|
||||
for order_part in orderby.split(','):
|
||||
order_split = order_part.split()
|
||||
order_field = order_split[0]
|
||||
if order_field == groupby:
|
||||
if groupby_type == 'many2one':
|
||||
order_clause = self._generate_order_by(order_part, query).replace('ORDER BY ', '')
|
||||
if order_clause:
|
||||
orderby_terms.append(order_clause)
|
||||
groupby_terms += [order_term.split()[0] for order_term in order_clause.split(',')]
|
||||
else:
|
||||
orderby_terms.append(order_part)
|
||||
elif order_field in aggregated_fields:
|
||||
orderby_terms.append(order_part)
|
||||
else:
|
||||
# Cannot order by a field that will not appear in the results (needs to be grouped or aggregated)
|
||||
_logger.warn('%s: read_group order by `%s` ignored, cannot sort on empty columns (not grouped/aggregated)',
|
||||
self._name, order_part)
|
||||
return groupby_terms, orderby_terms
|
||||
|
||||
def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False):
|
||||
"""
|
||||
|
@ -2275,9 +2281,9 @@ class BaseModel(object):
|
|||
|
||||
# TODO it seems fields_get can be replaced by _all_columns (no need for translation)
|
||||
fget = self.fields_get(cr, uid, fields)
|
||||
flist = ''
|
||||
group_count = group_by = groupby
|
||||
group_by_params = {}
|
||||
select_terms = []
|
||||
groupby_type = None
|
||||
if groupby:
|
||||
if fget.get(groupby):
|
||||
groupby_type = fget[groupby]['type']
|
||||
|
@ -2305,12 +2311,9 @@ class BaseModel(object):
|
|||
timezone = context.get('tz', 'UTC')
|
||||
qualified_groupby_field = "timezone('%s', timezone('UTC',%s))" % (timezone, qualified_groupby_field)
|
||||
qualified_groupby_field = "date_trunc('%s', %s)" % (interval, qualified_groupby_field)
|
||||
flist = "%s as %s " % (qualified_groupby_field, groupby)
|
||||
elif groupby_type == 'boolean':
|
||||
qualified_groupby_field = "coalesce(%s,false)" % qualified_groupby_field
|
||||
flist = "%s as %s " % (qualified_groupby_field, groupby)
|
||||
else:
|
||||
flist = qualified_groupby_field
|
||||
select_terms.append("%s as %s " % (qualified_groupby_field, groupby))
|
||||
else:
|
||||
# Don't allow arbitrary values, as this would be a SQL injection vector!
|
||||
raise except_orm(_('Invalid group_by'),
|
||||
|
@ -2318,34 +2321,48 @@ class BaseModel(object):
|
|||
|
||||
aggregated_fields = [
|
||||
f for f in fields
|
||||
if f not in ('id', 'sequence')
|
||||
if f not in ('id', 'sequence', groupby)
|
||||
if fget[f]['type'] in ('integer', 'float')
|
||||
if (f in self._all_columns and getattr(self._all_columns[f].column, '_classic_write'))]
|
||||
for f in aggregated_fields:
|
||||
group_operator = fget[f].get('group_operator', 'sum')
|
||||
if flist:
|
||||
flist += ', '
|
||||
qualified_field = self._inherits_join_calc(f, query)
|
||||
flist += "%s(%s) AS %s" % (group_operator, qualified_field, f)
|
||||
select_terms.append("%s(%s) AS %s" % (group_operator, qualified_field, f))
|
||||
|
||||
order = orderby or groupby
|
||||
orderby_clause = ''
|
||||
ob = ''
|
||||
if order:
|
||||
orderby_clause, ob = self._read_group_generate_order_by(order, aggregated_fields, groupby, query)
|
||||
|
||||
gb = groupby and (' GROUP BY ' + qualified_groupby_field) or ''
|
||||
order = orderby or groupby or ''
|
||||
groupby_terms, orderby_terms = self._read_group_prepare(order, aggregated_fields, groupby, qualified_groupby_field, query, groupby_type)
|
||||
|
||||
from_clause, where_clause, where_clause_params = query.get_sql()
|
||||
where_clause = where_clause and ' WHERE ' + where_clause
|
||||
limit_str = limit and ' limit %d' % limit or ''
|
||||
offset_str = offset and ' offset %d' % offset or ''
|
||||
if len(groupby_list) < 2 and context.get('group_by_no_leaf'):
|
||||
group_count = '_'
|
||||
cr.execute('SELECT min(%s.id) AS id, count(%s.id) AS %s_count' % (self._table, self._table, group_count) + (flist and ',') + flist + ' FROM ' + from_clause + where_clause + gb + (ob and ',') + ob + orderby_clause + limit_str + offset_str, where_clause_params)
|
||||
alldata = {}
|
||||
groupby = group_by
|
||||
count_field = '_'
|
||||
else:
|
||||
count_field = groupby
|
||||
|
||||
prefix_terms = lambda prefix, terms: (prefix + " " + ",".join(terms)) if terms else ''
|
||||
prefix_term = lambda prefix, term: ('%s %s' % (prefix, term)) if term else ''
|
||||
|
||||
query = """
|
||||
SELECT min(%(table)s.id) AS id, count(%(table)s.id) AS %(count_field)s_count
|
||||
%(extra_fields)s
|
||||
FROM %(from)s
|
||||
%(where)s
|
||||
%(groupby)s
|
||||
%(orderby)s
|
||||
%(limit)s
|
||||
%(offset)s
|
||||
""" % {
|
||||
'table': self._table,
|
||||
'count_field': count_field,
|
||||
'extra_fields': prefix_terms(',', select_terms),
|
||||
'from': from_clause,
|
||||
'where': prefix_term('WHERE', where_clause),
|
||||
'groupby': prefix_terms('GROUP BY', groupby_terms),
|
||||
'orderby': prefix_terms('ORDER BY', orderby_terms),
|
||||
'limit': prefix_term('LIMIT', int(limit) if limit else None),
|
||||
'offset': prefix_term('OFFSET', int(offset) if limit else None),
|
||||
}
|
||||
cr.execute(query, where_clause_params)
|
||||
alldata = {}
|
||||
fetched_data = cr.dictfetchall()
|
||||
|
||||
data_ids = []
|
||||
|
@ -2356,8 +2373,6 @@ class BaseModel(object):
|
|||
data_ids.append(r['id'])
|
||||
del r['id']
|
||||
|
||||
|
||||
|
||||
if groupby:
|
||||
data = self.read(cr, uid, data_ids, [groupby], context=context)
|
||||
# restore order of the search as read() uses the default _order (this is only for groups, so the footprint of data should be small):
|
||||
|
@ -2847,7 +2862,7 @@ class BaseModel(object):
|
|||
msg = "Table '%s': dropping index for column '%s' of type '%s' as it is not required anymore"
|
||||
_schema.debug(msg, self._table, k, f._type)
|
||||
|
||||
if isinstance(f, fields.many2one):
|
||||
if isinstance(f, fields.many2one) or (isinstance(f, fields.function) and f._type == 'many2one' and f.store):
|
||||
dest_model = self.pool[f._obj]
|
||||
if dest_model._table != 'ir_actions':
|
||||
self._m2o_fix_foreign_key(cr, self._table, k, dest_model, f.ondelete)
|
||||
|
@ -2882,7 +2897,7 @@ class BaseModel(object):
|
|||
todo_end.append((order, self._update_store, (f, k)))
|
||||
|
||||
# and add constraints if needed
|
||||
if isinstance(f, fields.many2one):
|
||||
if isinstance(f, fields.many2one) or (isinstance(f, fields.function) and f._type == 'many2one' and f.store):
|
||||
if f._obj not in self.pool:
|
||||
raise except_orm('Programming Error', 'There is no reference available for %s' % (f._obj,))
|
||||
dest_model = self.pool[f._obj]
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import base64
|
||||
import contextlib
|
||||
from contextlib import closing
|
||||
from functools import wraps
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import threading
|
||||
import traceback
|
||||
from contextlib import contextmanager, closing
|
||||
import tempfile
|
||||
import zipfile
|
||||
|
||||
import psycopg2
|
||||
|
||||
import openerp
|
||||
from openerp import SUPERUSER_ID
|
||||
|
@ -28,7 +31,8 @@ def _initialize_db(id, db_name, demo, lang, user_password):
|
|||
self_actions[id]['progress'] = 0
|
||||
db = openerp.sql_db.db_connect(db_name)
|
||||
with closing(db.cursor()) as cr:
|
||||
openerp.modules.db.initialize(cr) # TODO this should be removed as it is done by RegistryManager.new().
|
||||
# TODO this should be removed as it is done by RegistryManager.new().
|
||||
openerp.modules.db.initialize(cr)
|
||||
openerp.tools.config['lang'] = lang
|
||||
cr.commit()
|
||||
|
||||
|
@ -55,14 +59,13 @@ def _initialize_db(id, db_name, demo, lang, user_password):
|
|||
self_actions[id]['traceback'] = traceback.format_exc()
|
||||
|
||||
def dispatch(method, params):
|
||||
if method in [ 'create', 'get_progress', 'drop', 'dump',
|
||||
'restore', 'rename',
|
||||
'change_admin_password', 'migrate_databases',
|
||||
'create_database', 'duplicate_database' ]:
|
||||
if method in ['create', 'get_progress', 'drop', 'dump', 'restore', 'rename',
|
||||
'change_admin_password', 'migrate_databases',
|
||||
'create_database', 'duplicate_database']:
|
||||
passwd = params[0]
|
||||
params = params[1:]
|
||||
security.check_super(passwd)
|
||||
elif method in [ 'db_exist', 'list', 'list_lang', 'server_version' ]:
|
||||
elif method in ['db_exist', 'list', 'list_lang', 'server_version']:
|
||||
# params = params
|
||||
# No security check for these methods
|
||||
pass
|
||||
|
@ -78,9 +81,9 @@ def _create_empty_database(name):
|
|||
cr.execute("SELECT datname FROM pg_database WHERE datname = %s",
|
||||
(name,))
|
||||
if cr.fetchall():
|
||||
raise openerp.exceptions.Warning(" %s database already exists!" % name )
|
||||
raise openerp.exceptions.Warning("database %r already exists!" % (name,))
|
||||
else:
|
||||
cr.autocommit(True) # avoid transaction block
|
||||
cr.autocommit(True) # avoid transaction block
|
||||
cr.execute("""CREATE DATABASE "%s" ENCODING 'unicode' TEMPLATE "%s" """ % (name, chosen_template))
|
||||
|
||||
def exp_create(db_name, demo, lang, user_password='admin'):
|
||||
|
@ -96,7 +99,7 @@ def exp_create(db_name, demo, lang, user_password='admin'):
|
|||
|
||||
_logger.info('CREATE DATABASE %s', db_name.lower())
|
||||
create_thread = threading.Thread(target=_initialize_db,
|
||||
args=(id, db_name, demo, lang, user_password))
|
||||
args=(id, db_name, demo, lang, user_password))
|
||||
create_thread.start()
|
||||
self_actions[id]['thread'] = create_thread
|
||||
return id
|
||||
|
@ -121,14 +124,14 @@ def exp_duplicate_database(db_original_name, db_name):
|
|||
openerp.sql_db.close_db(db_original_name)
|
||||
db = openerp.sql_db.db_connect('postgres')
|
||||
with closing(db.cursor()) as cr:
|
||||
cr.autocommit(True) # avoid transaction block
|
||||
cr.autocommit(True) # avoid transaction block
|
||||
cr.execute("""CREATE DATABASE "%s" ENCODING 'unicode' TEMPLATE "%s" """ % (db_name, db_original_name))
|
||||
return True
|
||||
|
||||
def exp_get_progress(id):
|
||||
if self_actions[id]['thread'].isAlive():
|
||||
# return openerp.modules.init_progress[db_name]
|
||||
return min(self_actions[id].get('progress', 0),0.95), []
|
||||
return min(self_actions[id].get('progress', 0), 0.95), []
|
||||
else:
|
||||
clean = self_actions[id]['clean']
|
||||
if clean:
|
||||
|
@ -140,9 +143,9 @@ def exp_get_progress(id):
|
|||
self_actions.pop(id)
|
||||
return 1.0, users
|
||||
else:
|
||||
e = self_actions[id]['exception'] # TODO this seems wrong: actions[id]['traceback'] is set, but not 'exception'.
|
||||
self_actions.pop(id)
|
||||
raise Exception, e
|
||||
a = self_actions.pop(id)
|
||||
exc, tb = a['exception'], a['traceback']
|
||||
raise Exception, exc, tb
|
||||
|
||||
def exp_drop(db_name):
|
||||
if db_name not in exp_list(True):
|
||||
|
@ -152,18 +155,17 @@ def exp_drop(db_name):
|
|||
|
||||
db = openerp.sql_db.db_connect('postgres')
|
||||
with closing(db.cursor()) as cr:
|
||||
cr.autocommit(True) # avoid transaction block
|
||||
cr.autocommit(True) # avoid transaction block
|
||||
# Try to terminate all other connections that might prevent
|
||||
# dropping the database
|
||||
try:
|
||||
|
||||
# PostgreSQL 9.2 renamed pg_stat_activity.procpid to pid:
|
||||
# http://www.postgresql.org/docs/9.2/static/release-9-2.html#AEN110389
|
||||
pid_col = 'pid' if cr._cnx.server_version >= 90200 else 'procpid'
|
||||
|
||||
cr.execute("""SELECT pg_terminate_backend(%(pid_col)s)
|
||||
FROM pg_stat_activity
|
||||
WHERE datname = %%s AND
|
||||
WHERE datname = %%s AND
|
||||
%(pid_col)s != pg_backend_pid()""" % {'pid_col': pid_col},
|
||||
(db_name,))
|
||||
except Exception:
|
||||
|
@ -178,94 +180,140 @@ def exp_drop(db_name):
|
|||
_logger.info('DROP DB: %s', db_name)
|
||||
return True
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _set_pg_password_in_environment():
|
||||
def _set_pg_password_in_environment(func):
|
||||
""" On systems where pg_restore/pg_dump require an explicit
|
||||
password (i.e. when not connecting via unix sockets, and most
|
||||
importantly on Windows), it is necessary to pass the PG user
|
||||
password in the environment or in a special .pgpass file.
|
||||
|
||||
This context management method handles setting
|
||||
This decorator handles setting
|
||||
:envvar:`PGPASSWORD` if it is not already
|
||||
set, and removing it afterwards.
|
||||
|
||||
See also http://www.postgresql.org/docs/8.4/static/libpq-envars.html
|
||||
|
||||
|
||||
.. note:: This is not thread-safe, and should never be enabled for
|
||||
SaaS (giving SaaS users the super-admin password is not a good idea
|
||||
anyway)
|
||||
"""
|
||||
if os.environ.get('PGPASSWORD') or not openerp.tools.config['db_password']:
|
||||
yield
|
||||
else:
|
||||
os.environ['PGPASSWORD'] = openerp.tools.config['db_password']
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
del os.environ['PGPASSWORD']
|
||||
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
if os.environ.get('PGPASSWORD') or not openerp.tools.config['db_password']:
|
||||
return func(*args, **kwargs)
|
||||
else:
|
||||
os.environ['PGPASSWORD'] = openerp.tools.config['db_password']
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
finally:
|
||||
del os.environ['PGPASSWORD']
|
||||
return wrapper
|
||||
|
||||
def exp_dump(db_name):
|
||||
with _set_pg_password_in_environment():
|
||||
cmd = ['pg_dump', '--format=c', '--no-owner']
|
||||
with tempfile.TemporaryFile() as t:
|
||||
dump_db(db_name, t)
|
||||
t.seek(0)
|
||||
return t.read().encode('base64')
|
||||
|
||||
@_set_pg_password_in_environment
|
||||
def dump_db(db, stream):
|
||||
"""Dump database `db` into file-like object `stream`"""
|
||||
with openerp.tools.osutil.tempdir() as dump_dir:
|
||||
registry = openerp.modules.registry.RegistryManager.get(db)
|
||||
with registry.cursor() as cr:
|
||||
filestore = registry['ir.attachment']._filestore(cr, SUPERUSER_ID)
|
||||
if os.path.exists(filestore):
|
||||
shutil.copytree(filestore, os.path.join(dump_dir, 'filestore'))
|
||||
|
||||
dump_file = os.path.join(dump_dir, 'dump.sql')
|
||||
cmd = ['pg_dump', '--format=p', '--no-owner', '--file=' + dump_file]
|
||||
if openerp.tools.config['db_user']:
|
||||
cmd.append('--username=' + openerp.tools.config['db_user'])
|
||||
if openerp.tools.config['db_host']:
|
||||
cmd.append('--host=' + openerp.tools.config['db_host'])
|
||||
if openerp.tools.config['db_port']:
|
||||
cmd.append('--port=' + str(openerp.tools.config['db_port']))
|
||||
cmd.append(db_name)
|
||||
cmd.append(db)
|
||||
|
||||
stdin, stdout = openerp.tools.exec_pg_command_pipe(*tuple(cmd))
|
||||
stdin.close()
|
||||
data = stdout.read()
|
||||
res = stdout.close()
|
||||
if openerp.tools.exec_pg_command(*cmd):
|
||||
_logger.error('DUMP DB: %s failed! Please verify the configuration of the database '
|
||||
'password on the server. You may need to create a .pgpass file for '
|
||||
'authentication, or specify `db_password` in the server configuration '
|
||||
'file.', db)
|
||||
raise Exception("Couldn't dump database")
|
||||
|
||||
if not data or res:
|
||||
_logger.error(
|
||||
'DUMP DB: %s failed! Please verify the configuration of the database password on the server. '
|
||||
'You may need to create a .pgpass file for authentication, or specify `db_password` in the '
|
||||
'server configuration file.\n %s', db_name, data)
|
||||
raise Exception, "Couldn't dump database"
|
||||
_logger.info('DUMP DB successful: %s', db_name)
|
||||
openerp.tools.osutil.zip_dir(dump_dir, stream, include_dir=False)
|
||||
|
||||
return base64.encodestring(data)
|
||||
_logger.info('DUMP DB successful: %s', db)
|
||||
|
||||
def exp_restore(db_name, data):
|
||||
with _set_pg_password_in_environment():
|
||||
if exp_db_exist(db_name):
|
||||
_logger.warning('RESTORE DB: %s already exists', db_name)
|
||||
raise Exception, "Database already exists"
|
||||
def exp_restore(db_name, data, copy=False):
|
||||
data_file = tempfile.NamedTemporaryFile(delete=False)
|
||||
try:
|
||||
data_file.write(data.decode('base64'))
|
||||
data_file.close()
|
||||
restore_db(db_name, data_file.name, copy=copy)
|
||||
finally:
|
||||
os.unlink(data_file.name)
|
||||
return True
|
||||
|
||||
_create_empty_database(db_name)
|
||||
@_set_pg_password_in_environment
|
||||
def restore_db(db, dump_file, copy=False):
|
||||
assert isinstance(db, basestring)
|
||||
if exp_db_exist(db):
|
||||
_logger.warning('RESTORE DB: %s already exists', db)
|
||||
raise Exception("Database already exists")
|
||||
|
||||
cmd = ['pg_restore', '--no-owner']
|
||||
_create_empty_database(db)
|
||||
|
||||
filestore_path = None
|
||||
with openerp.tools.osutil.tempdir() as dump_dir:
|
||||
if zipfile.is_zipfile(dump_file):
|
||||
# v8 format
|
||||
with zipfile.ZipFile(dump_file, 'r') as z:
|
||||
# only extract known members!
|
||||
filestore = [m for m in z.namelist() if m.startswith('filestore/')]
|
||||
z.extractall(dump_dir, ['dump.sql'] + filestore)
|
||||
|
||||
if filestore:
|
||||
filestore_path = os.path.join(dump_dir, 'filestore')
|
||||
|
||||
pg_cmd = 'psql'
|
||||
pg_args = ['-q', '-f', os.path.join(dump_dir, 'dump.sql')]
|
||||
|
||||
else:
|
||||
# <= 7.0 format (raw pg_dump output)
|
||||
pg_cmd = 'pg_restore'
|
||||
pg_args = ['--no-owner', dump_file]
|
||||
|
||||
args = []
|
||||
if openerp.tools.config['db_user']:
|
||||
cmd.append('--username=' + openerp.tools.config['db_user'])
|
||||
args.append('--username=' + openerp.tools.config['db_user'])
|
||||
if openerp.tools.config['db_host']:
|
||||
cmd.append('--host=' + openerp.tools.config['db_host'])
|
||||
args.append('--host=' + openerp.tools.config['db_host'])
|
||||
if openerp.tools.config['db_port']:
|
||||
cmd.append('--port=' + str(openerp.tools.config['db_port']))
|
||||
cmd.append('--dbname=' + db_name)
|
||||
args2 = tuple(cmd)
|
||||
args.append('--port=' + str(openerp.tools.config['db_port']))
|
||||
args.append('--dbname=' + db)
|
||||
pg_args = args + pg_args
|
||||
|
||||
buf=base64.decodestring(data)
|
||||
if os.name == "nt":
|
||||
tmpfile = (os.environ['TMP'] or 'C:\\') + os.tmpnam()
|
||||
file(tmpfile, 'wb').write(buf)
|
||||
args2=list(args2)
|
||||
args2.append(tmpfile)
|
||||
args2=tuple(args2)
|
||||
stdin, stdout = openerp.tools.exec_pg_command_pipe(*args2)
|
||||
if not os.name == "nt":
|
||||
stdin.write(base64.decodestring(data))
|
||||
stdin.close()
|
||||
res = stdout.close()
|
||||
if res:
|
||||
raise Exception, "Couldn't restore database"
|
||||
_logger.info('RESTORE DB: %s', db_name)
|
||||
if openerp.tools.exec_pg_command(pg_cmd, *pg_args):
|
||||
raise Exception("Couldn't restore database")
|
||||
|
||||
return True
|
||||
registry = openerp.modules.registry.RegistryManager.new(db)
|
||||
with registry.cursor() as cr:
|
||||
if copy:
|
||||
# if it's a copy of a database, force generation of a new dbuuid
|
||||
registry['ir.config_parameter'].init(cr, force=True)
|
||||
if filestore_path:
|
||||
filestore_dest = registry['ir.attachment']._filestore(cr, SUPERUSER_ID)
|
||||
shutil.move(filestore_path, filestore_dest)
|
||||
|
||||
if openerp.tools.config['unaccent']:
|
||||
try:
|
||||
with cr.savepoint():
|
||||
cr.execute("CREATE EXTENSION unaccent")
|
||||
except psycopg2.Error:
|
||||
pass
|
||||
|
||||
_logger.info('RESTORE DB: %s', db)
|
||||
|
||||
def exp_rename(old_name, new_name):
|
||||
openerp.modules.registry.RegistryManager.delete(old_name)
|
||||
|
@ -273,7 +321,7 @@ def exp_rename(old_name, new_name):
|
|||
|
||||
db = openerp.sql_db.db_connect('postgres')
|
||||
with closing(db.cursor()) as cr:
|
||||
cr.autocommit(True) # avoid transaction block
|
||||
cr.autocommit(True) # avoid transaction block
|
||||
try:
|
||||
cr.execute('ALTER DATABASE "%s" RENAME TO "%s"' % (old_name, new_name))
|
||||
_logger.info('RENAME DB: %s -> %s', old_name, new_name)
|
||||
|
@ -282,6 +330,7 @@ def exp_rename(old_name, new_name):
|
|||
raise Exception("Couldn't rename database %s to %s: %s" % (old_name, new_name, e))
|
||||
return True
|
||||
|
||||
@openerp.tools.mute_logger('openerp.sql_db')
|
||||
def exp_db_exist(db_name):
|
||||
## Not True: in fact, check if connection to database is possible. The database may exists
|
||||
return bool(openerp.sql_db.db_connect(db_name))
|
||||
|
|
|
@ -148,7 +148,7 @@ class Cursor(object):
|
|||
def check(f):
|
||||
@wraps(f)
|
||||
def wrapper(self, *args, **kwargs):
|
||||
if self.__closed:
|
||||
if self._closed:
|
||||
msg = 'Unable to use a closed cursor.'
|
||||
if self.__closer:
|
||||
msg += ' It was closed at %s, line %s' % self.__closer
|
||||
|
@ -165,7 +165,7 @@ class Cursor(object):
|
|||
self.sql_log = _logger.isEnabledFor(logging.DEBUG)
|
||||
|
||||
self.sql_log_count = 0
|
||||
self.__closed = True # avoid the call of close() (by __del__) if an exception
|
||||
self._closed = True # avoid the call of close() (by __del__) if an exception
|
||||
# is raised by any of the following initialisations
|
||||
self._pool = pool
|
||||
self.dbname = dbname
|
||||
|
@ -180,7 +180,7 @@ class Cursor(object):
|
|||
self.__caller = frame_codeinfo(currentframe(),2)
|
||||
else:
|
||||
self.__caller = False
|
||||
self.__closed = False # real initialisation value
|
||||
self._closed = False # real initialisation value
|
||||
self.autocommit(False)
|
||||
self.__closer = False
|
||||
|
||||
|
@ -189,7 +189,7 @@ class Cursor(object):
|
|||
self.cache = {}
|
||||
|
||||
def __del__(self):
|
||||
if not self.__closed and not self._cnx.closed:
|
||||
if not self._closed and not self._cnx.closed:
|
||||
# Oops. 'self' has not been closed explicitly.
|
||||
# The cursor will be deleted by the garbage collector,
|
||||
# but the database connection is not put back into the connection
|
||||
|
@ -302,7 +302,7 @@ class Cursor(object):
|
|||
# collected as fast as they should). The problem is probably due in
|
||||
# part because browse records keep a reference to the cursor.
|
||||
del self._obj
|
||||
self.__closed = True
|
||||
self._closed = True
|
||||
|
||||
# Clean the underlying connection.
|
||||
self._cnx.rollback()
|
||||
|
|
|
@ -15,9 +15,10 @@ import time
|
|||
import unittest2
|
||||
import urllib2
|
||||
import xmlrpclib
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import werkzeug
|
||||
|
||||
import openerp
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
@ -48,10 +49,9 @@ def acquire_test_cursor(session_id):
|
|||
cr._test_lock.acquire()
|
||||
return cr
|
||||
|
||||
def release_test_cursor(session_id):
|
||||
def release_test_cursor(cr):
|
||||
if openerp.tools.config['test_enable']:
|
||||
cr = HTTP_SESSION.get(session_id)
|
||||
if cr:
|
||||
if hasattr(cr, '_test_lock'):
|
||||
cr._test_lock.release()
|
||||
return True
|
||||
return False
|
||||
|
@ -212,7 +212,8 @@ class HttpCase(TransactionCase):
|
|||
# OSError, and no errno/strerror/filename, only a pair of
|
||||
# unnamed arguments (matching errno and strerror)
|
||||
err, _ = e.args
|
||||
if err == errno.EINTR: continue
|
||||
if err == errno.EINTR:
|
||||
continue
|
||||
raise
|
||||
|
||||
if ready:
|
||||
|
@ -224,24 +225,24 @@ class HttpCase(TransactionCase):
|
|||
# process lines
|
||||
if '\n' in buf:
|
||||
line, buf = buf.split('\n', 1)
|
||||
|
||||
line = str(line)
|
||||
if 'CoreText' in line:
|
||||
continue
|
||||
|
||||
# relay everything from console.log, even 'ok' or 'error...' lines
|
||||
_logger.info("phantomjs: %s", line)
|
||||
|
||||
if line == "ok":
|
||||
break
|
||||
if line.startswith("error"):
|
||||
line_ = line[6:]
|
||||
try: line_ = json.loads(line_)
|
||||
except ValueError: pass
|
||||
# when error occurs the execution stack may be sent as as JSON
|
||||
try:
|
||||
line_ = json.loads(line_)
|
||||
except ValueError:
|
||||
pass
|
||||
self.fail(line_ or "phantomjs test failed")
|
||||
|
||||
try: line = json.loads(line)
|
||||
except ValueError: pass
|
||||
_logger.info("phantomjs: %s", line)
|
||||
|
||||
def phantom_run(self, cmd, timeout):
|
||||
_logger.debug('executing `%s`', ' '.join(cmd))
|
||||
_logger.info('phantom_run executing %s', ' '.join(cmd))
|
||||
try:
|
||||
phantom = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
except OSError:
|
||||
|
@ -252,6 +253,7 @@ class HttpCase(TransactionCase):
|
|||
# kill phantomjs if phantom.exit() wasn't called in the test
|
||||
if phantom.poll() is None:
|
||||
phantom.terminate()
|
||||
_logger.info("phantom_run execution finished")
|
||||
|
||||
def phantom_jsfile(self, jsfile, timeout=30, **kw):
|
||||
options = {
|
||||
|
|
|
@ -113,20 +113,22 @@ function PhantomTest() {
|
|||
console.log('loaded', url, status);
|
||||
// process ready
|
||||
waitFor(function() {
|
||||
console.log("waiting for: calling page evaluate");
|
||||
console.log("PhantomTest.run: wait for condition: " + ready);
|
||||
return self.page.evaluate(function (ready) {
|
||||
console.log("waiting for", ready);
|
||||
var r = false;
|
||||
try {
|
||||
console.log("waiting for: page evaluating ", ready);
|
||||
console.log("page.evaluate eval expr:", ready);
|
||||
r = !!eval(ready);
|
||||
} catch(ex) { }
|
||||
console.log("waiting for: returning from page evaluate");
|
||||
} catch(ex) {
|
||||
}
|
||||
console.log("page.evaluate eval result:", r);
|
||||
return r;
|
||||
}, ready);
|
||||
// run test
|
||||
}, function() {
|
||||
console.log("PhantomTest.run: condition statified, executing: " + code);
|
||||
self.page.evaluate(function (code) { return eval(code); }, code);
|
||||
console.log("PhantomTest.run: execution launched, waiting for console.log('ok')...");
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -32,7 +32,7 @@ from random import randint
|
|||
# Image resizing
|
||||
# ----------------------------------------
|
||||
|
||||
def image_resize_image(base64_source, size=(1024, 1024), encoding='base64', filetype='PNG', avoid_if_small=False):
|
||||
def image_resize_image(base64_source, size=(1024, 1024), encoding='base64', filetype=None, avoid_if_small=False):
|
||||
""" Function to resize an image. The image will be resized to the given
|
||||
size, while keeping the aspect ratios, and holes in the image will be
|
||||
filled with transparent background. The image will not be stretched if
|
||||
|
@ -58,7 +58,8 @@ def image_resize_image(base64_source, size=(1024, 1024), encoding='base64', file
|
|||
height mean an automatically computed value based respectivelly
|
||||
on height or width of the source image.
|
||||
:param encoding: the output encoding
|
||||
:param filetype: the output filetype
|
||||
:param filetype: the output filetype, by default the source image's
|
||||
:type filetype: str, any PIL image format (supported for creation)
|
||||
:param avoid_if_small: do not resize if image height and width
|
||||
are smaller than the expected size.
|
||||
"""
|
||||
|
@ -68,6 +69,8 @@ def image_resize_image(base64_source, size=(1024, 1024), encoding='base64', file
|
|||
return base64_source
|
||||
image_stream = StringIO.StringIO(base64_source.decode(encoding))
|
||||
image = Image.open(image_stream)
|
||||
# store filetype here, as Image.new below will lose image.format
|
||||
filetype = filetype or image.format
|
||||
|
||||
asked_width, asked_height = size
|
||||
if asked_width is None:
|
||||
|
@ -95,21 +98,21 @@ def image_resize_image(base64_source, size=(1024, 1024), encoding='base64', file
|
|||
image.save(background_stream, filetype)
|
||||
return background_stream.getvalue().encode(encoding)
|
||||
|
||||
def image_resize_image_big(base64_source, size=(1204, 1204), encoding='base64', filetype='PNG', avoid_if_small=True):
|
||||
def image_resize_image_big(base64_source, size=(1204, 1024), encoding='base64', filetype=None, avoid_if_small=True):
|
||||
""" Wrapper on image_resize_image, to resize images larger than the standard
|
||||
'big' image size: 1024x1024px.
|
||||
:param size, encoding, filetype, avoid_if_small: refer to image_resize_image
|
||||
"""
|
||||
return image_resize_image(base64_source, size, encoding, filetype, avoid_if_small)
|
||||
|
||||
def image_resize_image_medium(base64_source, size=(128, 128), encoding='base64', filetype='PNG', avoid_if_small=False):
|
||||
def image_resize_image_medium(base64_source, size=(128, 128), encoding='base64', filetype=None, avoid_if_small=False):
|
||||
""" Wrapper on image_resize_image, to resize to the standard 'medium'
|
||||
image size: 180x180.
|
||||
:param size, encoding, filetype, avoid_if_small: refer to image_resize_image
|
||||
"""
|
||||
return image_resize_image(base64_source, size, encoding, filetype, avoid_if_small)
|
||||
|
||||
def image_resize_image_small(base64_source, size=(64, 64), encoding='base64', filetype='PNG', avoid_if_small=False):
|
||||
def image_resize_image_small(base64_source, size=(64, 64), encoding='base64', filetype=None, avoid_if_small=False):
|
||||
""" Wrapper on image_resize_image, to resize to the standard 'small' image
|
||||
size: 50x50.
|
||||
:param size, encoding, filetype, avoid_if_small: refer to image_resize_image
|
||||
|
|
|
@ -23,8 +23,12 @@
|
|||
Some functions related to the os and os.path module
|
||||
"""
|
||||
|
||||
from contextlib import contextmanager
|
||||
import os
|
||||
from os.path import join as opj
|
||||
import shutil
|
||||
import tempfile
|
||||
import zipfile
|
||||
|
||||
if os.name == 'nt':
|
||||
import ctypes
|
||||
|
@ -61,6 +65,30 @@ def walksymlinks(top, topdown=True, onerror=None):
|
|||
if not topdown:
|
||||
yield dirpath, dirnames, filenames
|
||||
|
||||
@contextmanager
|
||||
def tempdir():
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
try:
|
||||
yield tmpdir
|
||||
finally:
|
||||
shutil.rmtree(tmpdir)
|
||||
|
||||
def zip_dir(path, stream, include_dir=True): # TODO add ignore list
|
||||
path = os.path.normpath(path)
|
||||
len_prefix = len(os.path.dirname(path)) if include_dir else len(path)
|
||||
if len_prefix:
|
||||
len_prefix += 1
|
||||
|
||||
with zipfile.ZipFile(stream, 'w', compression=zipfile.ZIP_DEFLATED, allowZip64=True) as zipf:
|
||||
for dirpath, dirnames, filenames in os.walk(path):
|
||||
for fname in filenames:
|
||||
bname, ext = os.path.splitext(fname)
|
||||
ext = ext or bname
|
||||
if ext not in ['.pyc', '.pyo', '.swp', '.DS_Store']:
|
||||
path = os.path.normpath(os.path.join(dirpath, fname))
|
||||
if os.path.isfile(path):
|
||||
zipf.write(path, path[len_prefix:])
|
||||
|
||||
|
||||
if os.name != 'nt':
|
||||
getppid = os.getppid
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
# Copyright (C) 2004-2012 OpenERP s.a. (<http://www.openerp.com>).
|
||||
# Copyright (C) 2004-2014 OpenERP s.a. (<http://www.openerp.com>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
|
@ -225,9 +225,14 @@ def safe_eval(expr, globals_dict=None, locals_dict=None, mode="eval", nocopy=Fal
|
|||
'False': False,
|
||||
'None': None,
|
||||
'str': str,
|
||||
'unicode': unicode,
|
||||
'globals': locals,
|
||||
'locals': locals,
|
||||
'bool': bool,
|
||||
'int': int,
|
||||
'float': float,
|
||||
'long': long,
|
||||
'enumerate': enumerate,
|
||||
'dict': dict,
|
||||
'list': list,
|
||||
'tuple': tuple,
|
||||
|
@ -235,15 +240,23 @@ def safe_eval(expr, globals_dict=None, locals_dict=None, mode="eval", nocopy=Fal
|
|||
'abs': abs,
|
||||
'min': min,
|
||||
'max': max,
|
||||
'sum': sum,
|
||||
'reduce': reduce,
|
||||
'filter': filter,
|
||||
'round': round,
|
||||
'len': len,
|
||||
'set': set,
|
||||
'repr': repr,
|
||||
'int': int,
|
||||
'float': float,
|
||||
'set': set,
|
||||
'all': all,
|
||||
'any': any,
|
||||
'ord': ord,
|
||||
'chr': chr,
|
||||
'cmp': cmp,
|
||||
'divmod': divmod,
|
||||
'isinstance': isinstance,
|
||||
'range': range,
|
||||
'xrange': xrange,
|
||||
'zip': zip,
|
||||
}
|
||||
)
|
||||
if locals_builtins:
|
||||
|
|
Loading…
Reference in New Issue