diff --git a/openerp/addons/base/i18n/ab.po b/openerp/addons/base/i18n/ab.po index 62a7450b7ac..0d6e5aaef89 100644 --- a/openerp/addons/base/i18n/ab.po +++ b/openerp/addons/base/i18n/ab.po @@ -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 diff --git a/openerp/addons/base/i18n/af.po b/openerp/addons/base/i18n/af.po index ea79c793e40..69e8103fdbd 100644 --- a/openerp/addons/base/i18n/af.po +++ b/openerp/addons/base/i18n/af.po @@ -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 diff --git a/openerp/addons/base/i18n/am.po b/openerp/addons/base/i18n/am.po index a787d963761..fca6a2b148c 100644 --- a/openerp/addons/base/i18n/am.po +++ b/openerp/addons/base/i18n/am.po @@ -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 diff --git a/openerp/addons/base/i18n/ar.po b/openerp/addons/base/i18n/ar.po index 443c5f9b027..4e9c2e2b4bd 100644 --- a/openerp/addons/base/i18n/ar.po +++ b/openerp/addons/base/i18n/ar.po @@ -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 diff --git a/openerp/addons/base/i18n/bg.po b/openerp/addons/base/i18n/bg.po index 41b86bf9de9..0f4c299e8e9 100644 --- a/openerp/addons/base/i18n/bg.po +++ b/openerp/addons/base/i18n/bg.po @@ -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 diff --git a/openerp/addons/base/i18n/bn.po b/openerp/addons/base/i18n/bn.po index 56b2bc70a5b..6f848c3918a 100644 --- a/openerp/addons/base/i18n/bn.po +++ b/openerp/addons/base/i18n/bn.po @@ -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 diff --git a/openerp/addons/base/i18n/bs.po b/openerp/addons/base/i18n/bs.po index c343f0c527a..ba7d39d340f 100644 --- a/openerp/addons/base/i18n/bs.po +++ b/openerp/addons/base/i18n/bs.po @@ -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 diff --git a/openerp/addons/base/i18n/ca.po b/openerp/addons/base/i18n/ca.po index 91d10c6e60a..c51a2a0e546 100644 --- a/openerp/addons/base/i18n/ca.po +++ b/openerp/addons/base/i18n/ca.po @@ -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 diff --git a/openerp/addons/base/i18n/cs.po b/openerp/addons/base/i18n/cs.po index 2f66120919d..3c18cc5bc07 100644 --- a/openerp/addons/base/i18n/cs.po +++ b/openerp/addons/base/i18n/cs.po @@ -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 diff --git a/openerp/addons/base/i18n/da.po b/openerp/addons/base/i18n/da.po index bf43d7574a4..ad2f9c7542b 100644 --- a/openerp/addons/base/i18n/da.po +++ b/openerp/addons/base/i18n/da.po @@ -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 diff --git a/openerp/addons/base/i18n/de.po b/openerp/addons/base/i18n/de.po index 2e662191b9a..dcf9b51367a 100644 --- a/openerp/addons/base/i18n/de.po +++ b/openerp/addons/base/i18n/de.po @@ -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 diff --git a/openerp/addons/base/i18n/el.po b/openerp/addons/base/i18n/el.po index 1d0c013b663..d0144cde2fa 100644 --- a/openerp/addons/base/i18n/el.po +++ b/openerp/addons/base/i18n/el.po @@ -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" diff --git a/openerp/addons/base/i18n/en_GB.po b/openerp/addons/base/i18n/en_GB.po index 91729acef84..4577c3be9d6 100644 --- a/openerp/addons/base/i18n/en_GB.po +++ b/openerp/addons/base/i18n/en_GB.po @@ -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 diff --git a/openerp/addons/base/i18n/es.po b/openerp/addons/base/i18n/es.po index 933fcfc62ab..161143cb35c 100644 --- a/openerp/addons/base/i18n/es.po +++ b/openerp/addons/base/i18n/es.po @@ -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 diff --git a/openerp/addons/base/i18n/es_AR.po b/openerp/addons/base/i18n/es_AR.po index 2ba523bbae8..8316f687607 100644 --- a/openerp/addons/base/i18n/es_AR.po +++ b/openerp/addons/base/i18n/es_AR.po @@ -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 diff --git a/openerp/addons/base/i18n/es_BO.po b/openerp/addons/base/i18n/es_BO.po index 2a5ec16a919..090125e661b 100644 --- a/openerp/addons/base/i18n/es_BO.po +++ b/openerp/addons/base/i18n/es_BO.po @@ -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 diff --git a/openerp/addons/base/i18n/es_CL.po b/openerp/addons/base/i18n/es_CL.po index c7af368f07c..0df508399c7 100644 --- a/openerp/addons/base/i18n/es_CL.po +++ b/openerp/addons/base/i18n/es_CL.po @@ -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 diff --git a/openerp/addons/base/i18n/es_CR.po b/openerp/addons/base/i18n/es_CR.po index 20829b9b9ba..56f664e3259 100644 --- a/openerp/addons/base/i18n/es_CR.po +++ b/openerp/addons/base/i18n/es_CR.po @@ -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 diff --git a/openerp/addons/base/i18n/es_DO.po b/openerp/addons/base/i18n/es_DO.po index 715f2b3dd7d..c93d78cbb59 100644 --- a/openerp/addons/base/i18n/es_DO.po +++ b/openerp/addons/base/i18n/es_DO.po @@ -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 diff --git a/openerp/addons/base/i18n/es_EC.po b/openerp/addons/base/i18n/es_EC.po index acd1e756205..11977f93058 100644 --- a/openerp/addons/base/i18n/es_EC.po +++ b/openerp/addons/base/i18n/es_EC.po @@ -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 diff --git a/openerp/addons/base/i18n/es_MX.po b/openerp/addons/base/i18n/es_MX.po index b89ada14216..e8919023269 100644 --- a/openerp/addons/base/i18n/es_MX.po +++ b/openerp/addons/base/i18n/es_MX.po @@ -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 diff --git a/openerp/addons/base/i18n/es_PE.po b/openerp/addons/base/i18n/es_PE.po index b7303b7cbb6..d6c439413be 100644 --- a/openerp/addons/base/i18n/es_PE.po +++ b/openerp/addons/base/i18n/es_PE.po @@ -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 diff --git a/openerp/addons/base/i18n/es_VE.po b/openerp/addons/base/i18n/es_VE.po index a19f76163b0..ff2410c4b72 100644 --- a/openerp/addons/base/i18n/es_VE.po +++ b/openerp/addons/base/i18n/es_VE.po @@ -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 diff --git a/openerp/addons/base/i18n/et.po b/openerp/addons/base/i18n/et.po index 7900ab0471e..0e16cb4c3be 100644 --- a/openerp/addons/base/i18n/et.po +++ b/openerp/addons/base/i18n/et.po @@ -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 diff --git a/openerp/addons/base/i18n/eu.po b/openerp/addons/base/i18n/eu.po index b0631ea17c5..54a890ed5a6 100644 --- a/openerp/addons/base/i18n/eu.po +++ b/openerp/addons/base/i18n/eu.po @@ -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 diff --git a/openerp/addons/base/i18n/fa.po b/openerp/addons/base/i18n/fa.po index fdaface74b7..51409288f56 100644 --- a/openerp/addons/base/i18n/fa.po +++ b/openerp/addons/base/i18n/fa.po @@ -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" diff --git a/openerp/addons/base/i18n/fa_AF.po b/openerp/addons/base/i18n/fa_AF.po index 308ab0e88a4..808dd0f67fe 100644 --- a/openerp/addons/base/i18n/fa_AF.po +++ b/openerp/addons/base/i18n/fa_AF.po @@ -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 diff --git a/openerp/addons/base/i18n/fi.po b/openerp/addons/base/i18n/fi.po index 98cfd3020ec..86b4287d829 100644 --- a/openerp/addons/base/i18n/fi.po +++ b/openerp/addons/base/i18n/fi.po @@ -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 diff --git a/openerp/addons/base/i18n/fr.po b/openerp/addons/base/i18n/fr.po index 0067da733b4..1470da528cd 100644 --- a/openerp/addons/base/i18n/fr.po +++ b/openerp/addons/base/i18n/fr.po @@ -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 diff --git a/openerp/addons/base/i18n/fr_CA.po b/openerp/addons/base/i18n/fr_CA.po index 67772af5635..0d23b54db1a 100644 --- a/openerp/addons/base/i18n/fr_CA.po +++ b/openerp/addons/base/i18n/fr_CA.po @@ -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 diff --git a/openerp/addons/base/i18n/gl.po b/openerp/addons/base/i18n/gl.po index b1a78185e33..d0ea8b19736 100644 --- a/openerp/addons/base/i18n/gl.po +++ b/openerp/addons/base/i18n/gl.po @@ -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 diff --git a/openerp/addons/base/i18n/gu.po b/openerp/addons/base/i18n/gu.po index 33bba236795..32d105932c4 100644 --- a/openerp/addons/base/i18n/gu.po +++ b/openerp/addons/base/i18n/gu.po @@ -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 diff --git a/openerp/addons/base/i18n/he.po b/openerp/addons/base/i18n/he.po index 242b49c86d7..829f5f0dd6e 100644 --- a/openerp/addons/base/i18n/he.po +++ b/openerp/addons/base/i18n/he.po @@ -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 diff --git a/openerp/addons/base/i18n/hi.po b/openerp/addons/base/i18n/hi.po index 1b8a36beada..cb6b74c5d76 100644 --- a/openerp/addons/base/i18n/hi.po +++ b/openerp/addons/base/i18n/hi.po @@ -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 diff --git a/openerp/addons/base/i18n/hr.po b/openerp/addons/base/i18n/hr.po index 7e0b440a29d..502eb7017f4 100644 --- a/openerp/addons/base/i18n/hr.po +++ b/openerp/addons/base/i18n/hr.po @@ -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 diff --git a/openerp/addons/base/i18n/hu.po b/openerp/addons/base/i18n/hu.po index a9b8c05473a..65678f0c39c 100644 --- a/openerp/addons/base/i18n/hu.po +++ b/openerp/addons/base/i18n/hu.po @@ -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 diff --git a/openerp/addons/base/i18n/hy.po b/openerp/addons/base/i18n/hy.po index 19df5b5a136..25c2c973edc 100644 --- a/openerp/addons/base/i18n/hy.po +++ b/openerp/addons/base/i18n/hy.po @@ -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 diff --git a/openerp/addons/base/i18n/id.po b/openerp/addons/base/i18n/id.po index 63fe93c7cd4..3e1d92922eb 100644 --- a/openerp/addons/base/i18n/id.po +++ b/openerp/addons/base/i18n/id.po @@ -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 diff --git a/openerp/addons/base/i18n/is.po b/openerp/addons/base/i18n/is.po index 111dd7f87e5..979703590c3 100644 --- a/openerp/addons/base/i18n/is.po +++ b/openerp/addons/base/i18n/is.po @@ -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 diff --git a/openerp/addons/base/i18n/it.po b/openerp/addons/base/i18n/it.po index ab827533fa3..b42ad24c4d9 100644 --- a/openerp/addons/base/i18n/it.po +++ b/openerp/addons/base/i18n/it.po @@ -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 diff --git a/openerp/addons/base/i18n/ja.po b/openerp/addons/base/i18n/ja.po index 72eae935dcb..496096243a8 100644 --- a/openerp/addons/base/i18n/ja.po +++ b/openerp/addons/base/i18n/ja.po @@ -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 diff --git a/openerp/addons/base/i18n/ka.po b/openerp/addons/base/i18n/ka.po index 5049f475f6d..8ded3adc823 100644 --- a/openerp/addons/base/i18n/ka.po +++ b/openerp/addons/base/i18n/ka.po @@ -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 diff --git a/openerp/addons/base/i18n/kk.po b/openerp/addons/base/i18n/kk.po index 56144fd0745..1f79cb3bea8 100644 --- a/openerp/addons/base/i18n/kk.po +++ b/openerp/addons/base/i18n/kk.po @@ -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 diff --git a/openerp/addons/base/i18n/ko.po b/openerp/addons/base/i18n/ko.po index 3bda6b5d13a..59699d475f1 100644 --- a/openerp/addons/base/i18n/ko.po +++ b/openerp/addons/base/i18n/ko.po @@ -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 diff --git a/openerp/addons/base/i18n/lt.po b/openerp/addons/base/i18n/lt.po index e362c97bf07..255c8cca12c 100644 --- a/openerp/addons/base/i18n/lt.po +++ b/openerp/addons/base/i18n/lt.po @@ -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 diff --git a/openerp/addons/base/i18n/lv.po b/openerp/addons/base/i18n/lv.po index 7c00e4a6466..11ab8a66389 100644 --- a/openerp/addons/base/i18n/lv.po +++ b/openerp/addons/base/i18n/lv.po @@ -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 diff --git a/openerp/addons/base/i18n/mk.po b/openerp/addons/base/i18n/mk.po index 59b8e0cec98..16ef3f6b681 100644 --- a/openerp/addons/base/i18n/mk.po +++ b/openerp/addons/base/i18n/mk.po @@ -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 diff --git a/openerp/addons/base/i18n/mn.po b/openerp/addons/base/i18n/mn.po index db754c219dd..c6aadd7f268 100644 --- a/openerp/addons/base/i18n/mn.po +++ b/openerp/addons/base/i18n/mn.po @@ -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 diff --git a/openerp/addons/base/i18n/nb.po b/openerp/addons/base/i18n/nb.po index e06ad54485e..4cc42eafc50 100644 --- a/openerp/addons/base/i18n/nb.po +++ b/openerp/addons/base/i18n/nb.po @@ -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 diff --git a/openerp/addons/base/i18n/nl.po b/openerp/addons/base/i18n/nl.po index b606faeffad..1a075753bd5 100644 --- a/openerp/addons/base/i18n/nl.po +++ b/openerp/addons/base/i18n/nl.po @@ -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 diff --git a/openerp/addons/base/i18n/nl_BE.po b/openerp/addons/base/i18n/nl_BE.po index 7e7f119b9db..650b53002a3 100644 --- a/openerp/addons/base/i18n/nl_BE.po +++ b/openerp/addons/base/i18n/nl_BE.po @@ -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 diff --git a/openerp/addons/base/i18n/pl.po b/openerp/addons/base/i18n/pl.po index 5e86abf9fa6..b98dcfd3e61 100644 --- a/openerp/addons/base/i18n/pl.po +++ b/openerp/addons/base/i18n/pl.po @@ -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 diff --git a/openerp/addons/base/i18n/pt.po b/openerp/addons/base/i18n/pt.po index 6be215090c5..40be88b6a5b 100644 --- a/openerp/addons/base/i18n/pt.po +++ b/openerp/addons/base/i18n/pt.po @@ -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 diff --git a/openerp/addons/base/i18n/pt_BR.po b/openerp/addons/base/i18n/pt_BR.po index 2162fc2c0a2..dd733b834d7 100644 --- a/openerp/addons/base/i18n/pt_BR.po +++ b/openerp/addons/base/i18n/pt_BR.po @@ -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 diff --git a/openerp/addons/base/i18n/ro.po b/openerp/addons/base/i18n/ro.po index 180a8a62e14..2eef4d852ee 100644 --- a/openerp/addons/base/i18n/ro.po +++ b/openerp/addons/base/i18n/ro.po @@ -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 diff --git a/openerp/addons/base/i18n/ru.po b/openerp/addons/base/i18n/ru.po index dcbe8b5720a..36b5686a172 100644 --- a/openerp/addons/base/i18n/ru.po +++ b/openerp/addons/base/i18n/ru.po @@ -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 diff --git a/openerp/addons/base/i18n/sk.po b/openerp/addons/base/i18n/sk.po index 8ec906cc533..fb2f429717c 100644 --- a/openerp/addons/base/i18n/sk.po +++ b/openerp/addons/base/i18n/sk.po @@ -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 diff --git a/openerp/addons/base/i18n/sl.po b/openerp/addons/base/i18n/sl.po index 397a8f49a89..b8c16a387b2 100644 --- a/openerp/addons/base/i18n/sl.po +++ b/openerp/addons/base/i18n/sl.po @@ -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 diff --git a/openerp/addons/base/i18n/sq.po b/openerp/addons/base/i18n/sq.po index 301ea83076d..d2e13a78447 100644 --- a/openerp/addons/base/i18n/sq.po +++ b/openerp/addons/base/i18n/sq.po @@ -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 diff --git a/openerp/addons/base/i18n/sr.po b/openerp/addons/base/i18n/sr.po index c489716cfb9..418010621fc 100644 --- a/openerp/addons/base/i18n/sr.po +++ b/openerp/addons/base/i18n/sr.po @@ -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 diff --git a/openerp/addons/base/i18n/sr@latin.po b/openerp/addons/base/i18n/sr@latin.po index e5d812f4805..6574959fd41 100644 --- a/openerp/addons/base/i18n/sr@latin.po +++ b/openerp/addons/base/i18n/sr@latin.po @@ -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 diff --git a/openerp/addons/base/i18n/sv.po b/openerp/addons/base/i18n/sv.po index 5982af2691f..06b1a9d5ba7 100644 --- a/openerp/addons/base/i18n/sv.po +++ b/openerp/addons/base/i18n/sv.po @@ -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 diff --git a/openerp/addons/base/i18n/th.po b/openerp/addons/base/i18n/th.po index f9f8ca739ca..560d3e03eef 100644 --- a/openerp/addons/base/i18n/th.po +++ b/openerp/addons/base/i18n/th.po @@ -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 diff --git a/openerp/addons/base/i18n/tlh.po b/openerp/addons/base/i18n/tlh.po index d2f6d3bf758..9ac32871bc4 100644 --- a/openerp/addons/base/i18n/tlh.po +++ b/openerp/addons/base/i18n/tlh.po @@ -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 diff --git a/openerp/addons/base/i18n/tr.po b/openerp/addons/base/i18n/tr.po index 2a24b4abc2e..eea736aa946 100644 --- a/openerp/addons/base/i18n/tr.po +++ b/openerp/addons/base/i18n/tr.po @@ -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 diff --git a/openerp/addons/base/i18n/uk.po b/openerp/addons/base/i18n/uk.po index cdd9ab74f32..7065c8cf42b 100644 --- a/openerp/addons/base/i18n/uk.po +++ b/openerp/addons/base/i18n/uk.po @@ -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 diff --git a/openerp/addons/base/i18n/ur.po b/openerp/addons/base/i18n/ur.po index 617dddd1f43..0b9702b1f6d 100644 --- a/openerp/addons/base/i18n/ur.po +++ b/openerp/addons/base/i18n/ur.po @@ -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 diff --git a/openerp/addons/base/i18n/vi.po b/openerp/addons/base/i18n/vi.po index 8d0606f4987..c6a970ba990 100644 --- a/openerp/addons/base/i18n/vi.po +++ b/openerp/addons/base/i18n/vi.po @@ -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 diff --git a/openerp/addons/base/i18n/zh_CN.po b/openerp/addons/base/i18n/zh_CN.po index f8175c7974c..f9ff9274e58 100644 --- a/openerp/addons/base/i18n/zh_CN.po +++ b/openerp/addons/base/i18n/zh_CN.po @@ -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 diff --git a/openerp/addons/base/i18n/zh_HK.po b/openerp/addons/base/i18n/zh_HK.po index 05261c1e0bc..852bbaa62c1 100644 --- a/openerp/addons/base/i18n/zh_HK.po +++ b/openerp/addons/base/i18n/zh_HK.po @@ -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 diff --git a/openerp/addons/base/i18n/zh_TW.po b/openerp/addons/base/i18n/zh_TW.po index a57c92f2c2b..4d96c33f880 100644 --- a/openerp/addons/base/i18n/zh_TW.po +++ b/openerp/addons/base/i18n/zh_TW.po @@ -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 diff --git a/openerp/addons/base/ir/ir_http.py b/openerp/addons/base/ir/ir_http.py index ae04dcf5853..62ecd605223 100644 --- a/openerp/addons/base/ir/ir_http.py +++ b/openerp/addons/base/ir/ir_http.py @@ -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 diff --git a/openerp/addons/base/ir/ir_model.py b/openerp/addons/base/ir/ir_model.py index d119c0344b4..76166ee92e0 100644 --- a/openerp/addons/base/ir/ir_model.py +++ b/openerp/addons/base/ir/ir_model.py @@ -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': diff --git a/openerp/addons/base/ir/ir_qweb.py b/openerp/addons/base/ir/ir_qweb.py index 590ce90188f..0b3bf91dc1f 100644 --- a/openerp/addons/base/ir/ir_qweb.py +++ b/openerp/addons/base/ir/ir_qweb.py @@ -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): diff --git a/openerp/addons/base/ir/ir_ui_view.py b/openerp/addons/base/ir/ir_ui_view.py index d4655bf8b9c..360c5e37b31 100644 --- a/openerp/addons/base/ir/ir_ui_view.py +++ b/openerp/addons/base/ir/ir_ui_view.py @@ -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 diff --git a/openerp/addons/base/res/res_company.py b/openerp/addons/base/res/res_company.py index ae83aa4f2d1..64ab5676ca2 100644 --- a/openerp/addons/base/res/res_company.py +++ b/openerp/addons/base/res/res_company.py @@ -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): """ diff --git a/openerp/addons/base/res/res_partner.py b/openerp/addons/base/res/res_partner.py index e9573088680..46f0218e21f 100644 --- a/openerp/addons/base/res/res_partner.py +++ b/openerp/addons/base/res/res_partner.py @@ -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) diff --git a/openerp/addons/base/res/res_users.py b/openerp/addons/base/res/res_users.py index b2aba694078..1668cbda3c5 100644 --- a/openerp/addons/base/res/res_users.py +++ b/openerp/addons/base/res/res_users.py @@ -3,7 +3,7 @@ # # OpenERP, Open Source Management Solution # Copyright (C) 2004-2009 Tiny SPRL (). -# Copyright (C) 2010-2013 OpenERP s.a. (). +# Copyright (C) 2010-2014 OpenERP s.a. (). # # 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): """ diff --git a/openerp/addons/base/res/res_users_view.xml b/openerp/addons/base/res/res_users_view.xml index ba603c7a0f0..8a3f372af7a 100644 --- a/openerp/addons/base/res/res_users_view.xml +++ b/openerp/addons/base/res/res_users_view.xml @@ -282,7 +282,7 @@ - diff --git a/openerp/addons/base/tests/test_base.py b/openerp/addons/base/tests/test_base.py index 263cb0adf23..bbc5b21d1fb 100644 --- a/openerp/addons/base/tests/test_base.py +++ b/openerp/addons/base/tests/test_base.py @@ -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): diff --git a/openerp/addons/base/tests/test_fields.py b/openerp/addons/base/tests/test_fields.py index 47ffcd2f5b1..ce34bceb014 100644 --- a/openerp/addons/base/tests/test_fields.py +++ b/openerp/addons/base/tests/test_fields.py @@ -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 = """

