From c186d01d8efd3578f9eb1dd75bb01805eb41cfb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Tue, 30 Oct 2012 12:26:32 +0100 Subject: [PATCH 01/17] [FIX] invoice tasks work: default to account tax when product has no tax lp bug: https://launchpad.net/bugs/1048305 fixed bzr revid: stephane.bidoul@acsone.eu-20121030112632-7cbfv7xgzqnua6i5 --- .../test/test_hr_timesheet_invoice.yml | 1 + .../wizard/hr_timesheet_invoice_create.py | 7 +++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/addons/hr_timesheet_invoice/test/test_hr_timesheet_invoice.yml b/addons/hr_timesheet_invoice/test/test_hr_timesheet_invoice.yml index 91520da56a2..bc19ce40794 100644 --- a/addons/hr_timesheet_invoice/test/test_hr_timesheet_invoice.yml +++ b/addons/hr_timesheet_invoice/test/test_hr_timesheet_invoice.yml @@ -69,6 +69,7 @@ assert aline.invoice_id == invoice_id, "Invoice doesn't match the one at analytic line" # The expected amount is hard coded here, please keep updated: assert invoice_id.amount_untaxed == 187.5, "Invoice amount mismatch: %s" % invoice_id.amount_untaxed + assert invoice_id.amount_tax == 28.13, "Invoice tax mismatch: %s" % invoice_id.amount_tax - I create final invoice for this analytic account. - diff --git a/addons/hr_timesheet_invoice/wizard/hr_timesheet_invoice_create.py b/addons/hr_timesheet_invoice/wizard/hr_timesheet_invoice_create.py index 1201cc7d4e5..695d589ee2e 100644 --- a/addons/hr_timesheet_invoice/wizard/hr_timesheet_invoice_create.py +++ b/addons/hr_timesheet_invoice/wizard/hr_timesheet_invoice_create.py @@ -49,7 +49,6 @@ class account_analytic_line(osv.osv): # } def invoice_cost_create(self, cr, uid, ids, data=None, context=None): analytic_account_obj = self.pool.get('account.analytic.account') - res_partner_obj = self.pool.get('res.partner') account_payment_term_obj = self.pool.get('account.payment.term') invoice_obj = self.pool.get('account.invoice') product_obj = self.pool.get('product.product') @@ -115,7 +114,7 @@ class account_analytic_line(osv.osv): for product_id, user_id, factor_id, qty, uom, line_name in cr.fetchall(): if data.get('product'): - product_id = data['product'][0] + product_id = data['product'][0] product = product_obj.browse(cr, uid, product_id, context=context2) if not product: raise osv.except_osv(_('Error!'), _('There is no product defined for the line %s. Please select one or force the product through the wizard.') % (line_name)) @@ -132,7 +131,7 @@ class account_analytic_line(osv.osv): general_account = product.product_tmpl_id.property_account_income or product.categ_id.property_account_income_categ if not general_account: raise osv.except_osv(_("Configuration Error!"), _("Please define income account for product '%s'.") % product.name) - taxes = product.taxes_id + taxes = product.taxes_id or general_account.tax_ids tax = fiscal_pos_obj.map_tax(cr, uid, account.partner_id.property_account_position, taxes) curr_line = { 'price_unit': price, @@ -211,7 +210,7 @@ class hr_timesheet_invoice_create(osv.osv_memory): data = context and context.get('active_ids', []) for analytic in analytic_obj.browse(cr, uid, data, context=context): if analytic.invoice_id: - raise osv.except_osv(_('Warning!'), _("Invoice is already linked to some of the analytic line(s)!")) + raise osv.except_osv(_('Warning!'), _("Invoice is already linked to some of the analytic line(s)!")) def do_create(self, cr, uid, ids, context=None): data = self.read(cr, uid, ids, [], context=context)[0] From ad811bf48e17db0972e0473fec2fc3bc455e1729 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Tue, 30 Oct 2012 15:55:37 +0100 Subject: [PATCH 02/17] [FIX] hr_timesheet_invoice: make tax test more independent of master data bzr revid: stephane.bidoul@acsone.eu-20121030145537-u4jdutaw2mzdur39 --- addons/hr_timesheet_invoice/__openerp__.py | 1 + .../test/test_hr_timesheet_invoice.yml | 41 +++++- .../test_hr_timesheet_invoice_no_prod_tax.yml | 120 ++++++++++++++++++ 3 files changed, 158 insertions(+), 4 deletions(-) create mode 100644 addons/hr_timesheet_invoice/test/test_hr_timesheet_invoice_no_prod_tax.yml diff --git a/addons/hr_timesheet_invoice/__openerp__.py b/addons/hr_timesheet_invoice/__openerp__.py index 65ccc8948c2..d15dd3b0e3b 100644 --- a/addons/hr_timesheet_invoice/__openerp__.py +++ b/addons/hr_timesheet_invoice/__openerp__.py @@ -50,6 +50,7 @@ reports.""", ], 'demo': ['hr_timesheet_invoice_demo.xml'], 'test': ['test/test_hr_timesheet_invoice.yml', + 'test/test_hr_timesheet_invoice_no_prod_tax.yml', 'test/hr_timesheet_invoice_report.yml', ], 'installable': True, diff --git a/addons/hr_timesheet_invoice/test/test_hr_timesheet_invoice.yml b/addons/hr_timesheet_invoice/test/test_hr_timesheet_invoice.yml index bc19ce40794..de19ea8fb3a 100644 --- a/addons/hr_timesheet_invoice/test/test_hr_timesheet_invoice.yml +++ b/addons/hr_timesheet_invoice/test/test_hr_timesheet_invoice.yml @@ -30,6 +30,42 @@ - !python {model: hr.analytic.timesheet}: | self.on_change_account_id(cr, uid, [ref('account_analytic_line_developyamlforhrmodule0')], ref('account.analytic_agrolait')) + +- + I create a Tax Codes +- + !record {model: account.tax.code, id: tax_case}: + name: Tax_case + company_id: base.main_company + sign: 1 +- + I create a Tax +- + !record {model: account.tax, id: tax10}: + name: Tax 10.0 + amount: 10.0 + type: fixed + sequence: 1 + company_id: base.main_company + type_tax_use: all + tax_code_id: tax_case +- + I create an Account with the tax +- + !record {model: account.account, id: account_income_i000}: + name: "income account" + code: "I000" + type: 'other' + user_type: "account.data_account_type_income" + tax_ids: [tax10] +- + I set the account as property_account_income on the product and I set the tax on the product +- + !record {model: product.product, id: product.product_product_consultant}: + property_account_income: account_income_i000 + uom_id: product.product_uom_hour + taxes_id: [tax10] + list_price: 75 - I create invoice on analytic Line using "Invoice analytic Line" wizard. - @@ -39,13 +75,11 @@ price: 1 product: product.product_product_consultant time: 1 - - I click on "Create Invoice" button of "Invoice analytic Line" wizard to create invoice. - !python {model: hr.timesheet.invoice.create}: | self.do_create(cr, uid, [ref("hr_timesheet_invoice_create_0")], {"active_ids": [ref("hr_timesheet_invoice.account_analytic_line_developyamlforhrmodule0")]}) - - I check that Invoice is created for this timesheet. - @@ -67,9 +101,8 @@ assert product == product_exp assert aline.invoice_id, "Invoice created, but analytic line wasn't updated." assert aline.invoice_id == invoice_id, "Invoice doesn't match the one at analytic line" - # The expected amount is hard coded here, please keep updated: assert invoice_id.amount_untaxed == 187.5, "Invoice amount mismatch: %s" % invoice_id.amount_untaxed - assert invoice_id.amount_tax == 28.13, "Invoice tax mismatch: %s" % invoice_id.amount_tax + assert invoice_id.amount_tax == 50, "Invoice tax mismatch: %s" % invoice_id.amount_tax - I create final invoice for this analytic account. - diff --git a/addons/hr_timesheet_invoice/test/test_hr_timesheet_invoice_no_prod_tax.yml b/addons/hr_timesheet_invoice/test/test_hr_timesheet_invoice_no_prod_tax.yml new file mode 100644 index 00000000000..c560b9f50ce --- /dev/null +++ b/addons/hr_timesheet_invoice/test/test_hr_timesheet_invoice_no_prod_tax.yml @@ -0,0 +1,120 @@ +- + In order to test hr_timesheet_invoice in OpenERP, I create account line to manage invoice based on costs. +- + I create an account analytic line. +- + !record {model: account.analytic.line, id: account_analytic_line_developyamlforhrmodule1 }: + account_id: account.analytic_agrolait + amount: -1.0 + general_account_id: account.a_expense + journal_id: hr_timesheet.analytic_journal + name: develop yaml for hr module + product_id: product.product_product_consultant + product_uom_id: product.product_uom_hour + to_invoice: hr_timesheet_invoice.timesheet_invoice_factor2 + unit_amount: 5.00 +- + Assign partner name and price list in analytic account. +- + !record {model: account.analytic.account, id: account.analytic_agrolait}: + partner_id: base.res_partner_2 + pricelist_id: product.list0 +- + I open this account and make the state as pending. +- + !python {model: account.analytic.account}: | + self.set_open(cr, uid, [ref('account.analytic_agrolait')], None) + self.set_pending(cr, uid, [ref('account.analytic_agrolait')], None) +- + I assign account on analytic account line. +- + !python {model: hr.analytic.timesheet}: | + self.on_change_account_id(cr, uid, [ref('account_analytic_line_developyamlforhrmodule1')], ref('account.analytic_agrolait')) + +- + I create a Tax Codes +- + !record {model: account.tax.code, id: tax_case}: + name: Tax_case + company_id: base.main_company + sign: 1 +- + I create a Tax +- + !record {model: account.tax, id: tax8}: + name: Tax 8.0 + amount: 8.0 + type: fixed + sequence: 1 + company_id: base.main_company + type_tax_use: all + tax_code_id: tax_case +- + I create an Account with the taxt +- + !record {model: account.account, id: account_income_i000}: + name: "income account" + code: "I000" + type: 'other' + user_type: "account.data_account_type_income" + tax_ids: [tax8] +- + I set the account as property_account_income on the product and I set no tax on the product (so default tax from the account will be used) +- + !record {model: product.product, id: product.product_product_consultant}: + property_account_income: account_income_i000 + uom_id: product.product_uom_hour + taxes_id: [] + list_price: 75 +- + I create invoice on analytic Line using "Invoice analytic Line" wizard. +- + !record {model: hr.timesheet.invoice.create, id: hr_timesheet_invoice_create_0}: + date: 1 + name: 1 + price: 1 + product: product.product_product_consultant + time: 1 +- + I click on "Create Invoice" button of "Invoice analytic Line" wizard to create invoice. +- + !python {model: hr.timesheet.invoice.create}: | + self.do_create(cr, uid, [ref("hr_timesheet_invoice_create_0")], {"active_ids": [ref("hr_timesheet_invoice.account_analytic_line_developyamlforhrmodule1")]}) +- + I check that Invoice is created for this timesheet. +- + !python {model: account.analytic.line}: | + aline = self.browse(cr, uid, ref('account_analytic_line_developyamlforhrmodule1')) + analytic_account_obj = self.pool.get('account.analytic.account') + data = self.pool.get('hr.timesheet.invoice.create').read(cr, uid, [ref("hr_timesheet_invoice_create_0")], [], context)[0] + partner = aline.account_id.partner_id.id + + invoice_obj = self.pool.get('account.invoice') + invoice_ids = invoice_obj.search(cr, uid, [('partner_id', '=', partner)]) + invoice_id = invoice_obj.browse(cr, uid, invoice_ids)[0] + + for invoice in invoice_id.invoice_line: + product = invoice.product_id.id + + product_exp = data['product'][0] + + assert product == product_exp + assert aline.invoice_id, "Invoice created, but analytic line wasn't updated." + assert aline.invoice_id == invoice_id, "Invoice doesn't match the one at analytic line" + assert invoice_id.amount_untaxed == 187.5, "Invoice amount mismatch: %s" % invoice_id.amount_untaxed + assert invoice_id.amount_tax == 40, "Invoice tax mismatch: %s" % invoice_id.amount_tax +- + I create final invoice for this analytic account. +- + !record {model: hr.timesheet.invoice.create.final, id: hr_timesheet_invoice_create_final_0}: + date: 1 + name: 1 + price: 1 + time: 1 +- + I click on "Create Invoice" button to create Invoice and validate the invoice. +- + !python {model: hr.timesheet.invoice.create.final}: | + import netsvc + wkf_service = netsvc.LocalService("workflow") + res = self.do_create(cr, uid, [ref("hr_timesheet_invoice_create_final_0")], {"active_ids": [ref("account.analytic_agrolait")]}) From df4d649fd953f90ec14dc2a221fce979c0216733 Mon Sep 17 00:00:00 2001 From: "Quentin (OpenERP)" Date: Tue, 30 Oct 2012 16:33:09 +0100 Subject: [PATCH 03/17] [FIX] account: context passing in account.move.line search view bzr revid: qdp-launchpad@openerp.com-20121030153309-7dsnps6dhm0opnvv --- addons/account/account_view.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/account/account_view.xml b/addons/account/account_view.xml index d9e95f15079..63d7e891437 100644 --- a/addons/account/account_view.xml +++ b/addons/account/account_view.xml @@ -1230,8 +1230,8 @@ - - + + From 8a12c46307b2225739cf9e434f91b0167bc44523 Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Tue, 30 Oct 2012 16:53:13 +0100 Subject: [PATCH 04/17] [IMP] added commit_value() bzr revid: nicolas.vanhoren@openerp.com-20121030155313-g2cx14h4rl6zks6z --- addons/web/static/src/js/view_form.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index 5ecad26e5df..95a21877bd0 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -1991,6 +1991,11 @@ instance.web.form.FieldInterface = { * Called when the translate button is clicked. */ on_translate: function() {}, + /** + This method is called by the form view before reading on_change values and before saving. It tells + the field to save its value before reading it using get_value(). Must return a deferred. + */ + commit_value: function() {}, }; /** @@ -2134,7 +2139,10 @@ instance.web.form.AbstractField = instance.web.form.FormWidget.extend(instance.w } this.$el.attr('style', style); this.$el.css('minHeight', height); - } + }, + commit_value: function() { + return $.when(); + }, }); /** From 3c28326317bde6e52c7607642ba76b641f91da88 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Wed, 31 Oct 2012 09:48:47 +0100 Subject: [PATCH 05/17] [DOC] test-framework: added note about setUp() throwing exceptions. bzr revid: vmt@openerp.com-20121031084847-fuwtnu8nmegphwqm --- doc/test-framework.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/doc/test-framework.rst b/doc/test-framework.rst index f53d15ae0da..17a7b480640 100644 --- a/doc/test-framework.rst +++ b/doc/test-framework.rst @@ -104,3 +104,14 @@ TestCase subclasses .. automodule:: openerp.tests.common :members: + +.. note:: + + The `setUp` and `tearDown` methods are not part of the tests. Uncaught + exceptions in those methods are errors, not test failures. In particular, + a failing `setUp` will not be followed by a `tearDown` causing any + allocated resource in the `setUp` to not be released by the `tearDown`. + + In the :py:class:`openerp.tests.common.TransactionCase` and + :py:class:`openerp.tests.common.SingleTransactionCase`, this means the + test suite can hang because of unclosed cursors. From 2aee5380eb9d40c6851116e3f8c97937977ff22f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Wed, 31 Oct 2012 11:30:27 +0100 Subject: [PATCH 06/17] [IMP] ref and browse_ref helpers on openerp.tests.common.BaseCase bzr revid: stephane.bidoul@acsone.eu-20121031103027-2sozmbi3z7bkr22q --- openerp/tests/common.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/openerp/tests/common.py b/openerp/tests/common.py index b3ee4ac53f2..488e4bd8bd8 100644 --- a/openerp/tests/common.py +++ b/openerp/tests/common.py @@ -44,6 +44,8 @@ def stop_openerp(): class BaseCase(unittest2.TestCase): """ Subclass of TestCase for common OpenERP-specific code. + + This class is abstract and expects self.cr and self.uid to be initialized by subclasses. """ @classmethod @@ -53,7 +55,19 @@ class BaseCase(unittest2.TestCase): @classmethod def registry(self, model): return openerp.modules.registry.RegistryManager.get(DB)[model] - + + def ref(self, xmlid): + """Returns res_id corresponding to a given module_name.xml_id (cached) or raise ValueError if not found""" + assert "." in xmlid, "this method required a fully qualified xml_id" + module, xmlid = xmlid.split('.') + model, id = self.registry('ir.model.data').get_object_reference(self.cr, self.uid, module, xmlid) + return id + + def browse_ref(self, xmlid): + """Returns a browsable record for the given module_name.xml_id or raise ValueError if not found""" + assert "." in xmlid, "this method required a fully qualified xml_id" + module, xmlid = xmlid.split('.') + return self.registry('ir.model.data').get_object(self.cr, self.uid, module, xmlid) class TransactionCase(BaseCase): """ From 2ea22c52340f3a8485b89173efeb2793a6260ec8 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 31 Oct 2012 11:32:33 +0100 Subject: [PATCH 07/17] [FIX] first field of editable list o2ms not showing their content Bug introduced in xmo@openerp.com-20121019100725-izotstggoxpfjk6k (and xmo@openerp.com-20121022103935-78bm40gfotpt1ds6 to the extent that in the former the cell would disappear altogether and in the latter its content would be blanked out). As it was the last 15 times I made this mistake, adding a "!" before an `instanceof` call and forgetting parens just negates the object (first argument to `instanceof`), not the whole expression, and thus the expression is *always false* (the result of !a is a `boolean` primitive, and `primitive instanceof ObjectType` is always false) bzr revid: xmo@openerp.com-20121031103233-9qw40l6m2vy0l3m1 --- addons/web/static/src/js/view_form.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index 5ecad26e5df..76ddeb86acf 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -3321,7 +3321,7 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({ e.cancel = true; }); _(controller.columns).find(function (column) { - if (!column instanceof instance.web.list.Handle) { + if (!(column instanceof instance.web.list.Handle)) { return false; } column.modifiers.invisible = true; From e9d00c7148b2f644be6c5785875da69bbf8ceee1 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 31 Oct 2012 15:06:09 +0100 Subject: [PATCH 08/17] [FIX] broken data: urls + remove inane position: absolute on listview delete buttons bzr revid: xmo@openerp.com-20121031140609-d1d1w01dl9g41odf --- addons/web/static/src/css/base.css | 153 ++++++++++++++-------------- addons/web/static/src/css/base.sass | 14 ++- 2 files changed, 82 insertions(+), 85 deletions(-) diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index 7c62df158b3..d6fce64ca10 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -217,7 +217,7 @@ background-clip: padding-box; } .openerp.ui-dialog .ui-dialog-content { - padding: 0px; + padding: 0; } .openerp.ui-dialog .ui-dialog-titlebar, .openerp.ui-dialog .ui-dialog-content, .openerp.ui-dialog .ui-dialog-buttonpane { padding: 16px; @@ -1186,7 +1186,7 @@ color: white; padding: 2px 4px; margin: 1px 6px 0 0; - border: 1px solid lightgrey; + border: 1px solid lightGray; text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); -moz-border-radius: 4px; -webkit-border-radius: 4px; @@ -1211,7 +1211,7 @@ transform: scale(1.1); } .openerp .oe_secondary_submenu .oe_active { - border-top: 1px solid lightgrey; + border-top: 1px solid lightGray; border-bottom: 1px solid #dedede; text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); -moz-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.2), inset 0 -1px 3px rgba(40, 40, 40, 0.2); @@ -2136,7 +2136,7 @@ } .openerp .oe_form .oe_form_label_help[for] span, .openerp .oe_form .oe_form_label[for] span { font-size: 80%; - color: darkgreen; + color: darkGreen; vertical-align: top; position: relative; top: -4px; @@ -2567,9 +2567,6 @@ .openerp .oe_list_editable .oe_list_content td.oe_list_field_cell { padding: 4px 6px 3px 6px; } -.openerp .oe_list.oe_list_editable td.oe_list_record_delete { - position: absolute; -} .openerp .oe_list.oe_list_editable.oe_editing .oe_edition .oe_list_field_cell:not(.oe_readonly) { color: transparent; } @@ -2878,6 +2875,78 @@ color: #333333; } +.openerp .oe_fileupload { + display: inline-block; + clear: both; + width: 100%; +} +.openerp .oe_fileupload .oe_add { + float: left; + position: relative; + width: 100%; + left: 2px; + top: 7px; +} +.openerp .oe_fileupload .oe_add button { + display: inline; + height: 24px; + font-size: 12px; + line-height: 12px; + vertical-align: middle; +} +.openerp .oe_fileupload .oe_add button.oe_attach { + width: 24px; + overflow: hidden; + width: 24px; + overflow: hidden; + background: transparent; + color: #7c7bad; + box-shadow: none; + border: none; + text-shadow: none; +} +.openerp .oe_fileupload .oe_add button.oe_attach .oe_e { + position: relative; + top: -1px; + left: -9px; +} +.openerp .oe_fileupload .oe_add input.oe_form_binary_file { + display: inline-block; + margin-left: -5px; + height: 28px; + width: 52px; + margin-top: -26px; +} +.openerp .oe_fileupload .oe_add .oe_attach_label { + color: #7c7bad; + margin-left: -3px; +} +.openerp .oe_fileupload .oe_attachments { + margin-bottom: 4px; + margin-right: 0px; + font-size: 12px; + border-radius: 2px; + border: solid 1px rgba(124, 123, 173, 0.14); +} +.openerp .oe_fileupload .oe_attachments .oe_attachment { + padding: 2px; + padding-left: 4px; + padding-right: 4px; +} +.openerp .oe_fileupload .oe_attachments .oe_attachment .oe_e { + font-size: 23px; + margin-top: -5px; +} +.openerp .oe_fileupload .oe_attachments .oe_attachment .oe_e:hover { + text-decoration: none; +} +.openerp .oe_fileupload .oe_attachments .oe_attachment:nth-child(odd) { + background: white; +} +.openerp .oe_fileupload .oe_attachments .oe_attachment:nth-child(even) { + background: #f4f5fa; +} + .kitten-mode-activated { background-image: url(http://placekitten.com/g/1365/769); background-size: cover; @@ -2932,76 +3001,6 @@ div.ui-widget-overlay { border-radius: 3px; } -.openerp .oe_fileupload { - display: inline-block; - clear: both; - width: 100%; -} -.openerp .oe_fileupload .oe_add { - float: left; - position: relative; - width: 100%; - left: +2px; - top: +7px; -} -.openerp .oe_fileupload .oe_add button{ - display: inline; - height: 24px; - font-size: 12px; - line-height: 12px; - vertical-align: middle; -} -.openerp .oe_fileupload .oe_add button.oe_attach { - width: 24px; - overflow: hidden; - background: transparent; - color: #7C7BAD; - box-shadow: none; - border: none; - text-shadow: none; -} -.openerp .oe_fileupload .oe_add .oe_attach_label{ - color: #7C7BAD; - margin-left: -3px; -} -.openerp .oe_fileupload .oe_add button.oe_attach .oe_e{ - position: relative; - top: -1px; - left: -9px; -} -.openerp .oe_fileupload .oe_add input.oe_form_binary_file{ - display: inline-block; - margin-left: -5px; - height: 28px; - width: 52px; - margin-top: -26px; -} -.openerp .oe_fileupload .oe_attachments { - margin-bottom: 4px; - margin-right: 0px; - font-size: 12px; - border-radius: 2px; - border: solid 1px rgba(124,123,173,0.14); -} -.openerp .oe_fileupload .oe_attachments .oe_attachment{ - padding: 2px; - padding-left: 4px; - padding-right: 4px; -} -.openerp .oe_fileupload .oe_attachments .oe_attachment .oe_e{ - font-size: 23px; - margin-top: -5px; -} -.openerp .oe_fileupload .oe_attachments .oe_attachment .oe_e:hover{ - text-decoration: none; -} -.openerp .oe_fileupload .oe_attachments .oe_attachment:nth-child(odd){ - background:white; -} -.openerp .oe_fileupload .oe_attachments .oe_attachment:nth-child(even){ - background: #F4F5FA; -} - @media print { .openerp { text-shadow: none; diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass index d76a33fb315..1719a863549 100644 --- a/addons/web/static/src/css/base.sass +++ b/addons/web/static/src/css/base.sass @@ -258,6 +258,7 @@ $sheet-padding: 16px // so remove position:relative .ui-tabs position: static + // Modal box &.ui-dialog display: none @@ -271,7 +272,7 @@ $sheet-padding: 16px @include box-shadow(0 1px 12px rgba(0, 0, 0, 0.6)) @include background-clip() .ui-dialog-content - padding: 0px + padding: 0 .ui-dialog-titlebar, .ui-dialog-content, .ui-dialog-buttonpane padding: 16px .ui-dialog-titlebar @@ -316,7 +317,7 @@ $sheet-padding: 16px text-decoration: none .oe_about background-color: white - background-image: url(data:image/pngbase64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAKUlEQVQIHWO8e/fufwYsgAUkJigoiCIF5DMyoYggcUiXgNnBiGQKmAkARpcEQeriln4AAAAASUVORK5CYII=) + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAKUlEQVQIHWO8e/fufwYsgAUkJigoiCIF5DMyoYggcUiXgNnBiGQKmAkARpcEQeriln4AAAAASUVORK5CYII=) @include radius(0 0 2px 2px) a color: $link-color @@ -681,7 +682,7 @@ $sheet-padding: 16px // }}} // Login {{{ .oe_login - background-image: url(data:image/pngbase64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAKUlEQVQIHWO8e/fufwYsgAUkJigoiCIF5DMyoYggcUiXgNnBiGQKmAkARpcEQeriln4AAAAASUVORK5CYII=) + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAKUlEQVQIHWO8e/fufwYsgAUkJigoiCIF5DMyoYggcUiXgNnBiGQKmAkARpcEQeriln4AAAAASUVORK5CYII=) text-align: center font-size: 14px height: 100% @@ -1028,7 +1029,7 @@ $sheet-padding: 16px // About openerp {{{ .oe_about background-color: white - background-image: url(data:image/pngbase64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAKUlEQVQIHWO8e/fufwYsgAUkJigoiCIF5DMyoYggcUiXgNnBiGQKmAkARpcEQeriln4AAAAASUVORK5CYII=) + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAKUlEQVQIHWO8e/fufwYsgAUkJigoiCIF5DMyoYggcUiXgNnBiGQKmAkARpcEQeriln4AAAAASUVORK5CYII=) @include radius(0 0 2px 2px) a color: $link-color @@ -2033,9 +2034,6 @@ $sheet-padding: 16px .oe_list_content td.oe_list_field_cell padding: 4px 6px 3px 6px - .oe_list.oe_list_editable - td.oe_list_record_delete - position: absolute .oe_list.oe_list_editable.oe_editing .oe_edition .oe_list_field_cell:not(.oe_readonly) * @@ -2336,7 +2334,7 @@ $sheet-padding: 16px .oe_e:hover text-decoration: none .oe_attachment:nth-child(odd) - background:white + background: white .oe_attachment:nth-child(even) background: #F4F5FA From b6af4f22cb69bb018fd92284c72499dcd56ec873 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 31 Oct 2012 15:06:41 +0100 Subject: [PATCH 09/17] [FIX] [ESC] key closing dialogs when editing a list bzr revid: xmo@openerp.com-20121031140641-l1nf68jxiye8fyi8 --- addons/web/static/src/js/view_list_editable.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index acc1d781976..17ee06500e9 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -410,15 +410,15 @@ openerp.web.list_editable = function (instance) { }); this.editor.$el.on('keyup keydown', function (e) { - if (!self.editor.is_editing()) { return; } + if (!self.editor.is_editing()) { return true; } var key = _($.ui.keyCode).chain() .map(function (v, k) { return {name: k, code: v}; }) .find(function (o) { return o.code === e.which; }) .value(); - if (!key) { return; } + if (!key) { return true; } var method = e.type + '_' + key.name; - if (!(method in self)) { return; } - self[method](e); + if (!(method in self)) { return true; } + return self[method](e); }); }, /** @@ -446,7 +446,10 @@ openerp.web.list_editable = function (instance) { keyup_ENTER: function () { return this._next(); }, - keyup_ESCAPE: function () { + keydown_ESCAPE: function (e) { + return false; + }, + keyup_ESCAPE: function (e) { return this.cancel_edition(); }, /** From 62f4d999f4f293771b819c66f05ff7b2d271b21e Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 31 Oct 2012 15:07:18 +0100 Subject: [PATCH 10/17] [IMP] positioning of edition fields within a scrolled dialog they still don't work every time, but should be slightly more reliable bzr revid: xmo@openerp.com-20121031140718-j3e4r9djyq34rqu7 --- addons/web/static/src/js/view_list_editable.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index 17ee06500e9..021e5549c18 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -252,9 +252,10 @@ openerp.web.list_editable = function (instance) { var position = $cell.position(); field.set_dimensions($cell.outerHeight(), $cell.outerWidth()); - field.$el.css({ - top: position.top, - left: position.left, + field.$el.position({ + my: 'left top', + at: 'left top', + of: $cell }); }, /** From 1622caae274ec507f9d94f7977d1fee5a9279885 Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Wed, 31 Oct 2012 15:30:36 +0100 Subject: [PATCH 11/17] Changed the onchange handling to be async bzr revid: nicolas.vanhoren@openerp.com-20121031143036-eventomyjpwgalxb --- addons/web/static/src/js/view_form.js | 104 ++++++++++++-------------- 1 file changed, 46 insertions(+), 58 deletions(-) diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index c145b05a75f..c536ea0afe5 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -427,7 +427,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM var onchange = _.str.trim(on_change); var call = onchange.match(/^\s?(.*?)\((.*?)\)\s?$/); if (!call) { - return null; + throw new Error("Wrong on change format: " + onchange); } var method = call[1]; @@ -494,71 +494,59 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM var self = this; return this.on_change_mutex.exec(function() { try { - var response = {}, can_process_onchange = $.Deferred(); + var def; processed = processed || []; processed.push(widget.name); var on_change = widget.node.attrs.on_change; if (on_change) { var change_spec = self.parse_on_change(on_change, widget); - if (change_spec) { - var ajax = { - url: '/web/dataset/onchange', - async: false - }; - can_process_onchange = self.rpc(ajax, { - model: self.dataset.model, - method: change_spec.method, - args: [(self.datarecord.id == null ? [] : [self.datarecord.id])].concat(change_spec.args), - context_id: change_spec.context_index == undefined ? null : change_spec.context_index + 1 - }).then(function(r) { - _.extend(response, r); - }); - } else { - console.warn("Wrong on_change format", on_change); - } - } - // fail if onchange failed - if (can_process_onchange.isRejected()) { - return can_process_onchange; + def = self.rpc('/web/dataset/onchange', { + model: self.dataset.model, + method: change_spec.method, + args: [(self.datarecord.id == null ? [] : [self.datarecord.id])].concat(change_spec.args), + context_id: change_spec.context_index == undefined ? null : change_spec.context_index + 1 + }); + } else { + def = $.when({}); } + return def.pipe(function(response) { + if (widget.field['change_default']) { + var fieldname = widget.name + var value_; + if (response.value && (fieldname in response.value)) { + // Use value from onchange if onchange executed + value_ = response.value[fieldname]; + } else { + // otherwise get form value for field + value_ = self.fields[fieldname].get_value(); + } + var condition = fieldname + '=' + value_; - if (widget.field['change_default']) { - var fieldname = widget.name, value_; - if (response.value && (fieldname in response.value)) { - // Use value from onchange if onchange executed - value_ = response.value[fieldname]; - } else { - // otherwise get form value for field - value_ = self.fields[fieldname].get_value(); + if (value_) { + return self.rpc('/web/dataset/call', { + model: 'ir.values', + method: 'get_defaults', + args: [self.model, condition] + }).pipe(function (results) { + if (!results.length) { + return response; + } + if (!response.value) { + response.value = {}; + } + for(var i=0; i Date: Wed, 31 Oct 2012 15:37:23 +0100 Subject: [PATCH 12/17] [FIX] dataset methods to get their request context through Dataset#get_context if they go directly through the embedded model, child classes to DataSet don't get the occasion to override the generation/fetching of the context, and e.g. relational fields thus don't work correctly (as their field context isn't forwarded). bzr revid: xmo@openerp.com-20121031143723-j4q0t8cbzttela1r --- addons/web/static/src/js/data.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/addons/web/static/src/js/data.js b/addons/web/static/src/js/data.js index e279ade6fb3..63c90f53758 100644 --- a/addons/web/static/src/js/data.js +++ b/addons/web/static/src/js/data.js @@ -471,7 +471,7 @@ instance.web.DataSet = instance.web.CallbackEnabled.extend({ default_get: function(fields, options) { options = options || {}; return this._model.call('default_get', - [fields], {context: this._model.context(options.context)}); + [fields], {context: this.get_context(options.context)}); }, /** * Creates a new record in db @@ -480,7 +480,7 @@ instance.web.DataSet = instance.web.CallbackEnabled.extend({ * @returns {$.Deferred} */ create: function(data) { - return this._model.call('create', [data], {context: this._model.context()}); + return this._model.call('create', [data], {context: this.get_context()}); }, /** * Saves the provided data in an existing db record @@ -493,7 +493,7 @@ instance.web.DataSet = instance.web.CallbackEnabled.extend({ */ write: function (id, data, options) { options = options || {}; - return this._model.call('write', [[id], data], {context: this._model.context(options.context)}).then(this.trigger('dataset_changed', id, data, options)); + return this._model.call('write', [[id], data], {context: this.get_context(options.context)}).then(this.trigger('dataset_changed', id, data, options)); }, /** * Deletes an existing record from the database @@ -501,7 +501,7 @@ instance.web.DataSet = instance.web.CallbackEnabled.extend({ * @param {Number|String} ids identifier of the record to delete */ unlink: function(ids) { - return this._model.call('unlink', [ids], {context: this._model.context()}).then(this.trigger('dataset_changed', ids)); + return this._model.call('unlink', [ids], {context: this.get_context()}).then(this.trigger('dataset_changed', ids)); }, /** * Calls an arbitrary RPC method @@ -532,7 +532,7 @@ instance.web.DataSet = instance.web.CallbackEnabled.extend({ * @returns {$.Deferred} */ name_get: function(ids) { - return this._model.call('name_get', [ids], {context: this._model.context()}); + return this._model.call('name_get', [ids], {context: this.get_context()}); }, /** * @@ -556,7 +556,7 @@ instance.web.DataSet = instance.web.CallbackEnabled.extend({ * @param name */ name_create: function(name) { - return this._model.call('name_create', [name], {context: this._model.context()}); + return this._model.call('name_create', [name], {context: this.get_context()}); }, exec_workflow: function (id, signal) { return this._model.exec_workflow(id, signal); @@ -606,7 +606,7 @@ instance.web.DataSet = instance.web.CallbackEnabled.extend({ return instance.session.rpc('/web/dataset/resequence', { model: this.model, ids: ids, - context: this._model.context(options.context), + context: this.get_context(options.context), }).pipe(function (results) { return results; }); From 00fc09890bfe6d26c77e50c496101e3b71c1b1c3 Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Wed, 31 Oct 2012 16:03:24 +0100 Subject: [PATCH 13/17] temporary revert of 3331 bzr revid: nicolas.vanhoren@openerp.com-20121031150324-dxlhh3wxhob8numi --- addons/web/static/src/js/view_form.js | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index c536ea0afe5..e0727b99259 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -1979,11 +1979,6 @@ instance.web.form.FieldInterface = { * Called when the translate button is clicked. */ on_translate: function() {}, - /** - This method is called by the form view before reading on_change values and before saving. It tells - the field to save its value before reading it using get_value(). Must return a deferred. - */ - commit_value: function() {}, }; /** @@ -2127,10 +2122,7 @@ instance.web.form.AbstractField = instance.web.form.FormWidget.extend(instance.w } this.$el.attr('style', style); this.$el.css('minHeight', height); - }, - commit_value: function() { - return $.when(); - }, + } }); /** From 43513d7ccc0888d7357d00180c903b0a1a97997c Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Wed, 31 Oct 2012 16:20:11 +0100 Subject: [PATCH 14/17] [ADD] tests.common: added testcase for ref() and browse_ref(), renaming existing test file to make it more generic bzr revid: odo@openerp.com-20121031152011-gyih3j9itbgfauwv --- openerp/tests/__init__.py | 4 +-- ...per_class_teardown.py => test_basecase.py} | 28 +++++++++++++++++-- 2 files changed, 27 insertions(+), 5 deletions(-) rename openerp/tests/{test_per_class_teardown.py => test_basecase.py} (60%) diff --git a/openerp/tests/__init__.py b/openerp/tests/__init__.py index d06e1030d6f..aa39b777c0f 100644 --- a/openerp/tests/__init__.py +++ b/openerp/tests/__init__.py @@ -9,7 +9,7 @@ See the :ref:`test-framework` section in the :ref:`features` list. """ from . import test_expression, test_html_sanitize, test_ir_sequence, test_orm,\ - test_per_class_teardown, \ + test_basecase, \ test_view_validation, test_uninstall, test_misc, test_db_cursor fast_suite = [ @@ -21,7 +21,7 @@ checks = [ test_html_sanitize, test_db_cursor, test_orm, - test_per_class_teardown, + test_basecase, test_view_validation, test_misc, ] diff --git a/openerp/tests/test_per_class_teardown.py b/openerp/tests/test_basecase.py similarity index 60% rename from openerp/tests/test_per_class_teardown.py rename to openerp/tests/test_basecase.py index a1890095c09..55125b6d7c2 100644 --- a/openerp/tests/test_per_class_teardown.py +++ b/openerp/tests/test_basecase.py @@ -1,10 +1,9 @@ # -*- coding: utf-8 -*- import unittest2 -import openerp import common -class test_per_class_teardown(common.SingleTransactionCase): +class test_single_transaction_case(common.SingleTransactionCase): """ Check the whole-class transaction behavior of SingleTransactionCase. """ @@ -22,7 +21,30 @@ class test_per_class_teardown(common.SingleTransactionCase): ids = self.registry('res.partner').search(cr, uid, [('name', '=', 'test_per_class_teardown_partner')]) self.assertEqual(1, len(ids), "Test partner not found.") -class test_per_method_teardown(common.TransactionCase): + def test_20a(self): + """ Create a partner with a XML ID """ + cr, uid = self.cr, self.uid + res_partner = self.registry('res.partner') + ir_model_data = self.registry('ir.model.data') + pid, _ = res_partner.name_create(cr, uid, 'Mr Blue') + ir_model_data.create(cr, uid, {'name': 'test_partner_blue', + 'module': 'base', + 'model': 'res.partner', + 'res_id': pid}) + def test_20b(self): + """ Resolve xml id with ref() and browse_ref() """ + cr, uid = self.cr, self.uid + res_partner = self.registry('res.partner') + xid = 'base.test_partner_blue' + p_ref = self.ref(xid) + self.assertTrue(p_ref, "ref() should resolve xid to database ID") + partner = res_partner.browse(cr, uid, p_ref) + p_browse_ref = self.browse_ref(xid) + self.assertEqual(partner, p_browse_ref, "browse_ref() should resolve xid to browse records") + + + +class test_transaction_case(common.TransactionCase): """ Check the per-method transaction behavior of TransactionCase. """ From 51f2422b41e9640ee6b981ada9d126d34a0c3e85 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Wed, 31 Oct 2012 16:21:49 +0100 Subject: [PATCH 15/17] [IMP] tests.common: improved docstring for ref() and browse_ref() bzr revid: odo@openerp.com-20121031152149-73pxpymjf1yz5xqi --- openerp/tests/common.py | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/openerp/tests/common.py b/openerp/tests/common.py index 488e4bd8bd8..f05be10f098 100644 --- a/openerp/tests/common.py +++ b/openerp/tests/common.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -import os import threading import time import unittest2 @@ -40,7 +39,6 @@ def stop_openerp(): """ openerp.service.stop_services() - class BaseCase(unittest2.TestCase): """ Subclass of TestCase for common OpenERP-specific code. @@ -55,19 +53,30 @@ class BaseCase(unittest2.TestCase): @classmethod def registry(self, model): return openerp.modules.registry.RegistryManager.get(DB)[model] - - def ref(self, xmlid): - """Returns res_id corresponding to a given module_name.xml_id (cached) or raise ValueError if not found""" - assert "." in xmlid, "this method required a fully qualified xml_id" - module, xmlid = xmlid.split('.') - model, id = self.registry('ir.model.data').get_object_reference(self.cr, self.uid, module, xmlid) + + @classmethod + def ref(self, xid): + """ Returns database ID corresponding to a given identifier. + + :param xid: fully-qualified record identifier, in the form ``module.identifier`` + :raise: ValueError if not found + """ + assert "." in xid, "this method requires a fully qualified parameter, in the following form: 'module.identifier'" + module, xid = xid.split('.') + _, id = self.registry('ir.model.data').get_object_reference(self.cr, self.uid, module, xid) return id - - def browse_ref(self, xmlid): - """Returns a browsable record for the given module_name.xml_id or raise ValueError if not found""" - assert "." in xmlid, "this method required a fully qualified xml_id" - module, xmlid = xmlid.split('.') - return self.registry('ir.model.data').get_object(self.cr, self.uid, module, xmlid) + + @classmethod + def browse_ref(self, xid): + """ Returns a browsable record for the given identifier. + + :param xid: fully-qualified record identifier, in the form ``module.identifier`` + :raise: ValueError if not found + """ + assert "." in xid, "this method requires a fully qualified parameter, in the following form: 'module.identifier'" + module, xid = xid.split('.') + return self.registry('ir.model.data').get_object(self.cr, self.uid, module, xid) + class TransactionCase(BaseCase): """ From e44f673e61b2ef3f0953b86f9d0c4b3db8d934a0 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Wed, 31 Oct 2012 17:14:37 +0100 Subject: [PATCH 16/17] [FIX] tools.yaml_import: change w.r.t. etree FutureWarning, which was: FutureWarning: The behavior of this method will change in future versions. Use specific 'len(elem)' or 'elem is not None' test instead. bzr revid: vmt@openerp.com-20121031161437-pzz2czkaq6rw5t31 --- openerp/tools/yaml_import.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openerp/tools/yaml_import.py b/openerp/tools/yaml_import.py index 5defce5b655..1542f743941 100644 --- a/openerp/tools/yaml_import.py +++ b/openerp/tools/yaml_import.py @@ -381,7 +381,8 @@ class YamlInterpreter(object): return False return val - view = view_info and etree.fromstring(view_info['arch'].encode('utf-8')) or False + view = etree.fromstring(view_info['arch'].encode('utf-8')) if view_info else False + view = view if len(view) else False fields = fields or {} if view is not False: fg = view_info['fields'] From 7d1c5ac4ffad61b5954187a3091e332f3d975880 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Wed, 31 Oct 2012 17:45:12 +0100 Subject: [PATCH 17/17] [FIX] yaml_import: why the hell is False not iterable. bzr revid: vmt@openerp.com-20121031164512-qegyw1q865neivgt --- openerp/tools/yaml_import.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/openerp/tools/yaml_import.py b/openerp/tools/yaml_import.py index 1542f743941..ca7ab92a225 100644 --- a/openerp/tools/yaml_import.py +++ b/openerp/tools/yaml_import.py @@ -381,8 +381,11 @@ class YamlInterpreter(object): return False return val - view = etree.fromstring(view_info['arch'].encode('utf-8')) if view_info else False - view = view if len(view) else False + if view_info: + arch = etree.fromstring(view_info['arch'].encode('utf-8')) + view = arch if len(arch) else False + else: + view = False fields = fields or {} if view is not False: fg = view_info['fields']