Oops this should maybe be sanitized +% if object.some_field and not object.oriented: + + % if object.other_field: + + ${object.mako_thing} + + % endif + +%if object.dummy_field: +

Youpie

+%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('
+
', partner.comment, 'Error in HTML field: content does not seem to have been sanitized despise sanitize=True') + self.assertIn('', partner.comment, 'Error in HTML field: content does not seem to have been sanitized despise sanitize=True') + + self.partner._columns = old_columns diff --git a/openerp/addons/base/tests/test_ir_sequence.py b/openerp/addons/base/tests/test_ir_sequence.py index bc428d7f647..e925fffa3b1 100644 --- a/openerp/addons/base/tests/test_ir_sequence.py +++ b/openerp/addons/base/tests/test_ir_sequence.py @@ -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() diff --git a/openerp/addons/base/tests/test_qweb.py b/openerp/addons/base/tests/test_qweb.py index 71beaab605a..ca1503a0c24 100644 --- a/openerp/addons/base/tests/test_qweb.py +++ b/openerp/addons/base/tests/test_qweb.py @@ -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') diff --git a/openerp/addons/base/tests/test_views.py b/openerp/addons/base/tests/test_views.py index 6780996ed6e..06030a84792 100644 --- a/openerp/addons/base/tests/test_views.py +++ b/openerp/addons/base/tests/test_views.py @@ -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': """ + + """ + }) + id2 = Views.create(self.cr, self.uid, { + 'name': "Extension", + 'type': 'qweb', + 'inherit_id': id, + 'arch': """ + + bar + + """ + }) + + 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': """ + + """, + }) + + 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': """ + + + + """ + }) + id2 = Views.create(self.cr, self.uid, { + 'name': "Extension", + 'type': 'qweb', + 'inherit_id': id, + 'arch': """ + + bar + + """ + }) + + 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) - )), - '

' - '

Replacement data

' - '
' - '
' - '
') + ), + 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) - )), - '
' - '

Replacement data

' - '
' - '
' - '
') - + ), + E.form( + E.p("Replacement data"), + E.footer( + E.button(name="action_next", type="object", string="New button")), + string="Replacement title", version="7.0" + )) diff --git a/openerp/addons/test_converter/tests/test_html.py b/openerp/addons/test_converter/tests/test_html.py index 2f0896990ea..3ef6566e829 100644 --- a/openerp/addons/test_converter/tests/test_html.py +++ b/openerp/addons/test_converter/tests/test_html.py @@ -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): diff --git a/openerp/addons/test_impex/tests/test_export.py b/openerp/addons/test_impex/tests/test_export.py index 26219b36d3b..995d69471d6 100644 --- a/openerp/addons/test_impex/tests/test_export.py +++ b/openerp/addons/test_impex/tests/test_export.py @@ -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]]) diff --git a/openerp/http.py b/openerp/http.py index 1750478a836..4f6a6cdd86a 100644 --- a/openerp/http.py +++ b/openerp/http.py @@ -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(): diff --git a/openerp/modules/loading.py b/openerp/modules/loading.py index 7ca0a6785d6..681d8534c05 100644 --- a/openerp/modules/loading.py +++ b/openerp/modules/loading.py @@ -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) diff --git a/openerp/modules/module.py b/openerp/modules/module.py index 554b38f06cf..8958813baa2 100644 --- a/openerp/modules/module.py +++ b/openerp/modules/module.py @@ -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 diff --git a/openerp/netsvc.py b/openerp/netsvc.py index 755f6513b25..1e2407a7faf 100644 --- a/openerp/netsvc.py +++ b/openerp/netsvc.py @@ -2,7 +2,7 @@ ############################################################################## # # OpenERP, Open Source Management Solution -# Copyright (C) 2004-2012 OpenERP SA () +# Copyright (C) 2004-2014 OpenERP SA () # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -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) diff --git a/openerp/osv/fields.py b/openerp/osv/fields.py index 478f982cd69..d5d0ee0fa30 100644 --- a/openerp/osv/fields.py +++ b/openerp/osv/fields.py @@ -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__ diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 3126507608f..64fa06783ab 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -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] diff --git a/openerp/service/db.py b/openerp/service/db.py index f48a1e6e91b..f10f39aab9b 100644 --- a/openerp/service/db.py +++ b/openerp/service/db.py @@ -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)) diff --git a/openerp/sql_db.py b/openerp/sql_db.py index 694ae02b3d9..59841656d8c 100644 --- a/openerp/sql_db.py +++ b/openerp/sql_db.py @@ -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() diff --git a/openerp/tests/common.py b/openerp/tests/common.py index 4dcd2938ffe..a53a8d82240 100644 --- a/openerp/tests/common.py +++ b/openerp/tests/common.py @@ -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 = { diff --git a/openerp/tests/phantomtest.js b/openerp/tests/phantomtest.js index 86176402627..63a3b2e33ff 100644 --- a/openerp/tests/phantomtest.js +++ b/openerp/tests/phantomtest.js @@ -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')..."); }); } }); diff --git a/openerp/tools/image.py b/openerp/tools/image.py index 196ace2da60..08c2fd91f21 100644 --- a/openerp/tools/image.py +++ b/openerp/tools/image.py @@ -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 diff --git a/openerp/tools/osutil.py b/openerp/tools/osutil.py index b5cff6b2646..94684d34673 100644 --- a/openerp/tools/osutil.py +++ b/openerp/tools/osutil.py @@ -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 diff --git a/openerp/tools/safe_eval.py b/openerp/tools/safe_eval.py index b64b401a71c..508a072a3b2 100644 --- a/openerp/tools/safe_eval.py +++ b/openerp/tools/safe_eval.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- ############################################################################## -# Copyright (C) 2004-2012 OpenERP s.a. (). +# Copyright (C) 2004-2014 OpenERP s.a. (). # # 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: