From 2562b1c1baa2f11254f24d73eaa6b19a04adfca6 Mon Sep 17 00:00:00 2001 From: Cedric Snauwaert Date: Thu, 6 Nov 2014 09:53:00 +0100 Subject: [PATCH 01/37] [FIX] purchase_requisition: field date_start does not exist on purchase_requisition model so we should not use it in filters fixes #2838 --- addons/purchase_requisition/purchase_requisition_view.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/purchase_requisition/purchase_requisition_view.xml b/addons/purchase_requisition/purchase_requisition_view.xml index 718c9719d02..e510d8afbeb 100644 --- a/addons/purchase_requisition/purchase_requisition_view.xml +++ b/addons/purchase_requisition/purchase_requisition_view.xml @@ -164,7 +164,7 @@ - + From 5cc863245ba873a2a7dcd8e16cece9581796d271 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Thu, 6 Nov 2014 09:59:41 +0100 Subject: [PATCH 02/37] [IMP] fields: improve _determine_default() and add test for inherited fields --- openerp/addons/test_inherit/models.py | 3 ++ .../addons/test_inherit/tests/test_inherit.py | 20 ++++++---- openerp/fields.py | 40 +++++++++++-------- 3 files changed, 39 insertions(+), 24 deletions(-) diff --git a/openerp/addons/test_inherit/models.py b/openerp/addons/test_inherit/models.py index 1a386a28274..422d59fd323 100644 --- a/openerp/addons/test_inherit/models.py +++ b/openerp/addons/test_inherit/models.py @@ -69,4 +69,7 @@ class daughter(models.Model): # simply redeclare the field without adding any option template_id = fields.Many2one() + # change the default value of an inherited field + name = fields.Char(default='Baz') + # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/addons/test_inherit/tests/test_inherit.py b/openerp/addons/test_inherit/tests/test_inherit.py index e9be815d216..6909882b2c6 100644 --- a/openerp/addons/test_inherit/tests/test_inherit.py +++ b/openerp/addons/test_inherit/tests/test_inherit.py @@ -17,19 +17,25 @@ class test_inherits(common.TransactionCase): def test_field_extension(self): """ check the extension of a field in an inherited model """ - # the field mother.name should inherit required=True, and have "Bar" as - # a default value mother = self.env['test.inherit.mother'] + daughter = self.env['test.inherit.daughter'] + + # the field mother.name must have required=True and "Bar" as default field = mother._fields['name'] self.assertTrue(field.required) - self.assertEqual(field.default(mother), "Bar") - self.assertEqual(mother.default_get(['name']), {'name': "Bar"}) self.assertEqual(mother._defaults.get('name'), "Bar") + self.assertEqual(mother.default_get(['name']), {'name': "Bar"}) - # the field daughter.template_id should inherit - # model_name='test.inherit.mother', string='Template', required=True - daughter = self.env['test.inherit.daughter'] + # the field daughter.name must have required=False and "Baz" as default + field = daughter._fields['name'] + self.assertFalse(field.required) + self.assertEqual(field.default(mother), "Baz") + self.assertEqual(daughter._defaults.get('name'), "Baz") + self.assertEqual(daughter.default_get(['name']), {'name': "Baz"}) + + # the field daughter.template_id should have + # comodel_name='test.inherit.mother', string='Template', required=True field = daughter._fields['template_id'] self.assertEqual(field.comodel_name, 'test.inherit.mother') self.assertEqual(field.string, "Template") diff --git a/openerp/fields.py b/openerp/fields.py index 2d5e0c2d6a4..417591c3c53 100644 --- a/openerp/fields.py +++ b/openerp/fields.py @@ -347,30 +347,36 @@ class Field(object): # traverse the class hierarchy upwards, and take the first field # definition with a default or _defaults for self for klass in cls.__mro__: - field = klass.__dict__.get(name, self) - if not isinstance(field, type(self)): - return # klass contains another value overridden by self + if name in klass.__dict__: + field = klass.__dict__[name] + if not isinstance(field, type(self)): + # klass contains another value overridden by self + return - if 'default' in field._attrs: - # take the default in field, and adapt it for cls._defaults - value = field._attrs['default'] - if callable(value): - self.default = value - cls._defaults[name] = lambda model, cr, uid, context: \ - self.convert_to_write(value(model.browse(cr, uid, [], context))) - else: - self.default = lambda recs: value - cls._defaults[name] = value - return + if 'default' in field._attrs: + # take the default in field, and adapt it for cls._defaults + value = field._attrs['default'] + if callable(value): + from openerp import api + self.default = value + cls._defaults[name] = api.model( + lambda recs: self.convert_to_write(value(recs)) + ) + else: + self.default = lambda recs: value + cls._defaults[name] = value + return defaults = klass.__dict__.get('_defaults') or {} if name in defaults: # take the value from _defaults, and adapt it for self.default value = defaults[name] - value_func = value if callable(value) else lambda *args: value + if callable(value): + func = lambda recs: value(recs._model, recs._cr, recs._uid, recs._context) + else: + func = lambda recs: value self.default = lambda recs: self.convert_to_cache( - value_func(recs._model, recs._cr, recs._uid, recs._context), - recs, validate=False, + func(recs), recs, validate=False, ) cls._defaults[name] = value return From 150dc66ec02d68f19520a6f9224384d2562155f9 Mon Sep 17 00:00:00 2001 From: Julien Legros Date: Thu, 6 Nov 2014 11:47:38 +0100 Subject: [PATCH 03/37] [FIX] account_asset: moves should use the depreciation line date --- addons/account_asset/account_asset.py | 2 +- addons/account_asset/account_asset_demo.xml | 2 +- .../account_asset/test/account_asset_demo.yml | 44 ++++++++++++++++++- 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/addons/account_asset/account_asset.py b/addons/account_asset/account_asset.py index 806013e6a4c..9cf4c14fff2 100644 --- a/addons/account_asset/account_asset.py +++ b/addons/account_asset/account_asset.py @@ -399,7 +399,7 @@ class account_asset_depreciation_line(osv.osv): created_move_ids = [] asset_ids = [] for line in self.browse(cr, uid, ids, context=context): - depreciation_date = context.get('depreciation_date') or time.strftime('%Y-%m-%d') + depreciation_date = context.get('depreciation_date') or line.depreciation_date or time.strftime('%Y-%m-%d') ctx = dict(context, account_period_prefer_normal=True) period_ids = period_obj.find(cr, uid, depreciation_date, context=ctx) company_currency = line.asset_id.company_id.currency_id.id diff --git a/addons/account_asset/account_asset_demo.xml b/addons/account_asset/account_asset_demo.xml index 5b1a4b03be6..ec18c4a5df6 100644 --- a/addons/account_asset/account_asset_demo.xml +++ b/addons/account_asset/account_asset_demo.xml @@ -41,7 +41,7 @@ open - + CEO's Car diff --git a/addons/account_asset/test/account_asset_demo.yml b/addons/account_asset/test/account_asset_demo.yml index c0871d05268..56d4d7ce8ac 100644 --- a/addons/account_asset/test/account_asset_demo.yml +++ b/addons/account_asset/test/account_asset_demo.yml @@ -6,4 +6,46 @@ category_id: account_asset_category_sale - !record {model: account.asset.asset, id: account_asset_asset_vehicles0}: - method_number: 10 + method_number: 5 +- + !record {model: account.fiscalyear, id: data_fiscalyear_plus1}: + company_id: base.main_company + date_start: !eval "'%s-01-01' %(datetime.now().year+1)" + date_stop: !eval "'%s-12-31' %(datetime.now().year+1)" + name: !eval "'Fiscal Year X %s' %(datetime.now().year+1)" + code: !eval "'FY%s' %(datetime.now().year+1)" +- + !record {model: account.fiscalyear, id: data_fiscalyear_plus2}: + company_id: base.main_company + date_start: !eval "'%s-01-01' %(datetime.now().year+2)" + date_stop: !eval "'%s-12-31' %(datetime.now().year+2)" + name: !eval "'Fiscal Year X %s' %(datetime.now().year+2)" + code: !eval "'FY%s' %(datetime.now().year+2)" +- + !record {model: account.fiscalyear, id: data_fiscalyear_plus3}: + company_id: base.main_company + date_start: !eval "'%s-01-01' %(datetime.now().year+3)" + date_stop: !eval "'%s-12-31' %(datetime.now().year+3)" + name: !eval "'Fiscal Year X %s' %(datetime.now().year+3)" + code: !eval "'FY%s' %(datetime.now().year+3)" +- + !record {model: account.fiscalyear, id: data_fiscalyear_plus4}: + company_id: base.main_company + date_start: !eval "'%s-01-01' %(datetime.now().year+4)" + date_stop: !eval "'%s-12-31' %(datetime.now().year+4)" + name: !eval "'Fiscal Year X %s' %(datetime.now().year+4)" + code: !eval "'FY%s' %(datetime.now().year+4)" +- + !record {model: account.fiscalyear, id: data_fiscalyear_plus5}: + company_id: base.main_company + date_start: !eval "'%s-01-01' %(datetime.now().year+5)" + date_stop: !eval "'%s-12-31' %(datetime.now().year+5)" + name: !eval "'Fiscal Year X %s' %(datetime.now().year+5)" + code: !eval "'FY%s' %(datetime.now().year+5)" +- + !python {model: account.fiscalyear}: | + self.create_period(cr, uid, [ref("data_fiscalyear_plus1")]) + self.create_period(cr, uid, [ref("data_fiscalyear_plus2")]) + self.create_period(cr, uid, [ref("data_fiscalyear_plus3")]) + self.create_period(cr, uid, [ref("data_fiscalyear_plus4")]) + self.create_period(cr, uid, [ref("data_fiscalyear_plus5")]) From d039249dd2e25eb0fe55dc170ae3c3d8a02ed21a Mon Sep 17 00:00:00 2001 From: celm1990 Date: Thu, 30 Oct 2014 20:54:34 -0500 Subject: [PATCH 04/37] [FIX] fix wrong filter in search view (account/sale) the field section_id is created in addon sale, but used in the account reporting views. This commit moves the search view definition in the correct file. --- .../report/account_invoice_report_view.xml | 1 - addons/sale/__openerp__.py | 1 + addons/sale/report/invoice_report_view.xml | 17 +++++++++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 addons/sale/report/invoice_report_view.xml diff --git a/addons/account/report/account_invoice_report_view.xml b/addons/account/report/account_invoice_report_view.xml index bf95ba39d65..874f2ddda24 100644 --- a/addons/account/report/account_invoice_report_view.xml +++ b/addons/account/report/account_invoice_report_view.xml @@ -73,7 +73,6 @@ - diff --git a/addons/sale/__openerp__.py b/addons/sale/__openerp__.py index 62e7b0466a7..d004d762991 100644 --- a/addons/sale/__openerp__.py +++ b/addons/sale/__openerp__.py @@ -74,6 +74,7 @@ The Dashboard for the Sales Manager will include 'sales_team_view.xml', 'res_partner_view.xml', 'report/sale_report_view.xml', + 'report/invoice_report_view.xml', 'edi/sale_order_action_data.xml', 'res_config_view.xml', 'views/report_saleorder.xml', diff --git a/addons/sale/report/invoice_report_view.xml b/addons/sale/report/invoice_report_view.xml new file mode 100644 index 00000000000..73d50ea98da --- /dev/null +++ b/addons/sale/report/invoice_report_view.xml @@ -0,0 +1,17 @@ + + + + + account.invoice.report.search + account.invoice.report + + + + + + + + + + \ No newline at end of file From d36c8b5c9bf7ab9215bc63db6ee49bec5e5b225b Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Thu, 6 Nov 2014 14:49:25 +0100 Subject: [PATCH 05/37] [FIX] web: FieldMany2ManyBinaryMultiFiles must extend ReinitializeFieldMixin The add attachment button should be displayed while being in edit mode, but not in view mode As the widget depends on the form actual mode, the widget should be re-rendered each time the actual mode changes This is the point of the ReinitializeFieldMixin class --- addons/web/static/src/js/view_form.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index cfada94c4e7..077b05c6096 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -5222,7 +5222,7 @@ instance.web.form.FieldBinaryImage = instance.web.form.FieldBinary.extend({ * Options on attribute ; "blockui" {Boolean} block the UI or not * during the file is uploading */ -instance.web.form.FieldMany2ManyBinaryMultiFiles = instance.web.form.AbstractField.extend({ +instance.web.form.FieldMany2ManyBinaryMultiFiles = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, { template: "FieldBinaryFileUploader", init: function(field_manager, node) { this._super(field_manager, node); @@ -5236,7 +5236,8 @@ instance.web.form.FieldMany2ManyBinaryMultiFiles = instance.web.form.AbstractFie $(window).on(this.fileupload_id, _.bind(this.on_file_loaded, this)); }, start: function() { - this._super(this); + this._super(); + instance.web.form.ReinitializeFieldMixin.start.call(this); this.$el.on('change', 'input.oe_form_binary_file', this.on_file_change ); }, set_value: function(value_) { From 7e73a1071225d952229c2c06c4f3ef57b43ed270 Mon Sep 17 00:00:00 2001 From: "Torvald B. Bringsvor" Date: Wed, 5 Nov 2014 23:55:50 +0100 Subject: [PATCH 06/37] [FIX] account_voucher: multicompany compatibility When the company or the journal is changed, change the period accordingly to make sure to always keep coherant values. --- addons/account_voucher/account_voucher.py | 26 ++++++++++++++++--- .../account_voucher/account_voucher_view.xml | 2 +- .../voucher_payment_receipt_view.xml | 2 +- .../voucher_sales_purchase_view.xml | 2 +- 4 files changed, 26 insertions(+), 6 deletions(-) diff --git a/addons/account_voucher/account_voucher.py b/addons/account_voucher/account_voucher.py index c8be5c0925c..e1483ab0188 100644 --- a/addons/account_voucher/account_voucher.py +++ b/addons/account_voucher/account_voucher.py @@ -69,8 +69,10 @@ class account_voucher(osv.osv): invoice_pool = self.pool.get('account.invoice') journal_pool = self.pool.get('account.journal') if context.get('invoice_id', False): - currency_id = invoice_pool.browse(cr, uid, context['invoice_id'], context=context).currency_id.id - journal_id = journal_pool.search(cr, uid, [('currency', '=', currency_id)], limit=1) + invoice = invoice_pool.browse(cr, uid, context['invoice_id'], context=context) + journal_id = journal_pool.search(cr, uid, [ + ('currency', '=', invoice.currency_id.id), ('company_id', '=', invoice.company_id.id) + ], limit=1, context=context) return journal_id and journal_id[0] or False if context.get('journal_id', False): return context.get('journal_id') @@ -878,7 +880,13 @@ class account_voucher(osv.osv): currency_id = journal.currency.id else: currency_id = journal.company_id.currency_id.id - vals['value'].update({'currency_id': currency_id, 'payment_rate_currency_id': currency_id}) + + period_id = self.pool['account.period'].find(cr, uid, context=dict(context, company_id=company_id)) + vals['value'].update({ + 'currency_id': currency_id, + 'payment_rate_currency_id': currency_id, + 'period_id' : period_id + }) #in case we want to register the payment directly from an invoice, it's confusing to allow to switch the journal #without seeing that the amount is expressed in the journal currency, and not in the invoice currency. So to avoid #this common mistake, we simply reset the amount to 0 if the currency is not the invoice currency. @@ -891,6 +899,18 @@ class account_voucher(osv.osv): vals[key].update(res[key]) return vals + def onchange_company(self, cr, uid, ids, partner_id, journal_id, currency_id, company_id, context=None): + """ + If the company changes, check that the journal is in the right company. + If not, fetch a new journal. + """ + journal_pool = self.pool['account.journal'] + journal = journal_pool.browse(cr, uid, journal_id, context=context) + if journal.company_id.id != company_id: + # can not guess type of journal, better remove it + return {'value': {'journal_id': False}} + return {} + def button_proforma_voucher(self, cr, uid, ids, context=None): self.signal_workflow(cr, uid, ids, 'proforma_voucher') return {'type': 'ir.actions.act_window_close'} diff --git a/addons/account_voucher/account_voucher_view.xml b/addons/account_voucher/account_voucher_view.xml index 2b0508a7542..24c566481d8 100644 --- a/addons/account_voucher/account_voucher_view.xml +++ b/addons/account_voucher/account_voucher_view.xml @@ -55,7 +55,7 @@ - + diff --git a/addons/account_voucher/voucher_payment_receipt_view.xml b/addons/account_voucher/voucher_payment_receipt_view.xml index dd316ea2600..b933de11e6e 100644 --- a/addons/account_voucher/voucher_payment_receipt_view.xml +++ b/addons/account_voucher/voucher_payment_receipt_view.xml @@ -435,7 +435,7 @@ - + - + From c794562fa67c31edf316c9348b76b9bd5de84d67 Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Thu, 6 Nov 2014 15:32:57 +0100 Subject: [PATCH 07/37] [FIX] calendar: gantt view argument There is no 'date' field. Fixes #3496 --- addons/calendar/calendar_view.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/calendar/calendar_view.xml b/addons/calendar/calendar_view.xml index 29cc3e90e9c..3dde4743cde 100644 --- a/addons/calendar/calendar_view.xml +++ b/addons/calendar/calendar_view.xml @@ -240,7 +240,7 @@ CRM - Meetings Gantt calendar.event - + From 1c1e069f7e9eb4a2d2c734c5e40fd9f458ea6732 Mon Sep 17 00:00:00 2001 From: Frederic van der Essen Date: Thu, 6 Nov 2014 16:02:50 +0100 Subject: [PATCH 08/37] [IMP] point_of_sale: add a zero default margin when printing a receipt from the browser. --- addons/point_of_sale/static/src/css/pos.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/addons/point_of_sale/static/src/css/pos.css b/addons/point_of_sale/static/src/css/pos.css index da9aadc9b43..036f7a73eda 100644 --- a/addons/point_of_sale/static/src/css/pos.css +++ b/addons/point_of_sale/static/src/css/pos.css @@ -1113,6 +1113,10 @@ td { word-wrap: break-word; } +@page { + margin: 0; +} + @media print { .oe_leftbar, .pos .pos-topheader, From ae99a93b8844418ec1ac113ab9c946fba950b1e2 Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Thu, 6 Nov 2014 16:26:32 +0100 Subject: [PATCH 09/37] [FIX] web: many2many_binary widget upload once widget extended with ReinitializeFieldMixin, the event binding with the binary file input and the on_file_change method can be done in initialize_content instead of start This fix is related to d36c8b5c9bf7ab9215bc63db6ee49bec5e5b225b --- addons/web/static/src/js/view_form.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index 077b05c6096..819920f89b3 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -5235,9 +5235,7 @@ instance.web.form.FieldMany2ManyBinaryMultiFiles = instance.web.form.AbstractFie this.fileupload_id = _.uniqueId('oe_fileupload_temp'); $(window).on(this.fileupload_id, _.bind(this.on_file_loaded, this)); }, - start: function() { - this._super(); - instance.web.form.ReinitializeFieldMixin.start.call(this); + initialize_content: function() { this.$el.on('change', 'input.oe_form_binary_file', this.on_file_change ); }, set_value: function(value_) { From 54140331f3c38acaad15c401f118c8fabbdb7bc7 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Thu, 6 Nov 2014 16:00:58 +0100 Subject: [PATCH 10/37] [FIX] fields: fix the translations of the labels of inherited fields --- openerp/addons/base/ir/ir_translation.py | 4 ++-- openerp/fields.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/openerp/addons/base/ir/ir_translation.py b/openerp/addons/base/ir/ir_translation.py index a16933b7f63..d52513c87d9 100644 --- a/openerp/addons/base/ir/ir_translation.py +++ b/openerp/addons/base/ir/ir_translation.py @@ -400,7 +400,7 @@ class ir_translation(osv.osv): main_lang = 'en_US' translatable_fields = [] for k, f in trans_model._fields.items(): - if f.translate: + if getattr(f, 'translate', False): if f.inherited: parent_id = trans_model.read(cr, uid, [id], [f.related[0]], context=context)[0][f.related[0]][0] translatable_fields.append({'name': k, 'id': parent_id, 'model': f.base_field.model}) @@ -434,7 +434,7 @@ class ir_translation(osv.osv): if field: f = trans_model._fields[field] action['context'] = { - 'search_default_name': "%s,%s" % (f.base_field.model, field) + 'search_default_name': "%s,%s" % (f.base_field.model_name, field) } return action diff --git a/openerp/fields.py b/openerp/fields.py index 417591c3c53..58838bf0531 100644 --- a/openerp/fields.py +++ b/openerp/fields.py @@ -623,7 +623,8 @@ class Field(object): def _description_string(self, env): if self.string and env.lang: - name = "%s,%s" % (self.model_name, self.name) + field = self.base_field + name = "%s,%s" % (field.model_name, field.name) trans = env['ir.translation']._get_source(name, 'field', env.lang) return trans or self.string return self.string From b573077be66a5f2fead9653a67a54d4bc7164c29 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Thu, 6 Nov 2014 16:16:00 +0100 Subject: [PATCH 11/37] [FIX] models: do not copy translations of fields that are not copied Fixes #3272. --- openerp/models.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openerp/models.py b/openerp/models.py index 5cce6a6e0a9..0dd16a69d1c 100644 --- a/openerp/models.py +++ b/openerp/models.py @@ -4666,6 +4666,8 @@ class BaseModel(object): trans_obj = self.pool.get('ir.translation') for field_name, field in self._fields.iteritems(): + if not field.copy: + continue # removing the lang to compare untranslated values context_wo_lang = dict(context, lang=None) old_record, new_record = self.browse(cr, uid, [old_id, new_id], context=context_wo_lang) From c22ed1390bcebc3ed5797de874a6c6eadc0cb760 Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Thu, 6 Nov 2014 18:05:28 +0100 Subject: [PATCH 12/37] [FIX] project: access task without project Allow access to task without a project defined if the user is follower. Fixes #3450 --- addons/project/security/project_security.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/addons/project/security/project_security.xml b/addons/project/security/project_security.xml index 649f518de84..444a9d03b40 100644 --- a/addons/project/security/project_security.xml +++ b/addons/project/security/project_security.xml @@ -82,7 +82,9 @@ ['|', ('project_id.privacy_visibility', 'in', ['public', 'employees']), '&', - ('project_id.privacy_visibility', '=', 'followers'), + '|', + ('project_id', '=', False), + ('project_id.privacy_visibility', '=', 'followers'), ('message_follower_ids', 'in', [user.partner_id.id]), ] From 0c74b9d02758b33ff7639be81baa8a5fef415705 Mon Sep 17 00:00:00 2001 From: David Monjoie Date: Thu, 6 Nov 2014 13:20:44 +0100 Subject: [PATCH 13/37] [FIX] product: pricelist shouldn't convert uom when not needed or incompatible uom types Otherwise the Search More view will crash for example for sale order line because it will fetch the price of all products and then try to convert uoms for pricelist rule application without checking uom compatibility --- addons/product/pricelist.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/addons/product/pricelist.py b/addons/product/pricelist.py index 35be803ea39..34a87b8beaf 100644 --- a/addons/product/pricelist.py +++ b/addons/product/pricelist.py @@ -24,6 +24,7 @@ import time from openerp import tools from openerp.osv import fields, osv from openerp.tools.translate import _ +from openerp.exceptions import except_orm import openerp.addons.decimal_precision as dp @@ -251,8 +252,11 @@ class product_pricelist(osv.osv): price = False rule_id = False for rule in items: - if 'uom' in context: - qty_in_product_uom = product_uom_obj._compute_qty(cr, uid, context['uom'], qty, product.uom_id.id or product.uos_id.id) + if 'uom' in context and product.uom_id and context['uom'] != product.uom_id.id: + try: + qty_in_product_uom = product_uom_obj._compute_qty(cr, uid, context['uom'], qty, product.uom_id.id, dict(context.items() + [('raise-exception', False)])) + except except_orm: + qty_in_product_uom = qty else: qty_in_product_uom = qty if rule.min_quantity and qty_in_product_uom Date: Fri, 7 Nov 2014 11:27:37 +0100 Subject: [PATCH 14/37] [IMP] doc-ws: prepend setup code when copying from a snippet --- doc/_themes/odoodoc/layout.html | 2 +- .../odoodoc/static/ZeroClipboard.min.js | 9 - doc/_themes/odoodoc/static/ZeroClipboard.swf | Bin 2157 -> 0 bytes doc/_themes/odoodoc/static/app.js | 170 +++++++++--------- .../zeroclipboard-2.1.6/ZeroClipboard.min.js | 9 + .../zeroclipboard-2.1.6/ZeroClipboard.swf | Bin 0 -> 4036 bytes doc/modules/api_integration.rst | 71 +++++++- 7 files changed, 162 insertions(+), 99 deletions(-) delete mode 100644 doc/_themes/odoodoc/static/ZeroClipboard.min.js delete mode 100644 doc/_themes/odoodoc/static/ZeroClipboard.swf create mode 100644 doc/_themes/odoodoc/static/zeroclipboard-2.1.6/ZeroClipboard.min.js create mode 100644 doc/_themes/odoodoc/static/zeroclipboard-2.1.6/ZeroClipboard.swf diff --git a/doc/_themes/odoodoc/layout.html b/doc/_themes/odoodoc/layout.html index 8abb3fd7b69..7685d8af25f 100644 --- a/doc/_themes/odoodoc/layout.html +++ b/doc/_themes/odoodoc/layout.html @@ -6,7 +6,7 @@ '_static/bootstrap/js/tooltip.js', '_static/bootstrap/js/collapse.js', '_static/bootstrap/js/dropdown.js', - '_static/ZeroClipboard.min.js', + '_static/zeroclipboard-2.1.6/ZeroClipboard.min.js', '_static/app.js' ] %} diff --git a/doc/_themes/odoodoc/static/ZeroClipboard.min.js b/doc/_themes/odoodoc/static/ZeroClipboard.min.js deleted file mode 100644 index 56402340713..00000000000 --- a/doc/_themes/odoodoc/static/ZeroClipboard.min.js +++ /dev/null @@ -1,9 +0,0 @@ -/*! -* ZeroClipboard -* The ZeroClipboard library provides an easy way to copy text to the clipboard using an invisible Adobe Flash movie and a JavaScript interface. -* Copyright (c) 2014 Jon Rohan, James M. Greene -* Licensed MIT -* http://zeroclipboard.org/ -* v1.3.5 -*/ -!function(a){"use strict";function b(a){return a.replace(/,/g,".").replace(/[^0-9\.]/g,"")}function c(a){return parseFloat(b(a))>=10}var d,e={bridge:null,version:"0.0.0",disabled:null,outdated:null,ready:null},f={},g=0,h={},i=0,j={},k=null,l=null,m=function(){var a,b,c,d,e="ZeroClipboard.swf";if(document.currentScript&&(d=document.currentScript.src));else{var f=document.getElementsByTagName("script");if("readyState"in f[0])for(a=f.length;a--&&("interactive"!==f[a].readyState||!(d=f[a].src)););else if("loading"===document.readyState)d=f[f.length-1].src;else{for(a=f.length;a--;){if(c=f[a].src,!c){b=null;break}if(c=c.split("#")[0].split("?")[0],c=c.slice(0,c.lastIndexOf("/")+1),null==b)b=c;else if(b!==c){b=null;break}}null!==b&&(d=b)}}return d&&(d=d.split("#")[0].split("?")[0],e=d.slice(0,d.lastIndexOf("/")+1)+e),e}(),n=function(){var a=/\-([a-z])/g,b=function(a,b){return b.toUpperCase()};return function(c){return c.replace(a,b)}}(),o=function(b,c){var d,e,f;return a.getComputedStyle?d=a.getComputedStyle(b,null).getPropertyValue(c):(e=n(c),d=b.currentStyle?b.currentStyle[e]:b.style[e]),"cursor"!==c||d&&"auto"!==d||(f=b.tagName.toLowerCase(),"a"!==f)?d:"pointer"},p=function(b){b||(b=a.event);var c;this!==a?c=this:b.target?c=b.target:b.srcElement&&(c=b.srcElement),K.activate(c)},q=function(a,b,c){a&&1===a.nodeType&&(a.addEventListener?a.addEventListener(b,c,!1):a.attachEvent&&a.attachEvent("on"+b,c))},r=function(a,b,c){a&&1===a.nodeType&&(a.removeEventListener?a.removeEventListener(b,c,!1):a.detachEvent&&a.detachEvent("on"+b,c))},s=function(a,b){if(!a||1!==a.nodeType)return a;if(a.classList)return a.classList.contains(b)||a.classList.add(b),a;if(b&&"string"==typeof b){var c=(b||"").split(/\s+/);if(1===a.nodeType)if(a.className){for(var d=" "+a.className+" ",e=a.className,f=0,g=c.length;g>f;f++)d.indexOf(" "+c[f]+" ")<0&&(e+=" "+c[f]);a.className=e.replace(/^\s+|\s+$/g,"")}else a.className=b}return a},t=function(a,b){if(!a||1!==a.nodeType)return a;if(a.classList)return a.classList.contains(b)&&a.classList.remove(b),a;if(b&&"string"==typeof b||void 0===b){var c=(b||"").split(/\s+/);if(1===a.nodeType&&a.className)if(b){for(var d=(" "+a.className+" ").replace(/[\n\t]/g," "),e=0,f=c.length;f>e;e++)d=d.replace(" "+c[e]+" "," ");a.className=d.replace(/^\s+|\s+$/g,"")}else a.className=""}return a},u=function(){var a,b,c,d=1;return"function"==typeof document.body.getBoundingClientRect&&(a=document.body.getBoundingClientRect(),b=a.right-a.left,c=document.body.offsetWidth,d=Math.round(b/c*100)/100),d},v=function(b,c){var d={left:0,top:0,width:0,height:0,zIndex:B(c)-1};if(b.getBoundingClientRect){var e,f,g,h=b.getBoundingClientRect();"pageXOffset"in a&&"pageYOffset"in a?(e=a.pageXOffset,f=a.pageYOffset):(g=u(),e=Math.round(document.documentElement.scrollLeft/g),f=Math.round(document.documentElement.scrollTop/g));var i=document.documentElement.clientLeft||0,j=document.documentElement.clientTop||0;d.left=h.left+e-i,d.top=h.top+f-j,d.width="width"in h?h.width:h.right-h.left,d.height="height"in h?h.height:h.bottom-h.top}return d},w=function(a,b){var c=null==b||b&&b.cacheBust===!0&&b.useNoCache===!0;return c?(-1===a.indexOf("?")?"?":"&")+"noCache="+(new Date).getTime():""},x=function(b){var c,d,e,f=[],g=[],h=[];if(b.trustedOrigins&&("string"==typeof b.trustedOrigins?g.push(b.trustedOrigins):"object"==typeof b.trustedOrigins&&"length"in b.trustedOrigins&&(g=g.concat(b.trustedOrigins))),b.trustedDomains&&("string"==typeof b.trustedDomains?g.push(b.trustedDomains):"object"==typeof b.trustedDomains&&"length"in b.trustedDomains&&(g=g.concat(b.trustedDomains))),g.length)for(c=0,d=g.length;d>c;c++)if(g.hasOwnProperty(c)&&g[c]&&"string"==typeof g[c]){if(e=E(g[c]),!e)continue;if("*"===e){h=[e];break}h.push.apply(h,[e,"//"+e,a.location.protocol+"//"+e])}return h.length&&f.push("trustedOrigins="+encodeURIComponent(h.join(","))),"string"==typeof b.jsModuleId&&b.jsModuleId&&f.push("jsModuleId="+encodeURIComponent(b.jsModuleId)),f.join("&")},y=function(a,b,c){if("function"==typeof b.indexOf)return b.indexOf(a,c);var d,e=b.length;for("undefined"==typeof c?c=0:0>c&&(c=e+c),d=c;e>d;d++)if(b.hasOwnProperty(d)&&b[d]===a)return d;return-1},z=function(a){if("string"==typeof a)throw new TypeError("ZeroClipboard doesn't accept query strings.");return a.length?a:[a]},A=function(b,c,d,e){e?a.setTimeout(function(){b.apply(c,d)},0):b.apply(c,d)},B=function(a){var b,c;return a&&("number"==typeof a&&a>0?b=a:"string"==typeof a&&(c=parseInt(a,10))&&!isNaN(c)&&c>0&&(b=c)),b||("number"==typeof N.zIndex&&N.zIndex>0?b=N.zIndex:"string"==typeof N.zIndex&&(c=parseInt(N.zIndex,10))&&!isNaN(c)&&c>0&&(b=c)),b||0},C=function(a,b){if(a&&b!==!1&&"undefined"!=typeof console&&console&&(console.warn||console.log)){var c="`"+a+"` is deprecated. See docs for more info:\n https://github.com/zeroclipboard/zeroclipboard/blob/master/docs/instructions.md#deprecations";console.warn?console.warn(c):console.log(c)}},D=function(){var a,b,c,d,e,f,g=arguments[0]||{};for(a=1,b=arguments.length;b>a;a++)if(null!=(c=arguments[a]))for(d in c)if(c.hasOwnProperty(d)){if(e=g[d],f=c[d],g===f)continue;void 0!==f&&(g[d]=f)}return g},E=function(a){if(null==a||""===a)return null;if(a=a.replace(/^\s+|\s+$/g,""),""===a)return null;var b=a.indexOf("//");a=-1===b?a:a.slice(b+2);var c=a.indexOf("/");return a=-1===c?a:-1===b||0===c?null:a.slice(0,c),a&&".swf"===a.slice(-4).toLowerCase()?null:a||null},F=function(){var a=function(a,b){var c,d,e;if(null!=a&&"*"!==b[0]&&("string"==typeof a&&(a=[a]),"object"==typeof a&&"length"in a))for(c=0,d=a.length;d>c;c++)if(a.hasOwnProperty(c)&&(e=E(a[c]))){if("*"===e){b.length=0,b.push("*");break}-1===y(e,b)&&b.push(e)}},b={always:"always",samedomain:"sameDomain",never:"never"};return function(c,d){var e,f=d.allowScriptAccess;if("string"==typeof f&&(e=f.toLowerCase())&&/^always|samedomain|never$/.test(e))return b[e];var g=E(d.moviePath);null===g&&(g=c);var h=[];a(d.trustedOrigins,h),a(d.trustedDomains,h);var i=h.length;if(i>0){if(1===i&&"*"===h[0])return"always";if(-1!==y(c,h))return 1===i&&c===g?"sameDomain":"always"}return"never"}}(),G=function(a){if(null==a)return[];if(Object.keys)return Object.keys(a);var b=[];for(var c in a)a.hasOwnProperty(c)&&b.push(c);return b},H=function(a){if(a)for(var b in a)a.hasOwnProperty(b)&&delete a[b];return a},I=function(){try{return document.activeElement}catch(a){}return null},J=function(){var a=!1;if("boolean"==typeof e.disabled)a=e.disabled===!1;else{if("function"==typeof ActiveXObject)try{new ActiveXObject("ShockwaveFlash.ShockwaveFlash")&&(a=!0)}catch(b){}!a&&navigator.mimeTypes["application/x-shockwave-flash"]&&(a=!0)}return a},K=function(a,b){return this instanceof K?(this.id=""+g++,h[this.id]={instance:this,elements:[],handlers:{}},a&&this.clip(a),"undefined"!=typeof b&&(C("new ZeroClipboard(elements, options)",N.debug),K.config(b)),this.options=K.config(),"boolean"!=typeof e.disabled&&(e.disabled=!J()),e.disabled===!1&&e.outdated!==!0&&null===e.bridge&&(e.outdated=!1,e.ready=!1,O()),void 0):new K(a,b)};K.prototype.setText=function(a){return a&&""!==a&&(f["text/plain"]=a,e.ready===!0&&e.bridge&&"function"==typeof e.bridge.setText?e.bridge.setText(a):e.ready=!1),this},K.prototype.setSize=function(a,b){return e.ready===!0&&e.bridge&&"function"==typeof e.bridge.setSize?e.bridge.setSize(a,b):e.ready=!1,this};var L=function(a){e.ready===!0&&e.bridge&&"function"==typeof e.bridge.setHandCursor?e.bridge.setHandCursor(a):e.ready=!1};K.prototype.destroy=function(){this.unclip(),this.off(),delete h[this.id]};var M=function(){var a,b,c,d=[],e=G(h);for(a=0,b=e.length;b>a;a++)c=h[e[a]].instance,c&&c instanceof K&&d.push(c);return d};K.version="1.3.5";var N={swfPath:m,trustedDomains:a.location.host?[a.location.host]:[],cacheBust:!0,forceHandCursor:!1,zIndex:999999999,debug:!0,title:null,autoActivate:!0};K.config=function(a){"object"==typeof a&&null!==a&&D(N,a);{if("string"!=typeof a||!a){var b={};for(var c in N)N.hasOwnProperty(c)&&(b[c]="object"==typeof N[c]&&null!==N[c]?"length"in N[c]?N[c].slice(0):D({},N[c]):N[c]);return b}if(N.hasOwnProperty(a))return N[a]}},K.destroy=function(){K.deactivate();for(var a in h)if(h.hasOwnProperty(a)&&h[a]){var b=h[a].instance;b&&"function"==typeof b.destroy&&b.destroy()}var c=P(e.bridge);c&&c.parentNode&&(c.parentNode.removeChild(c),e.ready=null,e.bridge=null)},K.activate=function(a){d&&(t(d,N.hoverClass),t(d,N.activeClass)),d=a,s(a,N.hoverClass),Q();var b=N.title||a.getAttribute("title");if(b){var c=P(e.bridge);c&&c.setAttribute("title",b)}var f=N.forceHandCursor===!0||"pointer"===o(a,"cursor");L(f)},K.deactivate=function(){var a=P(e.bridge);a&&(a.style.left="0px",a.style.top="-9999px",a.removeAttribute("title")),d&&(t(d,N.hoverClass),t(d,N.activeClass),d=null)};var O=function(){var b,c,d=document.getElementById("global-zeroclipboard-html-bridge");if(!d){var f=K.config();f.jsModuleId="string"==typeof k&&k||"string"==typeof l&&l||null;var g=F(a.location.host,N),h=x(f),i=N.moviePath+w(N.moviePath,N),j=' ';d=document.createElement("div"),d.id="global-zeroclipboard-html-bridge",d.setAttribute("class","global-zeroclipboard-container"),d.style.position="absolute",d.style.left="0px",d.style.top="-9999px",d.style.width="15px",d.style.height="15px",d.style.zIndex=""+B(N.zIndex),document.body.appendChild(d),d.innerHTML=j}b=document["global-zeroclipboard-flash-bridge"],b&&(c=b.length)&&(b=b[c-1]),e.bridge=b||d.children[0].lastElementChild},P=function(a){for(var b=/^OBJECT|EMBED$/,c=a&&a.parentNode;c&&b.test(c.nodeName)&&c.parentNode;)c=c.parentNode;return c||null},Q=function(){if(d){var a=v(d,N.zIndex),b=P(e.bridge);b&&(b.style.top=a.top+"px",b.style.left=a.left+"px",b.style.width=a.width+"px",b.style.height=a.height+"px",b.style.zIndex=a.zIndex+1),e.ready===!0&&e.bridge&&"function"==typeof e.bridge.setSize?e.bridge.setSize(a.width,a.height):e.ready=!1}return this};K.prototype.on=function(a,b){var c,d,f,g={},i=h[this.id]&&h[this.id].handlers;if("string"==typeof a&&a)f=a.toLowerCase().split(/\s+/);else if("object"==typeof a&&a&&"undefined"==typeof b)for(c in a)a.hasOwnProperty(c)&&"string"==typeof c&&c&&"function"==typeof a[c]&&this.on(c,a[c]);if(f&&f.length){for(c=0,d=f.length;d>c;c++)a=f[c].replace(/^on/,""),g[a]=!0,i[a]||(i[a]=[]),i[a].push(b);g.noflash&&e.disabled&&T.call(this,"noflash",{}),g.wrongflash&&e.outdated&&T.call(this,"wrongflash",{flashVersion:e.version}),g.load&&e.ready&&T.call(this,"load",{flashVersion:e.version})}return this},K.prototype.off=function(a,b){var c,d,e,f,g,i=h[this.id]&&h[this.id].handlers;if(0===arguments.length)f=G(i);else if("string"==typeof a&&a)f=a.split(/\s+/);else if("object"==typeof a&&a&&"undefined"==typeof b)for(c in a)a.hasOwnProperty(c)&&"string"==typeof c&&c&&"function"==typeof a[c]&&this.off(c,a[c]);if(f&&f.length)for(c=0,d=f.length;d>c;c++)if(a=f[c].toLowerCase().replace(/^on/,""),g=i[a],g&&g.length)if(b)for(e=y(b,g);-1!==e;)g.splice(e,1),e=y(b,g,e);else i[a].length=0;return this},K.prototype.handlers=function(a){var b,c=null,d=h[this.id]&&h[this.id].handlers;if(d){if("string"==typeof a&&a)return d[a]?d[a].slice(0):null;c={};for(b in d)d.hasOwnProperty(b)&&d[b]&&(c[b]=d[b].slice(0))}return c};var R=function(b,c,d,e){var f=h[this.id]&&h[this.id].handlers[b];if(f&&f.length){var g,i,j,k=c||this;for(g=0,i=f.length;i>g;g++)j=f[g],c=k,"string"==typeof j&&"function"==typeof a[j]&&(j=a[j]),"object"==typeof j&&j&&"function"==typeof j.handleEvent&&(c=j,j=j.handleEvent),"function"==typeof j&&A(j,c,d,e)}return this};K.prototype.clip=function(a){a=z(a);for(var b=0;bd;d++)f=h[c[d]].instance,f&&f instanceof K&&g.push(f);return g};N.hoverClass="zeroclipboard-is-hover",N.activeClass="zeroclipboard-is-active",N.trustedOrigins=null,N.allowScriptAccess=null,N.useNoCache=!0,N.moviePath="ZeroClipboard.swf",K.detectFlashSupport=function(){return C("ZeroClipboard.detectFlashSupport",N.debug),J()},K.dispatch=function(a,b){if("string"==typeof a&&a){var c=a.toLowerCase().replace(/^on/,"");if(c)for(var e=d&&N.autoActivate===!0?S(d):M(),f=0,g=e.length;g>f;f++)T.call(e[f],c,b)}},K.prototype.setHandCursor=function(a){return C("ZeroClipboard.prototype.setHandCursor",N.debug),a="boolean"==typeof a?a:!!a,L(a),N.forceHandCursor=a,this},K.prototype.reposition=function(){return C("ZeroClipboard.prototype.reposition",N.debug),Q()},K.prototype.receiveEvent=function(a,b){if(C("ZeroClipboard.prototype.receiveEvent",N.debug),"string"==typeof a&&a){var c=a.toLowerCase().replace(/^on/,"");c&&T.call(this,c,b)}},K.prototype.setCurrent=function(a){return C("ZeroClipboard.prototype.setCurrent",N.debug),K.activate(a),this},K.prototype.resetBridge=function(){return C("ZeroClipboard.prototype.resetBridge",N.debug),K.deactivate(),this},K.prototype.setTitle=function(a){if(C("ZeroClipboard.prototype.setTitle",N.debug),a=a||N.title||d&&d.getAttribute("title")){var b=P(e.bridge);b&&b.setAttribute("title",a)}return this},K.setDefaults=function(a){C("ZeroClipboard.setDefaults",N.debug),K.config(a)},K.prototype.addEventListener=function(a,b){return C("ZeroClipboard.prototype.addEventListener",N.debug),this.on(a,b)},K.prototype.removeEventListener=function(a,b){return C("ZeroClipboard.prototype.removeEventListener",N.debug),this.off(a,b)},K.prototype.ready=function(){return C("ZeroClipboard.prototype.ready",N.debug),e.ready===!0};var T=function(a,g){a=a.toLowerCase().replace(/^on/,"");var h=g&&g.flashVersion&&b(g.flashVersion)||null,i=d,j=!0;switch(a){case"load":if(h){if(!c(h))return T.call(this,"onWrongFlash",{flashVersion:h}),void 0;e.outdated=!1,e.ready=!0,e.version=h}break;case"wrongflash":h&&!c(h)&&(e.outdated=!0,e.ready=!1,e.version=h);break;case"mouseover":s(i,N.hoverClass);break;case"mouseout":N.autoActivate===!0&&K.deactivate();break;case"mousedown":s(i,N.activeClass);break;case"mouseup":t(i,N.activeClass);break;case"datarequested":if(i){var k=i.getAttribute("data-clipboard-target"),l=k?document.getElementById(k):null;if(l){var m=l.value||l.textContent||l.innerText;m&&this.setText(m)}else{var n=i.getAttribute("data-clipboard-text");n&&this.setText(n)}}j=!1;break;case"complete":H(f),i&&i!==I()&&i.focus&&i.focus()}var o=i,p=[this,g];return R.call(this,a,o,p,j)};"function"==typeof define&&define.amd?define(["require","exports","module"],function(a,b,c){return k=c&&c.id||null,K}):"object"==typeof module&&module&&"object"==typeof module.exports&&module.exports&&"function"==typeof a.require?(l=module.id||null,module.exports=K):a.ZeroClipboard=K}(function(){return this}()); \ No newline at end of file diff --git a/doc/_themes/odoodoc/static/ZeroClipboard.swf b/doc/_themes/odoodoc/static/ZeroClipboard.swf deleted file mode 100644 index 55ccf9608ed3cf01db575de40c0be1bfc5440a80..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2157 zcmV-z2$J_hS5pr43;+Ol+I?2tQ`^`TzgL!YC1D%efJ4Zavmqf6femD{3n3(6;{X9n zFeKYFAHJ5aY$ZZ+C7F+QI?e2Kr+-1;voC$x_N9IAAJCbOOlEiA`_Sq1t^YvNBbfl+ zZmU7Z=lpcO&$$u@i2oZx@!ugdh*2SV9wGFrzQ16E=A4>7yHzM=4t7n;odxOMNW=4* zv)Sza{{F=N^n~ryvy(G3Guhl!c4}%IG{)US%cBS5mV0evK6FrEuI3m`&#{_lP_CUOI)A228dy(g_HMT6 z*tM1hIqBdkG(Kw*xM;Sjrr|c2GvBh_SoVG=#3>r+Xbz*E-L3Hv3!a!p)0c zKSSZQ_^&_wGY=F0tMZIFcHT6aRhv3B^ymIRDG0#>q&sw%O`-&vK-}AZ|4~F7I=)OK zmOaO?>SC$-nrU7%oWum%3&y9c+bx$Z1w6lJ8#S_M+a{xyxY63JGAGs*F4nQE8E(_0 zhhn+u7#B$F-dREgL^)QV^GVS|hnp+FD;&dsSE|Z!T=-mzTEEU1?5yRJ^!a zezjgI?5r)ldb+i+xw%w0V-t6&Wq9DX(;R7+d9*-1sx+zNvd1mv97gNRD+VP$0qDj} zy=7_PDY+T;PR&rK?y8y7dPg(AP;O;1d(_EvC6u|BaoEpVhQn^2QNF8;WHLXV_}KuU z$Nh3t++y{mgC_U~%CmYI={@VNXY~@r)XW_Ut%pscRn2@IAHVi$;7vC4a*6;)uHjVMGZNri~ z4lL$Hced7Gdz1YVAD&uKw-YbD318|JP)5WFc5mCo@A;>6$(oUXl8cWAR=Xs%pk;H+qvrV?iQ)Tnulo{(K;M!n%l zHHYqRLEE5!W`jz!R?9aGvnJ%%R`QP$Ach@WGaxk>jIv(ZDKEV$Jz3h4kJ+8=W3W(q zx*>Jgoz3|16b_&~i<-%$V4(rtuM1qQ{RC~h*O%cnT<$@D?veXhRXw!ri@m_GW z;Z;bt;4rYDPr`msi`WRu;Gy4U?+=C@>)MOte{E6v$Cs}!jnjNu8I%Pi-AIoL=p0Uu z56MFlL)oF+(B#lmdM5pxiwjtkWKQ9GqOo{yVw+%&;0WOaf<=M}!4knTiS!U5N`x2@ z;zZ~rLV^fMBBY4WM{qyE0|cjuaE=IrL>MB%1tMG|!X+YHCiof=M+g}s_!hyctYU(v z+_wE(<|~a?Z&ohpmv``6?)!Esh0y!9euZX$UZtM^y+((JktQ5{ zsfmd`gygDN6)uC>`zn7_<@M|I`jjXn|Nh~FBuc`2@EzeIk~|K+-*VrzQyGNX>ZipU zco-K)FmN~VSKqc%(32OV?bT6;`rQ#2@ex^)e~rV81h~Z=1o_lVd{jISI%682(h!0~ zNc&4oN~{HmNL2!f2<@s+9K)#@gg_kx{dxuerg#p6lPVuR+Z#*4j7Q;d0O;4@VO9Jl zNubuMP!*v9i6nmy6-W{ZDi8!-R=(zf5^jC(ub_&m&eL(A59q{;tgq+v$zm42&3+pM z)Ti3|ba99a3ej&vC<#IV_gi)On^ZqSz^W_7PcgWv+|fVFyf1yu9qBVCiZh`iY>p3n zqsMZ`xlQi^5aS}U(t8Z7 z13jV}0LKM@M+3Mg1)E=40jGMpeFSo;#3!O3p@H^MsT2W&UJhgFnxA8+!sRNPxys{Q jWh+-H=PDBXeSt28SA&3RsxNr4bpJQ|0FeIxQ+=8*M*tao diff --git a/doc/_themes/odoodoc/static/app.js b/doc/_themes/odoodoc/static/app.js index a03d3b2ad27..36a2dcff57e 100644 --- a/doc/_themes/odoodoc/static/app.js +++ b/doc/_themes/odoodoc/static/app.js @@ -25,7 +25,92 @@ $(function () { }); }, 100); - // lang switcher + /* + for clipboard: + * add per-language setup code to document, hidden + * adds button to each switchable language block except when they're setup + stuff because fuck'em + * per-language, add clipboard hook to prefix with setup bit on-copy + * setup bit is... ? + * actually not all blocks because we don't want to add the setup bits to + the setup bits, so that's kinda shit + */ + document.addEventListener('copy', function (e) { + var target = $(e.target).closest('.switchable:not(.setup)').get(0); + // not in a switchable + if (!target) { return; } + var lang = getHighlightLanguage(target); + if (!lang) { + // switchable without highlight (e.g. language-specific notes), + // don't munge + return; + } + e.preventDefault(); + + // get generic setup code + var prefix = document.querySelector('.setupcode.highlight-' + lang).textContent; + + // prepend setup code to current snippet, get all of current snippet + // in case only part of it was selected + var data = prefix + target.textContent; + // sane browsers + e.clipboardData.setData('text/plain', data); + // MSIE + e.clipboardData.setData('Text', data); + }); + + // stripe page stuff + if ($('div.document-super').hasClass('stripe')) { (function () { + // iterate on highlighted PL blocks (but not results because that'd + // be gross), extract all switchable PLs in the document and add + // clipboard-copy buttons + var languages = {}; + $('div.switchable').each(function () { + var language = getHighlightLanguage(this); + if (language) { + languages[language] = true; + } + }); + + // if can't find CSS where base rule lives something's probably + // broken, bail + var sheet = findSheet(/style\.css$/); + if (!sheet) { return; } + // build PL switcher UI and hook toggle event + $(buildSwitcher(Object.keys(languages))) + .prependTo('div.documentwrapper') + .on('click', 'li', function (e) { + $(e.target).addClass('active') + .siblings().removeClass('active'); + var id = e.target.textContent; + var lastIndex = sheet.cssRules.length - 1; + var content = sheet.cssRules[lastIndex].style.cssText; + // change rule in CSS because why not (also can add new + // languages without having to e.g. change CSS or anything) + var sel = [ + '.stripe .only-', id, ', ', + '.stripe .highlight-', id, ' > .highlight' + ].join(''); + sheet.deleteRule(lastIndex); + sheet.insertRule(sel + '{' + content + '}', lastIndex); + }); + })(); } + + /** + * @param {Node} node highlight node to get the language of + * @returns {String|null} either the highlight language or null + */ + function getHighlightLanguage(node) { + var classes = node.className.split(/\s+/); + for (var i = 0; i < classes.length; ++i) { + var cls = classes[i]; + if (/^highlight-/.test(cls)) { + return cls.slice(10); + } + } + return null; + } + // programming language switcher function findSheet(pattern, fromSheet) { if (fromSheet) { for(var i=0; i .highlight' - ].join(''); - sheet.deleteRule(lastIndex); - sheet.insertRule(sel + '{' + content + '}', lastIndex); - }); - })(); } - - // Config ZeroClipboard - ZeroClipboard.config({ - moviePath: '_static/ZeroClipboard.swf', - hoverClass: 'btn-clipboard-hover' - }); - - // Insert copy to clipboard button before .highlight or .example - $('.highlight-html, .highlight-scss').each(function () { - var highlight = $(this); - var previous = highlight.prev(); - var btnHtml = '
Copy
'; - - if (previous.hasClass('example')) { - previous.before(btnHtml.replace(/btn-clipboard/, 'btn-clipboard with-example')); - } else { - highlight.before(btnHtml); - } - }); - var zeroClipboard = new ZeroClipboard($('.btn-clipboard')); - var htmlBridge = $('#global-zeroclipboard-html-bridge'); - - // Handlers for ZeroClipboard - zeroClipboard.on('load', function () { - htmlBridge - .data('placement', 'top') - .attr('title', 'Copy to clipboard') - .tooltip(); - }); - - // Copy to clipboard - zeroClipboard.on('dataRequested', function (client) { - var highlight = $(this).parent().nextAll('.highlight').first(); - client.setText(highlight.text()); - }); - - // Notify copy success and reset tooltip title - zeroClipboard.on('complete', function () { - htmlBridge - .attr('title', 'Copied!') - .tooltip('fixTitle') - .tooltip('show') - .attr('title', 'Copy to clipboard') - .tooltip('fixTitle'); - }); - - // Notify copy failure - zeroClipboard.on('noflash wrongflash', function () { - htmlBridge.attr('title', 'Flash required') - .tooltip('fixTitle') - .tooltip('show'); - }); }); diff --git a/doc/_themes/odoodoc/static/zeroclipboard-2.1.6/ZeroClipboard.min.js b/doc/_themes/odoodoc/static/zeroclipboard-2.1.6/ZeroClipboard.min.js new file mode 100644 index 00000000000..365737f559a --- /dev/null +++ b/doc/_themes/odoodoc/static/zeroclipboard-2.1.6/ZeroClipboard.min.js @@ -0,0 +1,9 @@ +/*! + * ZeroClipboard + * The ZeroClipboard library provides an easy way to copy text to the clipboard using an invisible Adobe Flash movie and a JavaScript interface. + * Copyright (c) 2014 Jon Rohan, James M. Greene + * Licensed MIT + * http://zeroclipboard.org/ + * v2.1.6 + */ +!function(a,b){"use strict";var c,d,e=a,f=e.document,g=e.navigator,h=e.setTimeout,i=e.encodeURIComponent,j=e.ActiveXObject,k=e.Error,l=e.Number.parseInt||e.parseInt,m=e.Number.parseFloat||e.parseFloat,n=e.Number.isNaN||e.isNaN,o=e.Math.round,p=e.Date.now,q=e.Object.keys,r=e.Object.defineProperty,s=e.Object.prototype.hasOwnProperty,t=e.Array.prototype.slice,u=function(){var a=function(a){return a};if("function"==typeof e.wrap&&"function"==typeof e.unwrap)try{var b=f.createElement("div"),c=e.unwrap(b);1===b.nodeType&&c&&1===c.nodeType&&(a=e.unwrap)}catch(d){}return a}(),v=function(a){return t.call(a,0)},w=function(){var a,c,d,e,f,g,h=v(arguments),i=h[0]||{};for(a=1,c=h.length;c>a;a++)if(null!=(d=h[a]))for(e in d)s.call(d,e)&&(f=i[e],g=d[e],i!==g&&g!==b&&(i[e]=g));return i},x=function(a){var b,c,d,e;if("object"!=typeof a||null==a)b=a;else if("number"==typeof a.length)for(b=[],c=0,d=a.length;d>c;c++)s.call(a,c)&&(b[c]=x(a[c]));else{b={};for(e in a)s.call(a,e)&&(b[e]=x(a[e]))}return b},y=function(a,b){for(var c={},d=0,e=b.length;e>d;d++)b[d]in a&&(c[b[d]]=a[b[d]]);return c},z=function(a,b){var c={};for(var d in a)-1===b.indexOf(d)&&(c[d]=a[d]);return c},A=function(a){if(a)for(var b in a)s.call(a,b)&&delete a[b];return a},B=function(a,b){if(a&&1===a.nodeType&&a.ownerDocument&&b&&(1===b.nodeType&&b.ownerDocument&&b.ownerDocument===a.ownerDocument||9===b.nodeType&&!b.ownerDocument&&b===a.ownerDocument))do{if(a===b)return!0;a=a.parentNode}while(a);return!1},C=function(a){var b;return"string"==typeof a&&a&&(b=a.split("#")[0].split("?")[0],b=a.slice(0,a.lastIndexOf("/")+1)),b},D=function(a){var b,c;return"string"==typeof a&&a&&(c=a.match(/^(?:|[^:@]*@|.+\)@(?=http[s]?|file)|.+?\s+(?: at |@)(?:[^:\(]+ )*[\(]?)((?:http[s]?|file):\/\/[\/]?.+?\/[^:\)]*?)(?::\d+)(?::\d+)?/),c&&c[1]?b=c[1]:(c=a.match(/\)@((?:http[s]?|file):\/\/[\/]?.+?\/[^:\)]*?)(?::\d+)(?::\d+)?/),c&&c[1]&&(b=c[1]))),b},E=function(){var a,b;try{throw new k}catch(c){b=c}return b&&(a=b.sourceURL||b.fileName||D(b.stack)),a},F=function(){var a,c,d;if(f.currentScript&&(a=f.currentScript.src))return a;if(c=f.getElementsByTagName("script"),1===c.length)return c[0].src||b;if("readyState"in c[0])for(d=c.length;d--;)if("interactive"===c[d].readyState&&(a=c[d].src))return a;return"loading"===f.readyState&&(a=c[c.length-1].src)?a:(a=E())?a:b},G=function(){var a,c,d,e=f.getElementsByTagName("script");for(a=e.length;a--;){if(!(d=e[a].src)){c=null;break}if(d=C(d),null==c)c=d;else if(c!==d){c=null;break}}return c||b},H=function(){var a=C(F())||G()||"";return a+"ZeroClipboard.swf"},I={bridge:null,version:"0.0.0",pluginType:"unknown",disabled:null,outdated:null,unavailable:null,deactivated:null,overdue:null,ready:null},J="11.0.0",K={},L={},M=null,N={ready:"Flash communication is established",error:{"flash-disabled":"Flash is disabled or not installed","flash-outdated":"Flash is too outdated to support ZeroClipboard","flash-unavailable":"Flash is unable to communicate bidirectionally with JavaScript","flash-deactivated":"Flash is too outdated for your browser and/or is configured as click-to-activate","flash-overdue":"Flash communication was established but NOT within the acceptable time limit"}},O={swfPath:H(),trustedDomains:a.location.host?[a.location.host]:[],cacheBust:!0,forceEnhancedClipboard:!1,flashLoadTimeout:3e4,autoActivate:!0,bubbleEvents:!0,containerId:"global-zeroclipboard-html-bridge",containerClass:"global-zeroclipboard-container",swfObjectId:"global-zeroclipboard-flash-bridge",hoverClass:"zeroclipboard-is-hover",activeClass:"zeroclipboard-is-active",forceHandCursor:!1,title:null,zIndex:999999999},P=function(a){if("object"==typeof a&&null!==a)for(var b in a)if(s.call(a,b))if(/^(?:forceHandCursor|title|zIndex|bubbleEvents)$/.test(b))O[b]=a[b];else if(null==I.bridge)if("containerId"===b||"swfObjectId"===b){if(!cb(a[b]))throw new Error("The specified `"+b+"` value is not valid as an HTML4 Element ID");O[b]=a[b]}else O[b]=a[b];{if("string"!=typeof a||!a)return x(O);if(s.call(O,a))return O[a]}},Q=function(){return{browser:y(g,["userAgent","platform","appName"]),flash:z(I,["bridge"]),zeroclipboard:{version:Fb.version,config:Fb.config()}}},R=function(){return!!(I.disabled||I.outdated||I.unavailable||I.deactivated)},S=function(a,b){var c,d,e,f={};if("string"==typeof a&&a)e=a.toLowerCase().split(/\s+/);else if("object"==typeof a&&a&&"undefined"==typeof b)for(c in a)s.call(a,c)&&"string"==typeof c&&c&&"function"==typeof a[c]&&Fb.on(c,a[c]);if(e&&e.length){for(c=0,d=e.length;d>c;c++)a=e[c].replace(/^on/,""),f[a]=!0,K[a]||(K[a]=[]),K[a].push(b);if(f.ready&&I.ready&&Fb.emit({type:"ready"}),f.error){var g=["disabled","outdated","unavailable","deactivated","overdue"];for(c=0,d=g.length;d>c;c++)if(I[g[c]]===!0){Fb.emit({type:"error",name:"flash-"+g[c]});break}}}return Fb},T=function(a,b){var c,d,e,f,g;if(0===arguments.length)f=q(K);else if("string"==typeof a&&a)f=a.split(/\s+/);else if("object"==typeof a&&a&&"undefined"==typeof b)for(c in a)s.call(a,c)&&"string"==typeof c&&c&&"function"==typeof a[c]&&Fb.off(c,a[c]);if(f&&f.length)for(c=0,d=f.length;d>c;c++)if(a=f[c].toLowerCase().replace(/^on/,""),g=K[a],g&&g.length)if(b)for(e=g.indexOf(b);-1!==e;)g.splice(e,1),e=g.indexOf(b,e);else g.length=0;return Fb},U=function(a){var b;return b="string"==typeof a&&a?x(K[a])||null:x(K)},V=function(a){var b,c,d;return a=db(a),a&&!jb(a)?"ready"===a.type&&I.overdue===!0?Fb.emit({type:"error",name:"flash-overdue"}):(b=w({},a),ib.call(this,b),"copy"===a.type&&(d=pb(L),c=d.data,M=d.formatMap),c):void 0},W=function(){if("boolean"!=typeof I.ready&&(I.ready=!1),!Fb.isFlashUnusable()&&null===I.bridge){var a=O.flashLoadTimeout;"number"==typeof a&&a>=0&&h(function(){"boolean"!=typeof I.deactivated&&(I.deactivated=!0),I.deactivated===!0&&Fb.emit({type:"error",name:"flash-deactivated"})},a),I.overdue=!1,nb()}},X=function(){Fb.clearData(),Fb.blur(),Fb.emit("destroy"),ob(),Fb.off()},Y=function(a,b){var c;if("object"==typeof a&&a&&"undefined"==typeof b)c=a,Fb.clearData();else{if("string"!=typeof a||!a)return;c={},c[a]=b}for(var d in c)"string"==typeof d&&d&&s.call(c,d)&&"string"==typeof c[d]&&c[d]&&(L[d]=c[d])},Z=function(a){"undefined"==typeof a?(A(L),M=null):"string"==typeof a&&s.call(L,a)&&delete L[a]},$=function(a){return"undefined"==typeof a?x(L):"string"==typeof a&&s.call(L,a)?L[a]:void 0},_=function(a){if(a&&1===a.nodeType){c&&(xb(c,O.activeClass),c!==a&&xb(c,O.hoverClass)),c=a,wb(a,O.hoverClass);var b=a.getAttribute("title")||O.title;if("string"==typeof b&&b){var d=mb(I.bridge);d&&d.setAttribute("title",b)}var e=O.forceHandCursor===!0||"pointer"===yb(a,"cursor");Cb(e),Bb()}},ab=function(){var a=mb(I.bridge);a&&(a.removeAttribute("title"),a.style.left="0px",a.style.top="-9999px",a.style.width="1px",a.style.top="1px"),c&&(xb(c,O.hoverClass),xb(c,O.activeClass),c=null)},bb=function(){return c||null},cb=function(a){return"string"==typeof a&&a&&/^[A-Za-z][A-Za-z0-9_:\-\.]*$/.test(a)},db=function(a){var b;if("string"==typeof a&&a?(b=a,a={}):"object"==typeof a&&a&&"string"==typeof a.type&&a.type&&(b=a.type),b){!a.target&&/^(copy|aftercopy|_click)$/.test(b.toLowerCase())&&(a.target=d),w(a,{type:b.toLowerCase(),target:a.target||c||null,relatedTarget:a.relatedTarget||null,currentTarget:I&&I.bridge||null,timeStamp:a.timeStamp||p()||null});var e=N[a.type];return"error"===a.type&&a.name&&e&&(e=e[a.name]),e&&(a.message=e),"ready"===a.type&&w(a,{target:null,version:I.version}),"error"===a.type&&(/^flash-(disabled|outdated|unavailable|deactivated|overdue)$/.test(a.name)&&w(a,{target:null,minimumVersion:J}),/^flash-(outdated|unavailable|deactivated|overdue)$/.test(a.name)&&w(a,{version:I.version})),"copy"===a.type&&(a.clipboardData={setData:Fb.setData,clearData:Fb.clearData}),"aftercopy"===a.type&&(a=qb(a,M)),a.target&&!a.relatedTarget&&(a.relatedTarget=eb(a.target)),a=fb(a)}},eb=function(a){var b=a&&a.getAttribute&&a.getAttribute("data-clipboard-target");return b?f.getElementById(b):null},fb=function(a){if(a&&/^_(?:click|mouse(?:over|out|down|up|move))$/.test(a.type)){var c=a.target,d="_mouseover"===a.type&&a.relatedTarget?a.relatedTarget:b,g="_mouseout"===a.type&&a.relatedTarget?a.relatedTarget:b,h=Ab(c),i=e.screenLeft||e.screenX||0,j=e.screenTop||e.screenY||0,k=f.body.scrollLeft+f.documentElement.scrollLeft,l=f.body.scrollTop+f.documentElement.scrollTop,m=h.left+("number"==typeof a._stageX?a._stageX:0),n=h.top+("number"==typeof a._stageY?a._stageY:0),o=m-k,p=n-l,q=i+o,r=j+p,s="number"==typeof a.movementX?a.movementX:0,t="number"==typeof a.movementY?a.movementY:0;delete a._stageX,delete a._stageY,w(a,{srcElement:c,fromElement:d,toElement:g,screenX:q,screenY:r,pageX:m,pageY:n,clientX:o,clientY:p,x:o,y:p,movementX:s,movementY:t,offsetX:0,offsetY:0,layerX:0,layerY:0})}return a},gb=function(a){var b=a&&"string"==typeof a.type&&a.type||"";return!/^(?:(?:before)?copy|destroy)$/.test(b)},hb=function(a,b,c,d){d?h(function(){a.apply(b,c)},0):a.apply(b,c)},ib=function(a){if("object"==typeof a&&a&&a.type){var b=gb(a),c=K["*"]||[],d=K[a.type]||[],f=c.concat(d);if(f&&f.length){var g,h,i,j,k,l=this;for(g=0,h=f.length;h>g;g++)i=f[g],j=l,"string"==typeof i&&"function"==typeof e[i]&&(i=e[i]),"object"==typeof i&&i&&"function"==typeof i.handleEvent&&(j=i,i=i.handleEvent),"function"==typeof i&&(k=w({},a),hb(i,j,[k],b))}return this}},jb=function(a){var b=a.target||c||null,e="swf"===a._source;delete a._source;var f=["flash-disabled","flash-outdated","flash-unavailable","flash-deactivated","flash-overdue"];switch(a.type){case"error":-1!==f.indexOf(a.name)&&w(I,{disabled:"flash-disabled"===a.name,outdated:"flash-outdated"===a.name,unavailable:"flash-unavailable"===a.name,deactivated:"flash-deactivated"===a.name,overdue:"flash-overdue"===a.name,ready:!1});break;case"ready":var g=I.deactivated===!0;w(I,{disabled:!1,outdated:!1,unavailable:!1,deactivated:!1,overdue:g,ready:!g});break;case"beforecopy":d=b;break;case"copy":var h,i,j=a.relatedTarget;!L["text/html"]&&!L["text/plain"]&&j&&(i=j.value||j.outerHTML||j.innerHTML)&&(h=j.value||j.textContent||j.innerText)?(a.clipboardData.clearData(),a.clipboardData.setData("text/plain",h),i!==h&&a.clipboardData.setData("text/html",i)):!L["text/plain"]&&a.target&&(h=a.target.getAttribute("data-clipboard-text"))&&(a.clipboardData.clearData(),a.clipboardData.setData("text/plain",h));break;case"aftercopy":Fb.clearData(),b&&b!==vb()&&b.focus&&b.focus();break;case"_mouseover":Fb.focus(b),O.bubbleEvents===!0&&e&&(b&&b!==a.relatedTarget&&!B(a.relatedTarget,b)&&kb(w({},a,{type:"mouseenter",bubbles:!1,cancelable:!1})),kb(w({},a,{type:"mouseover"})));break;case"_mouseout":Fb.blur(),O.bubbleEvents===!0&&e&&(b&&b!==a.relatedTarget&&!B(a.relatedTarget,b)&&kb(w({},a,{type:"mouseleave",bubbles:!1,cancelable:!1})),kb(w({},a,{type:"mouseout"})));break;case"_mousedown":wb(b,O.activeClass),O.bubbleEvents===!0&&e&&kb(w({},a,{type:a.type.slice(1)}));break;case"_mouseup":xb(b,O.activeClass),O.bubbleEvents===!0&&e&&kb(w({},a,{type:a.type.slice(1)}));break;case"_click":d=null,O.bubbleEvents===!0&&e&&kb(w({},a,{type:a.type.slice(1)}));break;case"_mousemove":O.bubbleEvents===!0&&e&&kb(w({},a,{type:a.type.slice(1)}))}return/^_(?:click|mouse(?:over|out|down|up|move))$/.test(a.type)?!0:void 0},kb=function(a){if(a&&"string"==typeof a.type&&a){var b,c=a.target||null,d=c&&c.ownerDocument||f,g={view:d.defaultView||e,canBubble:!0,cancelable:!0,detail:"click"===a.type?1:0,button:"number"==typeof a.which?a.which-1:"number"==typeof a.button?a.button:d.createEvent?0:1},h=w(g,a);c&&d.createEvent&&c.dispatchEvent&&(h=[h.type,h.canBubble,h.cancelable,h.view,h.detail,h.screenX,h.screenY,h.clientX,h.clientY,h.ctrlKey,h.altKey,h.shiftKey,h.metaKey,h.button,h.relatedTarget],b=d.createEvent("MouseEvents"),b.initMouseEvent&&(b.initMouseEvent.apply(b,h),b._source="js",c.dispatchEvent(b)))}},lb=function(){var a=f.createElement("div");return a.id=O.containerId,a.className=O.containerClass,a.style.position="absolute",a.style.left="0px",a.style.top="-9999px",a.style.width="1px",a.style.height="1px",a.style.zIndex=""+Db(O.zIndex),a},mb=function(a){for(var b=a&&a.parentNode;b&&"OBJECT"===b.nodeName&&b.parentNode;)b=b.parentNode;return b||null},nb=function(){var a,b=I.bridge,c=mb(b);if(!b){var d=ub(e.location.host,O),g="never"===d?"none":"all",h=sb(O),i=O.swfPath+rb(O.swfPath,O);c=lb();var j=f.createElement("div");c.appendChild(j),f.body.appendChild(c);var k=f.createElement("div"),l="activex"===I.pluginType;k.innerHTML='"+(l?'':"")+'',b=k.firstChild,k=null,u(b).ZeroClipboard=Fb,c.replaceChild(b,j)}return b||(b=f[O.swfObjectId],b&&(a=b.length)&&(b=b[a-1]),!b&&c&&(b=c.firstChild)),I.bridge=b||null,b},ob=function(){var a=I.bridge;if(a){var b=mb(a);b&&("activex"===I.pluginType&&"readyState"in a?(a.style.display="none",function c(){if(4===a.readyState){for(var d in a)"function"==typeof a[d]&&(a[d]=null);a.parentNode&&a.parentNode.removeChild(a),b.parentNode&&b.parentNode.removeChild(b)}else h(c,10)}()):(a.parentNode&&a.parentNode.removeChild(a),b.parentNode&&b.parentNode.removeChild(b))),I.ready=null,I.bridge=null,I.deactivated=null}},pb=function(a){var b={},c={};if("object"==typeof a&&a){for(var d in a)if(d&&s.call(a,d)&&"string"==typeof a[d]&&a[d])switch(d.toLowerCase()){case"text/plain":case"text":case"air:text":case"flash:text":b.text=a[d],c.text=d;break;case"text/html":case"html":case"air:html":case"flash:html":b.html=a[d],c.html=d;break;case"application/rtf":case"text/rtf":case"rtf":case"richtext":case"air:rtf":case"flash:rtf":b.rtf=a[d],c.rtf=d}return{data:b,formatMap:c}}},qb=function(a,b){if("object"!=typeof a||!a||"object"!=typeof b||!b)return a;var c={};for(var d in a)if(s.call(a,d)){if("success"!==d&&"data"!==d){c[d]=a[d];continue}c[d]={};var e=a[d];for(var f in e)f&&s.call(e,f)&&s.call(b,f)&&(c[d][b[f]]=e[f])}return c},rb=function(a,b){var c=null==b||b&&b.cacheBust===!0;return c?(-1===a.indexOf("?")?"?":"&")+"noCache="+p():""},sb=function(a){var b,c,d,f,g="",h=[];if(a.trustedDomains&&("string"==typeof a.trustedDomains?f=[a.trustedDomains]:"object"==typeof a.trustedDomains&&"length"in a.trustedDomains&&(f=a.trustedDomains)),f&&f.length)for(b=0,c=f.length;c>b;b++)if(s.call(f,b)&&f[b]&&"string"==typeof f[b]){if(d=tb(f[b]),!d)continue;if("*"===d){h.length=0,h.push(d);break}h.push.apply(h,[d,"//"+d,e.location.protocol+"//"+d])}return h.length&&(g+="trustedOrigins="+i(h.join(","))),a.forceEnhancedClipboard===!0&&(g+=(g?"&":"")+"forceEnhancedClipboard=true"),"string"==typeof a.swfObjectId&&a.swfObjectId&&(g+=(g?"&":"")+"swfObjectId="+i(a.swfObjectId)),g},tb=function(a){if(null==a||""===a)return null;if(a=a.replace(/^\s+|\s+$/g,""),""===a)return null;var b=a.indexOf("//");a=-1===b?a:a.slice(b+2);var c=a.indexOf("/");return a=-1===c?a:-1===b||0===c?null:a.slice(0,c),a&&".swf"===a.slice(-4).toLowerCase()?null:a||null},ub=function(){var a=function(a){var b,c,d,e=[];if("string"==typeof a&&(a=[a]),"object"!=typeof a||!a||"number"!=typeof a.length)return e;for(b=0,c=a.length;c>b;b++)if(s.call(a,b)&&(d=tb(a[b]))){if("*"===d){e.length=0,e.push("*");break}-1===e.indexOf(d)&&e.push(d)}return e};return function(b,c){var d=tb(c.swfPath);null===d&&(d=b);var e=a(c.trustedDomains),f=e.length;if(f>0){if(1===f&&"*"===e[0])return"always";if(-1!==e.indexOf(b))return 1===f&&b===d?"sameDomain":"always"}return"never"}}(),vb=function(){try{return f.activeElement}catch(a){return null}},wb=function(a,b){if(!a||1!==a.nodeType)return a;if(a.classList)return a.classList.contains(b)||a.classList.add(b),a;if(b&&"string"==typeof b){var c=(b||"").split(/\s+/);if(1===a.nodeType)if(a.className){for(var d=" "+a.className+" ",e=a.className,f=0,g=c.length;g>f;f++)d.indexOf(" "+c[f]+" ")<0&&(e+=" "+c[f]);a.className=e.replace(/^\s+|\s+$/g,"")}else a.className=b}return a},xb=function(a,b){if(!a||1!==a.nodeType)return a;if(a.classList)return a.classList.contains(b)&&a.classList.remove(b),a;if("string"==typeof b&&b){var c=b.split(/\s+/);if(1===a.nodeType&&a.className){for(var d=(" "+a.className+" ").replace(/[\n\t]/g," "),e=0,f=c.length;f>e;e++)d=d.replace(" "+c[e]+" "," ");a.className=d.replace(/^\s+|\s+$/g,"")}}return a},yb=function(a,b){var c=e.getComputedStyle(a,null).getPropertyValue(b);return"cursor"!==b||c&&"auto"!==c||"A"!==a.nodeName?c:"pointer"},zb=function(){var a,b,c,d=1;return"function"==typeof f.body.getBoundingClientRect&&(a=f.body.getBoundingClientRect(),b=a.right-a.left,c=f.body.offsetWidth,d=o(b/c*100)/100),d},Ab=function(a){var b={left:0,top:0,width:0,height:0};if(a.getBoundingClientRect){var c,d,g,h=a.getBoundingClientRect();"pageXOffset"in e&&"pageYOffset"in e?(c=e.pageXOffset,d=e.pageYOffset):(g=zb(),c=o(f.documentElement.scrollLeft/g),d=o(f.documentElement.scrollTop/g));var i=f.documentElement.clientLeft||0,j=f.documentElement.clientTop||0;b.left=h.left+c-i,b.top=h.top+d-j,b.width="width"in h?h.width:h.right-h.left,b.height="height"in h?h.height:h.bottom-h.top}return b},Bb=function(){var a;if(c&&(a=mb(I.bridge))){var b=Ab(c);w(a.style,{width:b.width+"px",height:b.height+"px",top:b.top+"px",left:b.left+"px",zIndex:""+Db(O.zIndex)})}},Cb=function(a){I.ready===!0&&(I.bridge&&"function"==typeof I.bridge.setHandCursor?I.bridge.setHandCursor(a):I.ready=!1)},Db=function(a){if(/^(?:auto|inherit)$/.test(a))return a;var b;return"number"!=typeof a||n(a)?"string"==typeof a&&(b=Db(l(a,10))):b=a,"number"==typeof b?b:"auto"},Eb=function(a){function b(a){var b=a.match(/[\d]+/g);return b.length=3,b.join(".")}function c(a){return!!a&&(a=a.toLowerCase())&&(/^(pepflashplayer\.dll|libpepflashplayer\.so|pepperflashplayer\.plugin)$/.test(a)||"chrome.plugin"===a.slice(-13))}function d(a){a&&(i=!0,a.version&&(l=b(a.version)),!l&&a.description&&(l=b(a.description)),a.filename&&(k=c(a.filename)))}var e,f,h,i=!1,j=!1,k=!1,l="";if(g.plugins&&g.plugins.length)e=g.plugins["Shockwave Flash"],d(e),g.plugins["Shockwave Flash 2.0"]&&(i=!0,l="2.0.0.11");else if(g.mimeTypes&&g.mimeTypes.length)h=g.mimeTypes["application/x-shockwave-flash"],e=h&&h.enabledPlugin,d(e);else if("undefined"!=typeof a){j=!0;try{f=new a("ShockwaveFlash.ShockwaveFlash.7"),i=!0,l=b(f.GetVariable("$version"))}catch(n){try{f=new a("ShockwaveFlash.ShockwaveFlash.6"),i=!0,l="6.0.21"}catch(o){try{f=new a("ShockwaveFlash.ShockwaveFlash"),i=!0,l=b(f.GetVariable("$version"))}catch(p){j=!1}}}}I.disabled=i!==!0,I.outdated=l&&m(l)c;c++)a=e[c].replace(/^on/,""),f[a]=!0,g[a]||(g[a]=[]),g[a].push(b);if(f.ready&&I.ready&&this.emit({type:"ready",client:this}),f.error){var h=["disabled","outdated","unavailable","deactivated","overdue"];for(c=0,d=h.length;d>c;c++)if(I[h[c]]){this.emit({type:"error",name:"flash-"+h[c],client:this});break}}}return this},Nb=function(a,b){var c,d,e,f,g,h=Hb[this.id]&&Hb[this.id].handlers;if(0===arguments.length)f=q(h);else if("string"==typeof a&&a)f=a.split(/\s+/);else if("object"==typeof a&&a&&"undefined"==typeof b)for(c in a)s.call(a,c)&&"string"==typeof c&&c&&"function"==typeof a[c]&&this.off(c,a[c]);if(f&&f.length)for(c=0,d=f.length;d>c;c++)if(a=f[c].toLowerCase().replace(/^on/,""),g=h[a],g&&g.length)if(b)for(e=g.indexOf(b);-1!==e;)g.splice(e,1),e=g.indexOf(b,e);else g.length=0;return this},Ob=function(a){var b=null,c=Hb[this.id]&&Hb[this.id].handlers;return c&&(b="string"==typeof a&&a?c[a]?c[a].slice(0):[]:x(c)),b},Pb=function(a){if(Ub.call(this,a)){"object"==typeof a&&a&&"string"==typeof a.type&&a.type&&(a=w({},a));var b=w({},db(a),{client:this});Vb.call(this,b)}return this},Qb=function(a){a=Wb(a);for(var b=0;b0,d=!a.target||c&&-1!==b.indexOf(a.target),e=a.relatedTarget&&c&&-1!==b.indexOf(a.relatedTarget),f=a.client&&a.client===this;return d||e||f?!0:!1},Vb=function(a){if("object"==typeof a&&a&&a.type){var b=gb(a),c=Hb[this.id]&&Hb[this.id].handlers["*"]||[],d=Hb[this.id]&&Hb[this.id].handlers[a.type]||[],f=c.concat(d);if(f&&f.length){var g,h,i,j,k,l=this;for(g=0,h=f.length;h>g;g++)i=f[g],j=l,"string"==typeof i&&"function"==typeof e[i]&&(i=e[i]),"object"==typeof i&&i&&"function"==typeof i.handleEvent&&(j=i,i=i.handleEvent),"function"==typeof i&&(k=w({},a),hb(i,j,[k],b))}return this}},Wb=function(a){return"string"==typeof a&&(a=[]),"number"!=typeof a.length?[a]:a},Xb=function(a){if(a&&1===a.nodeType){var b=function(a){(a||(a=e.event))&&("js"!==a._source&&(a.stopImmediatePropagation(),a.preventDefault()),delete a._source)},c=function(c){(c||(c=e.event))&&(b(c),Fb.focus(a))};a.addEventListener("mouseover",c,!1),a.addEventListener("mouseout",b,!1),a.addEventListener("mouseenter",b,!1),a.addEventListener("mouseleave",b,!1),a.addEventListener("mousemove",b,!1),Kb[a.zcClippingId]={mouseover:c,mouseout:b,mouseenter:b,mouseleave:b,mousemove:b}}},Yb=function(a){if(a&&1===a.nodeType){var b=Kb[a.zcClippingId];if("object"==typeof b&&b){for(var c,d,e=["move","leave","enter","out","over"],f=0,g=e.length;g>f;f++)c="mouse"+e[f],d=b[c],"function"==typeof d&&a.removeEventListener(c,d,!1);delete Kb[a.zcClippingId]}}};Fb._createClient=function(){Lb.apply(this,v(arguments))},Fb.prototype.on=function(){return Mb.apply(this,v(arguments))},Fb.prototype.off=function(){return Nb.apply(this,v(arguments))},Fb.prototype.handlers=function(){return Ob.apply(this,v(arguments))},Fb.prototype.emit=function(){return Pb.apply(this,v(arguments))},Fb.prototype.clip=function(){return Qb.apply(this,v(arguments))},Fb.prototype.unclip=function(){return Rb.apply(this,v(arguments))},Fb.prototype.elements=function(){return Sb.apply(this,v(arguments))},Fb.prototype.destroy=function(){return Tb.apply(this,v(arguments))},Fb.prototype.setText=function(a){return Fb.setData("text/plain",a),this},Fb.prototype.setHtml=function(a){return Fb.setData("text/html",a),this},Fb.prototype.setRichText=function(a){return Fb.setData("application/rtf",a),this},Fb.prototype.setData=function(){return Fb.setData.apply(this,v(arguments)),this},Fb.prototype.clearData=function(){return Fb.clearData.apply(this,v(arguments)),this},Fb.prototype.getData=function(){return Fb.getData.apply(this,v(arguments))},"function"==typeof define&&define.amd?define(function(){return Fb}):"object"==typeof module&&module&&"object"==typeof module.exports&&module.exports?module.exports=Fb:a.ZeroClipboard=Fb}(function(){return this||window}()); diff --git a/doc/_themes/odoodoc/static/zeroclipboard-2.1.6/ZeroClipboard.swf b/doc/_themes/odoodoc/static/zeroclipboard-2.1.6/ZeroClipboard.swf new file mode 100644 index 0000000000000000000000000000000000000000..d4e2561b366e131d3bae303acce90a137a4956e1 GIT binary patch literal 4036 zcmV;#4?FNfS5ppe82|uy+Ko8*dmG1fGkXno36KCtQ4~c|ON%BYdPzcuWs8(#QzRjg zmIR51E{i4>z$~%SVi($7@UW~{Q6eW!(z{mg7ET*CZPcds-K0&AAKLHh2a=8WwLkW! z{uk<-UGS3Ae1SMSZ{EE3=H0Wa6(sx*LXDpx)P~V`;s8SE!{&d-2%T{Y#_;rbT3snw zwl@r`vwcP1FAon5EiW$*E}s~5+{K||r%#_AN*y0MetZBZ2E0|<*H;E??{MF_K)^Wl z@~&C-O~+Q*TF*HZ|7>4hU1}k}Ewo&5tw3ZUKSV8BqPFi19UD9bf(rRz!*NTxe@-u# zEi;8)-=gP63r>!zX^XC?C)Lp%{(w3pVZ?}njDq!Bt!bFrSIm`5l)IC?R@7d0Bc*G8t(d1pK`_5j9 zEgajobbGO)FVb@pSAxCmb)ci}({oc5OFjOErXEinI~l6MuxEx|@^&2q0X&Ds#gs$6y|9-UyY zl*!$td0(3GUDIBSq$_rwsV+Vw-x> zW^x@@2p*n~D+JzP-*e>dJo))%MlgvQ3=5V@d z>83qPSNx1^ttLa<7R$Kq>yewDH|Lv{M~b?~5OJ6p?KWehr-OOs`!sZ7!Z0mx(yWta zF2An3p6L0IKVo2Er!A_gHQm*3rjvcG}V65VLj@#_g zlCwnj5Gt0V7pOaF8;$~Qh3tSsz3xTJ$?4X>`w+S9sD!mSkaNw#B5m-NjWFnwh3Egsnx#PWeM+a`}1Mkn*=hVRIh2c8`cLwMCdjy|) zexvVJSPo@ebJ4Uttch?Eeh`s5YDBrE zs-~eH@mI^#G1NT;sGdCwlD1ClQ+s>WA4N9Es_G?LrquyG+RzbBLU$q>#y68(JQ#18gci*vlASVH$1+{*Is*~fxYM=U=y30Eqs_Ig|;@Y5f zVAf9hj40g0qcM;AJJm13X09$=nn=%zI$JgdJ2TKJ>1C%t<%yf4W3vnC$ywRk!bJm;YZajPSB3Kv zGqdD=xQHyedbw!kJvm3=n6znGO3>qOm<7Ms5P%n{xmffg1y^652D{%wRzb;8C^4FM%Buo<$hrZofiVmAp}}`9Im^@?(>=r9q)=r73H6BkIGWVo)iV#%Vpvw{8*%oDP*COwDAj$YFyvjaA8__M%_p zG&M}nL7Vz9$Xas|ifGoEo4%yLn5OqD(5D;jf;QpH(=a~59UT!DJZc$Is2Q~K6<;X& zC5v}`BRZy+^_&U0Xi|@JAnPHnRJ2UHvLej{EjQiAC zyL^7tr{N1v{Z69ZJ>MzI+wAd0cH`>SHbTt9o0c2jUZ;asUaw(x;wBVPX8Q8zEP?ac zi47NLFJG$TV$1CMoSRLv6TSHK_bmI+n}ZXh^H#2679iPS{IIFS-WOcJq~ zh%H3iN5obl?k8d!i5wu2b`m*AB1wWf2<{}fi{L{rzArC>@D&pK3O)$H zGKqbR<*#D^zX9W?VEitjK2l=?_mCW7~g~OeHi}$;~#@?%}()J_Oiz z0uH_l*b_c7FcuKvLq`=I0IYE4c{p+F0aOvJ zC>YmgQDp4!XIt<*fKjX+qsD_6HFaPV@5Ct4D9SQw6}y-*hcMc& zV${}+(ScVmYCkN>4d@W)(SVw{o&?|WYC_o8!SyEi&c1}uB`8=HFiPSm=y(t}3W!tj zVF7`wF^r87t4xLOVuV(Ez^Vm!|{OWsHL(;k`V;RIEsU1wd_%&zh=ZUuW>-b z^Pp}dtF84u(+6NJ2%Hjq3=(hj|1d?d`baEcHt{8a;_k;Kku4%6Xl3AToPgJ98e`EHQloCuK&!7EkIz zPVN?RG7xfjmynYnM0+d!I%aBRPZ@7yPXS)ayb0WWB|e}zt)-v!C*&<5r+c3zMJA%R z#MiV7+QllMxvDl<)uyW2J5}vcRl8i(u2i*5Rl8c%u2r?^sx}i~W~%pr zs;*F6OD7O>QEk2vB=J*?SdZ1_qfF{l6zdH&qaodnk{#fEzzLas7%4xMjD13MI(rl&>6&YNJ9dT&LYtJ94iJ<k#eHA=7 zo1Q~e{pz~*HLPE+ts7U@L(63zVsWjOTFVN?jhfK{rNzLTJK(Jy@b(V)?hbgTW+ZpO z01KW_IH>Q#wG>5JF_oQ9W#3C>7gE`5D$B#)P1G6uiZbZXfH5zGogm6_g4hstg0;*6 z45%0{h>FXq`Z-kfS_#*(x{>RB)}Mq{cog6~IAedkKA0oe%+SxE_n}_B73j5Jg_vRJ z6Z_*W5VX{K>Ne`%>`%Q0()#p5XN0fSj8>2X(LY)<_CM9zF5r=im#Bdkq31@UjT{!l z1n9M!Y6;A2;z7M6Y_J@GZlBfH)}b;$@3;dcyKRSW7O0;_MLU^EEi5gNsv^e{G5P_K4GP zKp%%bBX4BQb;yUi>3g{PWHTr9;Oe?zMM0Ad?VGr|`2!&*0Ak;NzIZWTnzyyj@LE;- zU0mI$YTw4yDI06QhchK+OcJ8WH`P+}4vVIE6L%J)fNtK2TnoqX!gWZbPqg315FDPR zCz$;`kwb~?mZ!S7NO6{UwZzN z%3j*scp8dYXXmsEkbm6}&a7wnJ*Y=aS2(B~?T_)Zr^T)?D-W@(+|P4i+-pV`$PRVU zyI`k{mjrPo2wYJ7LG&Ky#rPps#bQqAda}6zjva&*1?1WyeKMfv3l7I>xJU=5|>>L zu8(qNg*LQeYc>dbu+qT)^>Hda1#!vOfMky!W7X0i=G9+Y+lj zdcf|dZRsBv4>Mmt;Sr&OBU?9Cc`5mNP?P~x_Cw%DcZM&wWXryOb<2dGimZ9-z=HE4+H$%$G3Z<{?^2NX{Ep5Be@^H@g)0 zazGRu?`|K@?LFRQUgb|uU#G^$_?!4MOpX7-pK{;gsKM`e`3Xk+19XrTTO5k*BOF#6 z*I8_Y%<9;wwoU=6ZJ?IYh67{jr2uNJ!5hgxVeOysc@F0ES1|V^4sVYdhRo=~z$8G4 q0(=NwVMQruv>;C1I`OOZ6Zy{KVOI9d?ElO6{y#btH2OaX)bM*lY1Xp< literal 0 HcmV?d00001 diff --git a/doc/modules/api_integration.rst b/doc/modules/api_integration.rst index f6a51f0f9b8..43301979d0a 100644 --- a/doc/modules/api_integration.rst +++ b/doc/modules/api_integration.rst @@ -22,13 +22,74 @@ easily available over XML-RPC_ and accessible from a variety of languages. Connection and authentication ============================= +.. kinda gross because it duplicates existing bits + +.. rst-class:: setupcode hidden + + .. code-block:: python + + import xmlrpclib + info = xmlrpclib.ServerProxy('https://demo.odoo.com/start').start() + url, db, username, password = \ + info['host'], info['database'], info['user'], info['password'] + common = xmlrpclib.ServerProxy('{}/xmlrpc/2/common'.format(url)) + uid = common.authenticate(db, username, password, {}) + models = xmlrpclib.ServerProxy('{}/xmlrpc/2/object'.format(url)) + + .. code-block:: ruby + + require "xmlrpc/client" + info = XMLRPC::Client.new2('https://demo.odoo.com/start').call('start') + url, db, username, password = \ + info['host'], info['database'], info['user'], info['password'] + common = XMLRPC::Client.new2("#{url}/xmlrpc/2/common") + uid = common.authenticate(db, username, password, {}) + models = xmlrpclib.ServerProxy('{}/xmlrpc/2/object'.format(url)) + + .. code-block:: php + + require_once('ripcord.php'); + $info = ripcord::client('https://demo.odoo.com/start')->start(); + list($url, $db, $username, $password) = + array($info['host'], $info['database'], $info['user'], $info['password']); + $common = ripcord::client("$url/xmlrpc/2/common"); + $uid = $common->authenticate($db, $username, $password, array()); + $models = ripcord::client("$url/xmlrpc/2/object"); + + .. code-block:: java + + final XmlRpcClient client = new XmlRpcClient(); + + final XmlRpcClientConfigImpl start_config = new XmlRpcClientConfigImpl(); + start_config.setServerURL(new URL("https://demo.odoo.com/start")); + final Map info = (Map)client.execute( + start_config, "start", Collections.emptyList()); + + final String url = info.get("host"), + db = info.get("database"), + username = info.get("user"), + password = info.get("password"); + + final XmlRpcClientConfigImpl common_config = new XmlRpcClientConfigImpl(); + common_config.setServerURL(new URL(String.format("%s/xmlrpc/2/common", url))); + + int uid = (int)client.execute( + common_config, "authenticate", Arrays.asList( + db, username, password, Collections.emptyMap())); + + final XmlRpcClient models = new XmlRpcClient() {{ + setConfig(new XmlRpcClientConfigImpl() {{ + setServerURL(new URL(String.format("%s/xmlrpc/2/object", url))); + }}); + }}; + Configuration ------------- If you already have an Odoo server installed, you can just use its parameters -.. rst-class:: switchable +.. rst-class:: switchable setup .. code-block:: python @@ -61,7 +122,7 @@ parameters To make exploration simpler, you can also ask https://demo.odoo.com for a test database: -.. rst-class:: switchable +.. rst-class:: switchable setup .. code-block:: python @@ -134,7 +195,7 @@ authentication itself is done through the ``authenticate`` function and returns a user identifier (``uid``) used in authenticated calls instead of the login. -.. rst-class:: switchable +.. rst-class:: switchable setup .. code-block:: python @@ -166,7 +227,7 @@ the login. "protocol_version": 1, } -.. rst-class:: switchable +.. rst-class:: switchable setup .. code-block:: python @@ -209,7 +270,7 @@ For instance to see if we can read the ``res.partner`` model we can call ``raise_exception`` passed by keyword (in order to get a true/false result rather than true/error): -.. rst-class:: switchable +.. rst-class:: switchable setup .. code-block:: python From 7ffbd5c7269a0e0a8280913860adf3a75677b06b Mon Sep 17 00:00:00 2001 From: Christophe Matthieu Date: Thu, 6 Nov 2014 18:44:19 +0100 Subject: [PATCH 15/37] [FIX] product: name_get of product.attribute.value name was confusing for bom and product if the value of the attribute is display without the attribute name added key in context to keep the previous behaviour (e.g. in product view where the attribute name is present) --- addons/product/product.py | 8 ++++++++ addons/product/product_view.xml | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/addons/product/product.py b/addons/product/product.py index 8f4921f3974..7060a4f3992 100644 --- a/addons/product/product.py +++ b/addons/product/product.py @@ -365,6 +365,14 @@ class product_attribute_value(osv.osv): 'price_extra': value, }, context=context) + def name_get(self, cr, uid, ids, context=None): + if context and not context.get('show_attribute', True): + return super(product_attribute_value, self).name_get(cr, uid, ids, context=context) + res = [] + for value in self.browse(cr, uid, ids, context=context): + res.append([value.id, "%s: %s" % (value.attribute_id.name, value.name)]) + return res + _columns = { 'sequence': fields.integer('Sequence', help="Determine the display order"), 'name': fields.char('Value', translate=True, required=True), diff --git a/addons/product/product_view.xml b/addons/product/product_view.xml index 4eb42e38d3b..02dd5690395 100644 --- a/addons/product/product_view.xml +++ b/addons/product/product_view.xml @@ -249,7 +249,7 @@ - + From a6745127393ed4c8c2e82072ed92dce5768d64ff Mon Sep 17 00:00:00 2001 From: Christophe Matthieu Date: Thu, 6 Nov 2014 18:09:52 +0100 Subject: [PATCH 16/37] [FIX] mrp: The attribute on bom lines is not filtered in function of the selected product template --- addons/mrp/mrp_view.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/mrp/mrp_view.xml b/addons/mrp/mrp_view.xml index c43df1f9dfb..12904e22984 100644 --- a/addons/mrp/mrp_view.xml +++ b/addons/mrp/mrp_view.xml @@ -383,7 +383,7 @@ - + From c98e5b2d2c6036076cc3952c49e452e38d5ccc93 Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Fri, 7 Nov 2014 12:34:04 +0100 Subject: [PATCH 17/37] [FIX] mail: _fields['type'].selection is a callable This revert partially the rev. f2e4a10e1a58c932f9a989e642771f5ae75d756a File "/home/odoo/src/odoo/8.0/addons/mail/mail_mail.py", line 76, in default_get if context and context.get('default_type') and context.get('default_type') not in self._fields['type'].selection: TypeError: argument of type 'function' is not iterable --- addons/mail/mail_mail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/mail/mail_mail.py b/addons/mail/mail_mail.py index 482db7a3aed..db4a0ac01a1 100644 --- a/addons/mail/mail_mail.py +++ b/addons/mail/mail_mail.py @@ -73,7 +73,7 @@ class mail_mail(osv.Model): def default_get(self, cr, uid, fields, context=None): # protection for `default_type` values leaking from menu action context (e.g. for invoices) # To remove when automatic context propagation is removed in web client - if context and context.get('default_type') and context.get('default_type') not in self._fields['type'].selection: + if context and context.get('default_type') and context.get('default_type') not in self._all_columns['type'].column.selection: context = dict(context, default_type=None) return super(mail_mail, self).default_get(cr, uid, fields, context=context) From 5d79dd80e578c39a45f1603fbae665772ffaa446 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Fri, 7 Nov 2014 14:00:49 +0100 Subject: [PATCH 18/37] [ADD] ZeroClipboard-based "copy" button on snippets Also fix ruby setup code, some of its bits were actually Python and didn't quite work when pasted into irb... --- doc/_themes/odoodoc/static/app.js | 70 +++++++++++++++++++-------- doc/_themes/odoodoc/static/style.css | 25 +++++----- doc/_themes/odoodoc/static/style.less | 27 ++++++----- doc/modules/api_integration.rst | 4 +- 4 files changed, 80 insertions(+), 46 deletions(-) diff --git a/doc/_themes/odoodoc/static/app.js b/doc/_themes/odoodoc/static/app.js index 36a2dcff57e..47c33f41d23 100644 --- a/doc/_themes/odoodoc/static/app.js +++ b/doc/_themes/odoodoc/static/app.js @@ -35,28 +35,28 @@ $(function () { * actually not all blocks because we don't want to add the setup bits to the setup bits, so that's kinda shit */ - document.addEventListener('copy', function (e) { - var target = $(e.target).closest('.switchable:not(.setup)').get(0); - // not in a switchable - if (!target) { return; } - var lang = getHighlightLanguage(target); - if (!lang) { - // switchable without highlight (e.g. language-specific notes), - // don't munge - return; - } - e.preventDefault(); - // get generic setup code - var prefix = document.querySelector('.setupcode.highlight-' + lang).textContent; + document.addEventListener('copy', copyCode); - // prepend setup code to current snippet, get all of current snippet - // in case only part of it was selected - var data = prefix + target.textContent; - // sane browsers - e.clipboardData.setData('text/plain', data); - // MSIE - e.clipboardData.setData('Text', data); + ZeroClipboard.config({ + swfPath: '../_static/zeroclipboard-2.1.6/ZeroClipboard.swf', + flashLoadTimeout: 3e3, + }); + var zc = new ZeroClipboard(); + zc.on('ready', function () { + var $highlighted = $('.switchable:not(.setup) .highlight').addClass('with-btn-clipboard'); + var $clipboard_buttons = + $('') + .on('transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd', function () { + $(this).removeClass('active'); + }) + .prependTo($highlighted); + zc.clip($clipboard_buttons); + }); + zc.on('copy', function (e) { + // yellow flash + $(e.target).addClass('active'); + copyCode(e); }); // stripe page stuff @@ -96,6 +96,36 @@ $(function () { }); })(); } + + function copyCode(e) { + // works for both C-c and "Copy" button if copy button is injected + // inside highlighted code section + var target = $(e.target).closest('.switchable:not(.setup)').get(0); + // not in a switchable + if (!target) { return; } + var lang = getHighlightLanguage(target); + if (!lang) { + // switchable without highlight (e.g. language-specific notes), + // don't munge + return; + } + + // get generic setup code + var setup_selector = '.setupcode.highlight-' + lang + ' pre'; + var setup = document.querySelector(setup_selector).textContent; + + // prepend setup code to current snippet, get all of current snippet + // in case only part of it was selected. Ensure we don't get e.g. + // button text around snippet itself + var data = setup + '\n' + target.querySelector('pre').textContent; + // sane browsers & ZeroClipboard + e.clipboardData.setData('text/plain', data); + // MSIE + e.clipboardData.setData('Text', data); + + // no preventDefault on ZC event + e.preventDefault && e.preventDefault(); + } /** * @param {Node} node highlight node to get the language of * @returns {String|null} either the highlight language or null diff --git a/doc/_themes/odoodoc/static/style.css b/doc/_themes/odoodoc/static/style.css index fba330944dd..443cae9b7b5 100644 --- a/doc/_themes/odoodoc/static/style.css +++ b/doc/_themes/odoodoc/static/style.css @@ -6653,23 +6653,20 @@ div.section > h2 { word-break: normal; word-wrap: normal; } +.switchable .highlight.with-btn-clipboard pre { + padding-right: 50px; +} /* * ZeroClipboard styles */ -.zero-clipboard { +.highlight { position: relative; - display: none; -} -@media (min-width: 768px) { - .zero-clipboard { - display: block; - } } .btn-clipboard { position: absolute; top: 0; right: 0; - z-index: 10; + z-index: 3; display: block; padding: 5px 8px; font-size: 12px; @@ -6679,10 +6676,14 @@ div.section > h2 { border: 1px solid #e1e1e8; border-radius: 0 4px 0 4px; } -.btn-clipboard-hover { - color: #fff; - background-color: #a24689; - border-color: #a24689; +.btn-clipboard:focus { + outline: none; +} +.btn-clipboard.active { + background-color: #ffffad; + -webkit-transition: background-color 0.5s linear; + -o-transition: background-color 0.5s linear; + transition: background-color 0.5s linear; } img.align-center { display: block; diff --git a/doc/_themes/odoodoc/static/style.less b/doc/_themes/odoodoc/static/style.less index 71076470e7b..a896e01e170 100644 --- a/doc/_themes/odoodoc/static/style.less +++ b/doc/_themes/odoodoc/static/style.less @@ -473,23 +473,22 @@ div.section > h2 { word-break: normal; word-wrap: normal; } +.switchable .highlight.with-btn-clipboard pre { + // avoid copy button overlapping with even long lines + padding-right: 50px; +} /* * ZeroClipboard styles */ - -.zero-clipboard { +.highlight { position: relative; - display: none; - @media (min-width: @screen-sm-min) { - display: block; - } } .btn-clipboard { position: absolute; top: 0; right: 0; - z-index: 10; + z-index: 3; display: block; padding: 5px 8px; font-size: 12px; @@ -498,11 +497,15 @@ div.section > h2 { background-color: #fff; border: 1px solid #e1e1e8; border-radius: 0 4px 0 4px; -} -.btn-clipboard-hover { - color: #fff; - background-color: @brand-primary; - border-color: @brand-primary; + + &:focus { + outline: none; + } + + &.active { + background-color: #ffffad; + .transition(background-color .5s linear); + } } // rST styles diff --git a/doc/modules/api_integration.rst b/doc/modules/api_integration.rst index 43301979d0a..21d0163ba46 100644 --- a/doc/modules/api_integration.rst +++ b/doc/modules/api_integration.rst @@ -43,8 +43,8 @@ Connection and authentication url, db, username, password = \ info['host'], info['database'], info['user'], info['password'] common = XMLRPC::Client.new2("#{url}/xmlrpc/2/common") - uid = common.authenticate(db, username, password, {}) - models = xmlrpclib.ServerProxy('{}/xmlrpc/2/object'.format(url)) + uid = common.call('authenticate', db, username, password, {}) + models = XMLRPC::Client.new2("#{url}/xmlrpc/2/object").proxy .. code-block:: php From 166ddc98a959abd0b9de9e5e7f8116490a2ac03d Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Fri, 7 Nov 2014 14:13:15 +0100 Subject: [PATCH 19/37] [FIX] allow word wrapping in stripe-style code examples --- doc/_themes/odoodoc/static/style.css | 3 +++ doc/_themes/odoodoc/static/style.less | 7 ++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/doc/_themes/odoodoc/static/style.css b/doc/_themes/odoodoc/static/style.css index 443cae9b7b5..19f5270109e 100644 --- a/doc/_themes/odoodoc/static/style.css +++ b/doc/_themes/odoodoc/static/style.css @@ -6757,6 +6757,9 @@ td.field-body > ul { width: 0; border-left: 1px solid #777777; } + .stripe .highlight pre { + white-space: pre-wrap; + } } .stripe .switcher { color: white; diff --git a/doc/_themes/odoodoc/static/style.less b/doc/_themes/odoodoc/static/style.less index a896e01e170..719fa6f10e3 100644 --- a/doc/_themes/odoodoc/static/style.less +++ b/doc/_themes/odoodoc/static/style.less @@ -469,7 +469,7 @@ div.section > h2 { padding: 4px; font-size: 75%; - // code block lines should not wrap + // don't break lines within words word-break: normal; word-wrap: normal; } @@ -595,6 +595,11 @@ td.field-body { border-left: 1px solid @gray-light; } } + + .highlight pre { + // but allow additional line-breaks between "words" to avoid overflow + white-space: pre-wrap; + } } .switcher { From 967969d7b4460a52e826514ead0da0af68914e54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Fri, 7 Nov 2014 14:21:01 +0100 Subject: [PATCH 20/37] [FIX] kanban view background is correctly set (web_kanban) the .oe_background_grey class only changed part of the kanban background when it was scrolled. This makes sure that the full table has a grey background. --- addons/web_kanban/static/src/css/kanban.css | 3 +++ addons/web_kanban/static/src/css/kanban.sass | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/addons/web_kanban/static/src/css/kanban.css b/addons/web_kanban/static/src/css/kanban.css index eac7f20dcf6..fbda6d3e4c5 100644 --- a/addons/web_kanban/static/src/css/kanban.css +++ b/addons/web_kanban/static/src/css/kanban.css @@ -2,6 +2,9 @@ background: white; height: inherit; } +.openerp .oe_kanban_view.oe_background_grey > table.oe_kanban_groups { + background-color: #eee; +} .openerp .oe_kanban_view .oe_kanban_column_higlight { background: #eeddf6 !important; } diff --git a/addons/web_kanban/static/src/css/kanban.sass b/addons/web_kanban/static/src/css/kanban.sass index 8398f12ae08..9d329653ce6 100644 --- a/addons/web_kanban/static/src/css/kanban.sass +++ b/addons/web_kanban/static/src/css/kanban.sass @@ -50,6 +50,9 @@ // KanbanView {{{ //background: url(data:image/pngbase64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAKElEQVQIHWP8DwTv379nAAFBQUEGhnfv3oHEwADEZgJLIRGMIClkLQCr3x2Htp/lLwAAAABJRU5ErkJggg==) background: white + &.oe_background_grey + > table.oe_kanban_groups + background-color: #eee height: inherit .oe_kanban_column_higlight background: #eeddf6 !important @@ -539,7 +542,6 @@ .oe_kanban_column, .oe_kanban_group_header padding: 0px background: #ffffff - .openerp .oe_popup_form .oe_kanban_buttons From ce0521aa999d1bc07ad8c2a2908c9be16e00625e Mon Sep 17 00:00:00 2001 From: Nick Booker Date: Fri, 7 Nov 2014 14:25:04 +0100 Subject: [PATCH 21/37] [REF] account_anglo_saxon: refactor anglo saxon move lines So that we can customise the real-time stock journal items made during sale invoice postings for certain companies who account for it slightly differently. --- addons/account_anglo_saxon/invoice.py | 231 ++++++++++++++------------ 1 file changed, 129 insertions(+), 102 deletions(-) diff --git a/addons/account_anglo_saxon/invoice.py b/addons/account_anglo_saxon/invoice.py index de085e32e30..e4ea2adb748 100644 --- a/addons/account_anglo_saxon/invoice.py +++ b/addons/account_anglo_saxon/invoice.py @@ -34,114 +34,15 @@ class account_invoice_line(osv.osv): def move_line_get(self, cr, uid, invoice_id, context=None): res = super(account_invoice_line,self).move_line_get(cr, uid, invoice_id, context=context) inv = self.pool.get('account.invoice').browse(cr, uid, invoice_id, context=context) - company_currency = inv.company_id.currency_id.id - def get_price(cr, uid, inv, company_currency, i_line, price_unit): - cur_obj = self.pool.get('res.currency') - decimal_precision = self.pool.get('decimal.precision') - if inv.currency_id.id != company_currency: - price = cur_obj.compute(cr, uid, company_currency, inv.currency_id.id, price_unit * i_line.quantity, context={'date': inv.date_invoice}) - else: - price = price_unit * i_line.quantity - return round(price, decimal_precision.precision_get(cr, uid, 'Account')) - if inv.type in ('out_invoice','out_refund'): for i_line in inv.invoice_line: - if i_line.product_id and i_line.product_id.valuation == 'real_time': - # debit account dacc will be the output account - # first check the product, if empty check the category - dacc = i_line.product_id.property_stock_account_output and i_line.product_id.property_stock_account_output.id - if not dacc: - dacc = i_line.product_id.categ_id.property_stock_account_output_categ and i_line.product_id.categ_id.property_stock_account_output_categ.id - # in both cases the credit account cacc will be the expense account - # first check the product, if empty check the category - cacc = i_line.product_id.property_account_expense and i_line.product_id.property_account_expense.id - if not cacc: - cacc = i_line.product_id.categ_id.property_account_expense_categ and i_line.product_id.categ_id.property_account_expense_categ.id - if dacc and cacc: - price_unit = i_line.move_id and i_line.move_id.price_unit or i_line.product_id.standard_price - res.append({ - 'type':'src', - 'name': i_line.name[:64], - 'price_unit':price_unit, - 'quantity':i_line.quantity, - 'price':get_price(cr, uid, inv, company_currency, i_line, price_unit), - 'account_id':dacc, - 'product_id':i_line.product_id.id, - 'uos_id':i_line.uos_id.id, - 'account_analytic_id': False, - 'taxes':i_line.invoice_line_tax_id, - }) - - res.append({ - 'type':'src', - 'name': i_line.name[:64], - 'price_unit':price_unit, - 'quantity':i_line.quantity, - 'price': -1 * get_price(cr, uid, inv, company_currency, i_line, price_unit), - 'account_id':cacc, - 'product_id':i_line.product_id.id, - 'uos_id':i_line.uos_id.id, - 'account_analytic_id': False, - 'taxes':i_line.invoice_line_tax_id, - }) + res.extend(self._anglo_saxon_sale_move_lines(cr, uid, i_line, res, context=context)) elif inv.type in ('in_invoice','in_refund'): for i_line in inv.invoice_line: - if i_line.product_id and i_line.product_id.valuation == 'real_time': - if i_line.product_id.type != 'service': - # get the price difference account at the product - acc = i_line.product_id.property_account_creditor_price_difference and i_line.product_id.property_account_creditor_price_difference.id - if not acc: - # if not found on the product get the price difference account at the category - acc = i_line.product_id.categ_id.property_account_creditor_price_difference_categ and i_line.product_id.categ_id.property_account_creditor_price_difference_categ.id - a = None - - # oa will be the stock input account - # first check the product, if empty check the category - oa = i_line.product_id.property_stock_account_input and i_line.product_id.property_stock_account_input.id - if not oa: - oa = i_line.product_id.categ_id.property_stock_account_input_categ and i_line.product_id.categ_id.property_stock_account_input_categ.id - if oa: - # get the fiscal position - fpos = i_line.invoice_id.fiscal_position or False - a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, oa) - diff_res = [] - decimal_precision = self.pool.get('decimal.precision') - account_prec = decimal_precision.precision_get(cr, uid, 'Account') - # calculate and write down the possible price difference between invoice price and product price - for line in res: - if line.get('invl_id', 0) == i_line.id and a == line['account_id']: - uom = i_line.product_id.uos_id or i_line.product_id.uom_id - valuation_price_unit = self.pool.get('product.uom')._compute_price(cr, uid, uom.id, i_line.product_id.standard_price, i_line.uos_id.id) - if i_line.product_id.cost_method != 'standard' and i_line.purchase_line_id: - #for average/fifo/lifo costing method, fetch real cost price from incomming moves - stock_move_obj = self.pool.get('stock.move') - valuation_stock_move = stock_move_obj.search(cr, uid, [('purchase_line_id', '=', i_line.purchase_line_id.id)], limit=1, context=context) - if valuation_stock_move: - valuation_price_unit = stock_move_obj.browse(cr, uid, valuation_stock_move[0], context=context).price_unit - if inv.currency_id.id != company_currency: - valuation_price_unit = self.pool.get('res.currency').compute(cr, uid, company_currency, inv.currency_id.id, valuation_price_unit, context={'date': inv.date_invoice}) - if valuation_price_unit != i_line.price_unit and line['price_unit'] == i_line.price_unit and acc: - # price with discount and without tax included - price_unit = self.pool['account.tax'].compute_all(cr, uid, line['taxes'], - i_line.price_unit * (1-(i_line.discount or 0.0)/100.0), line['quantity'])['total'] - price_line = round(valuation_price_unit * line['quantity'], account_prec) - price_diff = round(price_unit - price_line, account_prec) - line.update({'price': price_line}) - diff_res.append({ - 'type': 'src', - 'name': i_line.name[:64], - 'price_unit': round(price_diff / line['quantity'], account_prec), - 'quantity': line['quantity'], - 'price': price_diff, - 'account_id': acc, - 'product_id': line['product_id'], - 'uos_id': line['uos_id'], - 'account_analytic_id': line['account_analytic_id'], - 'taxes': line.get('taxes', []), - }) - res += diff_res + res.extend(self._anglo_saxon_purchase_move_lines(cr, uid, i_line, res, context=context)) return res + def product_id_change(self, cr, uid, ids, product, uom_id, qty=0, name='', type='out_invoice', partner_id=False, fposition_id=False, price_unit=False, currency_id=False, company_id=None, context=None): fiscal_pool = self.pool.get('account.fiscal.position') res = super(account_invoice_line, self).product_id_change(cr, uid, ids, product, uom_id, qty, name, type, partner_id, fposition_id, price_unit, currency_id, company_id, context) @@ -163,6 +64,132 @@ class account_invoice_line(osv.osv): res['value'].update({'account_id':a}) return res + def _get_price(self, cr, uid, inv, company_currency, i_line, price_unit): + cur_obj = self.pool.get('res.currency') + decimal_precision = self.pool.get('decimal.precision') + if inv.currency_id.id != company_currency: + price = cur_obj.compute(cr, uid, company_currency, inv.currency_id.id, price_unit * i_line.quantity, context={'date': inv.date_invoice}) + else: + price = price_unit * i_line.quantity + return round(price, decimal_precision.precision_get(cr, uid, 'Account')) + + def _anglo_saxon_sale_move_lines(self, cr, uid, i_line, res, context=None): + """Return the additional move lines for sales invoices and refunds. + + i_line: An account.invoice.line object. + res: The move line entries produced so far by the parent move_line_get. + """ + inv = i_line.invoice_id + company_currency = inv.company_id.currency_id.id + + if i_line.product_id and i_line.product_id.valuation == 'real_time': + # debit account dacc will be the output account + # first check the product, if empty check the category + dacc = i_line.product_id.property_stock_account_output and i_line.product_id.property_stock_account_output.id + if not dacc: + dacc = i_line.product_id.categ_id.property_stock_account_output_categ and i_line.product_id.categ_id.property_stock_account_output_categ.id + # in both cases the credit account cacc will be the expense account + # first check the product, if empty check the category + cacc = i_line.product_id.property_account_expense and i_line.product_id.property_account_expense.id + if not cacc: + cacc = i_line.product_id.categ_id.property_account_expense_categ and i_line.product_id.categ_id.property_account_expense_categ.id + if dacc and cacc: + price_unit = i_line.move_id and i_line.move_id.price_unit or i_line.product_id.standard_price + return [ + { + 'type':'src', + 'name': i_line.name[:64], + 'price_unit':price_unit, + 'quantity':i_line.quantity, + 'price':self._get_price(cr, uid, inv, company_currency, i_line, price_unit), + 'account_id':dacc, + 'product_id':i_line.product_id.id, + 'uos_id':i_line.uos_id.id, + 'account_analytic_id': False, + 'taxes':i_line.invoice_line_tax_id, + }, + + { + 'type':'src', + 'name': i_line.name[:64], + 'price_unit':price_unit, + 'quantity':i_line.quantity, + 'price': -1 * self._get_price(cr, uid, inv, company_currency, i_line, price_unit), + 'account_id':cacc, + 'product_id':i_line.product_id.id, + 'uos_id':i_line.uos_id.id, + 'account_analytic_id': False, + 'taxes':i_line.invoice_line_tax_id, + }, + ] + return [] + + + def _anglo_saxon_purchase_move_lines(self, cr, uid, i_line, res, context=None): + """Return the additional move lines for purchase invoices and refunds. + + i_line: An account.invoice.line object. + res: The move line entries produced so far by the parent move_line_get. + """ + inv = i_line.invoice_id + company_currency = inv.company_id.currency_id.id + if i_line.product_id and i_line.product_id.valuation == 'real_time': + if i_line.product_id.type != 'service': + # get the price difference account at the product + acc = i_line.product_id.property_account_creditor_price_difference and i_line.product_id.property_account_creditor_price_difference.id + if not acc: + # if not found on the product get the price difference account at the category + acc = i_line.product_id.categ_id.property_account_creditor_price_difference_categ and i_line.product_id.categ_id.property_account_creditor_price_difference_categ.id + a = None + + # oa will be the stock input account + # first check the product, if empty check the category + oa = i_line.product_id.property_stock_account_input and i_line.product_id.property_stock_account_input.id + if not oa: + oa = i_line.product_id.categ_id.property_stock_account_input_categ and i_line.product_id.categ_id.property_stock_account_input_categ.id + if oa: + # get the fiscal position + fpos = i_line.invoice_id.fiscal_position or False + a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, oa) + diff_res = [] + decimal_precision = self.pool.get('decimal.precision') + account_prec = decimal_precision.precision_get(cr, uid, 'Account') + # calculate and write down the possible price difference between invoice price and product price + for line in res: + if line.get('invl_id', 0) == i_line.id and a == line['account_id']: + uom = i_line.product_id.uos_id or i_line.product_id.uom_id + valuation_price_unit = self.pool.get('product.uom')._compute_price(cr, uid, uom.id, i_line.product_id.standard_price, i_line.uos_id.id) + if i_line.product_id.cost_method != 'standard' and i_line.purchase_line_id: + #for average/fifo/lifo costing method, fetch real cost price from incomming moves + stock_move_obj = self.pool.get('stock.move') + valuation_stock_move = stock_move_obj.search(cr, uid, [('purchase_line_id', '=', i_line.purchase_line_id.id)], limit=1, context=context) + if valuation_stock_move: + valuation_price_unit = stock_move_obj.browse(cr, uid, valuation_stock_move[0], context=context).price_unit + if inv.currency_id.id != company_currency: + valuation_price_unit = self.pool.get('res.currency').compute(cr, uid, company_currency, inv.currency_id.id, valuation_price_unit, context={'date': inv.date_invoice}) + if valuation_price_unit != i_line.price_unit and line['price_unit'] == i_line.price_unit and acc: + # price with discount and without tax included + price_unit = self.pool['account.tax'].compute_all(cr, uid, line['taxes'], + i_line.price_unit * (1-(i_line.discount or 0.0)/100.0), line['quantity'])['total'] + price_line = round(valuation_price_unit * line['quantity'], account_prec) + price_diff = round(price_unit - price_line, account_prec) + line.update({'price': price_line}) + diff_res.append({ + 'type': 'src', + 'name': i_line.name[:64], + 'price_unit': round(price_diff / line['quantity'], account_prec), + 'quantity': line['quantity'], + 'price': price_diff, + 'account_id': acc, + 'product_id': line['product_id'], + 'uos_id': line['uos_id'], + 'account_analytic_id': line['account_analytic_id'], + 'taxes': line.get('taxes', []), + }) + return diff_res + return [] + + class account_invoice(osv.osv): _inherit = "account.invoice" From e7b6d0bda94e092ef9846f46c32693315108d629 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Fri, 7 Nov 2014 14:42:33 +0100 Subject: [PATCH 22/37] [ADD] doc: guide to the new API Raw API documentation is not sufficient for people to learn the working principles of the API, especially when they already know the old one. And some things have changed since the opendays so the presentations don't reflect the current state of affairs. Closes #3483 --- doc/glossary.rst | 3 + doc/reference/orm.rst | 678 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 676 insertions(+), 5 deletions(-) diff --git a/doc/glossary.rst b/doc/glossary.rst index 278098a5923..29cf6c4c7e6 100644 --- a/doc/glossary.rst +++ b/doc/glossary.rst @@ -13,6 +13,9 @@ ``account.invoice_graph``). From within a module, the :samp:`{module}.` prefix can be left out. + Sometimes referred to as "xml id" or ``xml_id`` as XML-based + :ref:`reference/data` make extensive use of them. + format string inspired by `jinja variables`_, format strings allow more easily mixing literal content and computed content (expressions): content diff --git a/doc/reference/orm.rst b/doc/reference/orm.rst index 03548d1e399..94aa18b4863 100644 --- a/doc/reference/orm.rst +++ b/doc/reference/orm.rst @@ -4,10 +4,678 @@ ORM === +Recordsets +========== + +.. versionadded:: 8.0 + +Interaction with models and records is performed through recordsets, a sorted +set of records of the same model. + +.. warning:: contrary to what the name implies, it is currently possible for + recordsets to contain duplicates. This may change in the future. + +Methods defined on a model are executed on a recordset, and their ``self`` is +a recordset:: + + class AModel(Model): + _name = 'a.model' + def a_method(self): + # self can be anywhere between 0 records and all records in the + # database + self.do_operation() + +Iterating on a recordset will yield new sets of *a single record* +("singletons"), much like iterating on a Python string yields strings of a +single characters:: + + def do_operation(self): + print self # => a.model(1, 2, 3, 4, 5) + for record in self: + print record # => a.model(1), then a.model(2), then a.model(3), ... + +Field access +------------ + +Recordsets provide an "Active Record" interface: model fields can be read and +written directly from the record, but only on singletons (single-record +recordsets). Setting a field's value triggers an update to the database:: + + >>> record.name + Example Name + >>> record.company_id.name + Company Name + >>> record.name = "Bob" + +Trying to read or write a field on multiple records will raise an error. + +Accessing a relational field (:class:`~openerp.fields.Many2one`, +:class:`~openerp.fields.One2many`, :class:`~openerp.fields.Many2many`) +*always* returns a recordset, empty if the field is not set. + +.. danger:: + + each assignment to a field triggers a database update, when setting + multiple fields at the same time or setting fields on multiple records + (to the same value), use :meth:`~openerp.models.Model.write`:: + + # 3 * len(records) database updates + for record in records: + record.a = 1 + record.b = 2 + record.c = 3 + + # len(records) database updates + for record in records: + record.write({'a': 1, 'b': 2, 'c': 3}) + + # 1 database update + records.write({'a': 1, 'b': 2, 'c': 3}) + +Set operations +-------------- + +Recordsets are immutable, but sets of the same model can be combined using +various set operations, returning new recordsets. Set operations do *not* +preserve order. + +.. addition preserves order but can introduce duplicates + +* ``record in set`` returns whether ``record`` (which must be a 1-element + recordset) is present in ``set``. ``record not in set`` is the inverse + operation +* ``set1 | set2`` returns the union of the two recordsets, a new recordset + containing all records present in either source +* ``set1 & set2`` returns the intersection of two recordsets, a new recordset + containing only records present in both sources +* ``set1 - set2`` returns a new recordset containing only records of ``set1`` + which are *not* in ``set2`` + +Other recordset operations +-------------------------- + +Recordsets are iterable so the usual Python tools are available for +transformation (:func:`python:map`, :func:`python:sorted`, +:func:`~python:itertools.ifilter`, ...) however these return either a +:class:`python:list` or an :term:`python:iterator`, removing the ability to +call methods on their result, or to use set operations. + +Recordsets therefore provide these operations returning recordsets themselves +(when possible): + +:meth:`~openerp.models.Model.filtered` + returns a recordset containing only records satisfying the provided + predicate function. The predicate can also be a string to filter by a + field being true or false:: + + # only keep records whose company is the current user's + records.filtered(lambda r: r.company_id == user.company_id) + + # only keep records whose partner is a company + records.filtered("partner_id.is_company") + +:meth:`~openerp.models.Model.sorted` + returns a recordset sorted by the provided key function. If no key + is provided, use the model's default sort order:: + + # sort records by name + records.sorted(key=lambda r: r.name) + +:meth:`~openerp.models.Model.mapped` + applies the provided function to each record in the recordset, returns + a recordset if the results are recordsets:: + + # returns a list of summing two fields for each record in the set + records.mapped(lambda r: r.field1 + r.field2) + + The provided function can be a string to get field values:: + + # returns a list of names + records.mapped('name') + + # returns a recordset of partners + record.mapped('partner_id') + + # returns the union of all partner banks, with duplicates removed + record.mapped('partner_id.bank_ids') + +Environment +=========== + +The :class:`~openerp.api.Environment` stores various contextual data used by +the ORM: the database cursor (for database queries), the current user +(for access rights checking) and the current context (storing arbitrary +metadata). The environment also stores caches. + +All recordsets have an environment, which is immutable, can be accessed +using :attr:`~openerp.models.Model.env` and gives access to the current user +(:attr:`~openerp.api.Environment.user`), the cursor +(:attr:`~openerp.api.Environment.cr`) or the context +(:attr:`~openerp.api.Environment.context`):: + + >>> records.env + + >>> records.env.user + res.user(3) + >>> records.env.cr + >> self.env['res.partner'] + res.partner + >>> self.env['res.partner'].search([['is_company', '=', True], ['customer', '=', True]]) + res.partner(7, 18, 12, 14, 17, 19, 8, 31, 26, 16, 13, 20, 30, 22, 29, 15, 23, 28, 74) + +Altering the environment +------------------------ + +The environment can be customized from a recordset. This returns a new +version of the recordset using the altered environment. + +:meth:`~openerp.models.Model.sudo` + creates a new environment with the provided user set, uses the + administrator if none is provided (to bypass access rights/rules in safe + contexts), returns a copy of the recordset it is called on using the + new environment:: + + # create partner object as administrator + env['res.partner'].sudo().create({'name': "A Partner"}) + + # list partners visible by the "public" user + public = env.ref('base.public_user') + env['res.partner'].sudo(public).search([]) + +:meth:`~openerp.models.Model.with_context` + #. can take a single positional parameter, which replaces the current + environment's context + #. can take any number of parameters by keyword, which are added to either + the current environment's context or the context set during step 1 + + :: + + # look for partner, or create one with specified timezone if none is + # found + env['res.partner'].with_context(tz=a_tz).find_or_create(email_address) + +:meth:`~openerp.models.Model.with_env` + replaces the existing environment entirely + +Common ORM methods +================== + +.. maybe these clarifications/examples should be in the APIDoc? + +:meth:`~openerp.models.Model.search` + Takes a :ref:`search domain `, returns a recordset + of matching records. Can return a subset of matching records (``offset`` + and ``limit`` parameters) and be ordered (``order`` parameter):: + + >>> # searches the current model + >>> self.search([('is_company', '=', True), ('customer', '=', True)]) + res.partner(7, 18, 12, 14, 17, 19, 8, 31, 26, 16, 13, 20, 30, 22, 29, 15, 23, 28, 74) + >>> self.search([('is_company', '=', True)], limit=1).name + 'Agrolait' + + .. tip:: to just check if any record matches a domain, or count the number + of records which do, use + :meth:`~openerp.models.Model.search_count` +:meth:`~openerp.models.Model.create` + Takes a number of field values, and returns a recordset containing the + record created:: + + >>> self.create({'name': "New Name"}) + res.partner(78) + +:meth:`~openerp.models.Model.write` + Takes a number of field values, writes them to all the records in its + recordset. Does not return anything:: + + self.write({'name': "Newer Name"}) + +:meth:`~openerp.models.Model.browse` + Takes a database id or a list of ids and returns a recordset, useful when + record ids are obtained from outside Odoo (e.g. round-trip through + external system) or :ref:`when calling methods in the old API + `:: + + >>> self.browse([7, 18, 12]) + res.partner(7, 18, 12]) + +:meth:`~openerp.models.Model.exists` + Returns a new recordset containing only the records which exist in the + database. Can be used to check whether a record (e.g. obtained externally) + still exists:: + + if not record.exists(): + raise Exception("The record has been deleted") + + or after calling a method which could have removed some records:: + + records.may_remove_some() + # only keep records which were not deleted + records = records.exists() + +:meth:`~openerp.api.Environment.ref` + Environment method returning the record matching a provided + :term:`external id`:: + + >>> env.ref('base.group_public') + res.groups(2) + +:meth:`~openerp.models.Model.ensure_one` + checks that the recordset is a singleton (only contains a single record), + raises an error otherwise:: + + records.ensure_one() + # is equivalent to but clearer than: + assert len(records) == 1, "Expected singleton" + +Creating Models +=============== + +Model fields are defined as attributes on the model itself:: + + from openerp import models, fields + class AModel(Model): + _name = 'a.model.name' + + field1 = fields.Char() + +.. warning:: this means you can not define a field and a method with the same + name, they will conflict + +By default, the field's label (user-visible name) is a capitalized version of +the field name, this can be overridden with the ``string`` parameter:: + + field2 = fields.Integer(string="an other field") + +For the various field types and parameters, see :ref:`the fields reference +`. + +Default values are defined as parameters on fields, either a value:: + + a_field = fields.Char(default="a value") + +or a function called to compute the default value, which should return that +value:: + + a_field = fields.Char(default=compute_default_value) + def compute_default_value(self): + return self.get_value() + +Computed fields +--------------- + +Fields can be computed (instead of read straight from the database) using the +``compute`` parameter. **It must assign the computed value to the field**. If +it uses the values of other *fields*, it should specify those fields using +:func:`~openerp.api.depends`:: + + from openerp import api + total = fields.Float(compute='_compute_total') + + @api.depends('value', 'tax') + def _compute_total(self): + for record in self: + record.total = record.value + record.value * record.tax + +* dependencies can be dotted paths when using sub-fields:: + + @api.depends('line_ids.value') + def _compute_total(self): + for record in self: + record.total = sum(line.value for line in record.line_ids) + +* computed fields are not stored by default, they are computed and + returned when requested. Setting ``store=True`` will store them in the + database and automatically enable searching +* searching on a computed field can also be enabled by setting the ``search`` + parameter. The value is a method name returning a + :ref:`reference/orm/domains`:: + + upper_name = field.Char(compute='_compute_upper', search='_search_upper') + + def _search_upper(self, operator, value): + if operator == 'like': + operator = 'ilike' + return [('name', operator, value)] + +* to allow *setting* values on a computed field, use the ``inverse`` + parameter. It is the name of a function reversing the computation and + setting the relevant fields:: + + document = fields.Char(compute='_get_document', inverse='_set_document') + + def _get_document(self): + for record in self: + with open(record.get_document_path) as f: + record.document = f.read() + def _set_document(self): + for record in self: + if not record.document: continue + with open(record.get_document_path()) as f: + f.write(record.document) + +* multiple fields can be computed at the same time by the same method, just + use the same method on all fields and set all of them:: + + discount_value = fields.Float(compute='_apply_discount') + total = fields.Float(compute='_apply_discount') + + @depends('value', 'discount') + def _apply_discount(self): + for record in self: + # compute actual discount from discount percentage + discount = self.value * self.discount + self.discount_value = discount + self.total = self.value - discount + +Related fields +'''''''''''''' + +A special case of computed fields are *related* (proxy) fields, which provide +the value of a sub-field on the current record. They are defined by setting +the ``related`` parameter and like regular computed fields they can be +stored:: + + nickname = fields.Char(related='user_id.partner_id.name', store=True) + +onchange: updating UI on the fly +-------------------------------- + +When a user changes a field's value in a form (but hasn't saved the form yet), +it can be useful to automatically update other fields based on that value +e.g. updating a final total when the tax is changed or a new invoice line is +added. + +* computed fields are automatically checked and recomputed, they do not need + an ``onchange`` +* for non-computed fields, the :func:`~openerp.api.onchange` decorator is used + to provide new field values:: + + @api.onchange('field1', 'field2') # if these fields are changed, call method + def check_change(self): + if self.field1 < self.field2: + self.field3 = True + + the changes performed during the method are then sent to the client program + and become visible to the user + +* Both computed fields and new-API onchanges are automatically called by the + client without having to add them in views +* It is possible to suppress the trigger from a specific field by adding + ``on_change="0"`` in a view:: + + + + will not trigger any interface update when the field is edited by the user, + even if there are function fields or explicit onchange depending on that + field. + +.. note:: + + ``onchange`` methods work on virtual records assignment on these records + is not written to the database, just used to know which value to send back + to the client + +.. _reference/orm/oldapi: + +Old API compatibility +--------------------- + +Odoo is currently transitioning from an older (less regular) API, it can be +necessary to manually bridge from one to the other manually: + +* RPC layers (both XML-RPC and JSON-RPC) are expressed in terms of the old + API, methods expressed purely in the new API are not available over RPC +* overridable methods may be called from older pieces of code still written + in the old API style + +The big differences between the old and new APIs are: + +* values of the :class:`~openerp.api.Environment` (cursor, user id and + context) are passed explicitly to methods instead +* record data (:attr:`~openerp.models.Model.ids`) are passed explicitly to + methods, and possibly not passed at all +* methods tend to work on lists of ids instead of recordsets + +By default, methods are assumed to use the new API style and are not callable +from the old API style. + +.. tip:: calls from the new API to the old API are bridged + :class: aphorism + + when using the new API style, calls to methods defined using the old API + are automatically converted on-the-fly, there should be no need to do + anything special:: + + >>> # method in the old API style + >>> def old_method(self, cr, uid, ids, context=None): + ... print ids + + >>> # method in the new API style + >>> def new_method(self): + ... # system automatically infers how to call the old-style + ... # method from the new-style method + ... self.old_method() + + >>> env[model].browse([1, 2, 3, 4]).new_method() + [1, 2, 3, 4] + +Two decorators can expose a new-style method to the old API: + +:func:`~openerp.api.model` + the method is exposed as not using ids, its recordset will generally be + empty. Its "old API" signature is ``cr, uid, *arguments, context``:: + + @api.model + def some_method(foo): + pass + # can be called as + old_style_model.some_method(cr, uid, a_value, context=context) + +:func:`~openerp.api.multi` + the method is exposed as taking a list of ids (possibly empty), its + "old API" signature is ``cr, uid, ids, *arguments, context``:: + + @api.multi + def some_method(foo): + pass + # can be called as + old_style_model.some_method(cr, uid, [id1, id2], a_value, context=context) + +Because new-style APIs tend to return recordsets and old-style APIs tend to +return lists of ids, there is also a decorator managing this: + +:func:`~openerp.api.returns` + the function is assumed to return a recordset, the first parameter should + be the name of the recordset's model or ``self`` (for the current model). + + No effect if the method is called in new API style, but transforms the + recordset into a list of ids when called from the old API style:: + + >>> @api.multi + ... @api.returns('self') + ... def some_method(): + ... return self + >>> new_style_model = env['a.model'].browse(1, 2, 3) + >>> new_style_model.some_method() + a.model(1, 2, 3) + >>> old_style_model = pool['a.model'] + >>> old_style_model.some_method(cr, uid, [1, 2, 3], context=context) + [1, 2, 3] + +Porting from the old API +------------------------ + +* methods still written in the old API should be automatically bridged by the + ORM, no need to switch to the old API, just call them as if they were a new + API method. See :ref:`reference/orm/oldapi/bridging` for more details. +* ``search`` returns a recordset, no point in e.g. browsing its result +* ``fields.related`` and ``fields.function`` are replaced by using a normal + field type with either a ``related`` or a ``compute`` parameter +* ``depends`` on field compute methods **must be complete**, it must list + **all** the fields and sub-fields which the compute method uses. It is + better to have too many dependencies (will recompute the field in cases + where that is not needed) than not enough (will forget to recompute the + field and then values will be incorrect) +* **remove** all ``onchange`` methods on computed fields. Computed fields are + automatically re-computed when one of their dependencies is changed, and + that is used to auto-generate ``onchange`` by the client +* the decorators :func:`~openerp.api.model` and :func:`~openerp.api.multi` are + for bridging *when calling from the old API context*, for internal or pure + new-api (e.g. compute) they are useless +* remove :attr:`~openerp.models.Model._default`, replace by ``default=`` + parameter on corresponding fields +* if a field's ``string`` is the titlecased version of the field name:: + + name = fields.Char(string="Name") + + it is useless and should be removed +* ``multi`` does not do anything on new API fields use the same ``compute`` + methods on all relevant fields for the same result +* provide ``compute``, ``inverse`` and ``search`` methods by name (as a + string), this makes them overridable (removes the need for an intermediate + "trampoline" function) +* double check that all fields and methods have different names, there is no + warning in case of collision (because Python handles it before Odoo sees + anything) +* the normal new-api import is ``from openerp import fields, models``. If + compatibility decorators are necessary, use ``from openerp import api, + fields, models`` +* avoid the :func:`~openerp.api.one` decorator, it probably does not do what + you expect +* remove explicit definition of :attr:`~openerp.models.Model.create_uid`, + :attr:`~openerp.models.Model.create_date`, + :attr:`~openerp.models.Model.write_uid` and + :attr:`~openerp.models.Model.write_date` fields: they are now created as + regular "legitimate" fields, and can be read and written like any other + field out-of-the-box +* when straight conversion is impossible (semantics can not be bridged) or the + "old API" version is not desirable and could be improved for the new API, it + is possible to use completely different "old API" and "new API" + implementations for the same method name using :func:`~openerp.api.v7` and + :func:`~openerp.api.v8`. The method should first be defined using the + old-API style and decorated with :func:`~openerp.api.v7`, it should then be + re-defined using the exact same name but the new-API style and decorated + with :func:`~openerp.api.v8`. Calls from an old-API context will be + dispatched to the first implementation and calls from a new-API context will + be dispatched to the second implementation. One implementation can call (and + frequently does) call the other by switching context. + + .. danger:: using these decorators makes methods extremely difficult to + override and harder to understand and document +* uses of :attr:`~openerp.models.Model._columns` or + :attr:`~openerp.models.Model._all_columns` should be replaced by + :attr:`~openerp.models.Model._fields`, which provides access to instances of + new-style :class:`openerp.fields.Field` instances (rather than old-style + :class:`openerp.osv.fields._column`). + + Non-stored computed fields created using the new API style are *not* + available in :attr:`~openerp.models.Model._columns` and can only be + inspected through :attr:`~openerp.models.Model._fields` +* reassigning ``self`` in a method is probably unnecessary and may break + translation introspection +* :class:`~openerp.api.Environment` objects rely on some threadlocal state, + which has to be set up before using them. It is necessary to do so using the + :meth:`openerp.api.Environment.manage` context manager when trying to use + the new API in contexts where it hasn't been set up yet, such as new threads + or a Python interactive environment:: + + >>> from openerp import api, modules + >>> r = modules.registry.RegistryManager.get('test') + >>> cr = r.cursor() + >>> env = api.Environment(cr, 1, {}) + Traceback (most recent call last): + ... + AttributeError: environments + >>> with api.Environment.manage(): + ... env = api.Environment(cr, 1, {}) + ... print env['res.partner'].browse(1) + ... + res.partner(1,) + +.. _reference/orm/oldapi/bridging: + +Automatic bridging of old API methods +''''''''''''''''''''''''''''''''''''' + +When models are initialized, all methods are automatically scanned and bridged +if they look like models declared in the old API style. This bridging makes +them transparently callable from new-API-style methods. + +Methods are matched as "old-API style" if their second positional parameter +(after ``self``) is called either ``cr`` or ``cursor``. The system also +recognizes the third positional parameter being called ``uid`` or ``user`` and +the fourth being called ``id`` or ``ids``. It also recognizes the presence of +any parameter called ``context``. + +When calling such methods from a new API context, the system will +automatically fill matched parameters from the current +:class:`~openerp.api.Environment` (for :attr:`~openerp.api.Environment.cr`, +:attr:`~openerp.api.Environment.user` and +:attr:`~openerp.api.Environment.context`) or the current recordset (for ``id`` +and ``ids``). + +In the rare cases where it is necessary, the bridging can be customized by +decorating the old-style method: + +* disabling it entirely, by decorating a method with + :func:`~openerp.api.noguess` there will be no bridging and methods will be + called the exact same way from the new and old API styles +* defining the bridge explicitly, this is mostly for methods which are matched + incorrectly (because parameters are named in unexpected ways): + + :func:`~openerp.api.cr` + will automatically prepend the current cursor to explicitly provided + parameters, positionally + :func:`~openerp.api.cr_uid` + will automatically prepend the current cursor and user's id to explictly + provided parameters + :func:`~openerp.api.cr_uid_ids` + will automatically prepend the current cursor, user's id and recordset's + ids to explicitly provided parameters + :func:`~openerp.api.cr_uid_id` + will loop over the current recordset and call the method once for each + record, prepending the current cursor, user's id and record's id to + explicitly provided parameters. + + .. danger:: the result of this wrapper is *always a list* when calling + from a new-API context + + All of these methods have a ``_context``-suffixed version + (e.g. :func:`~openerp.api.cr_uid_context`) which also passes the current + context *by keyword*. +* dual implementations using :func:`~openerp.api.v7` and + :func:`~openerp.api.v8` will be ignored as they provide their own "bridging" + +Low-level SQL +------------- + +The :attr:`~openerp.api.Environment.cr` attribute on environments is the +cursor for the current database transaction and allows executing SQL directly, +either for queries which are difficult to express using the ORM (e.g. complex +joins) or for performance reasons:: + + self.env.cr.execute("some_sql", param1, param2, param3) + +Because models use the same cursor and the :class:`~openerp.api.Environment` +holds various caches, these caches must be invalidated when *altering* the +database in raw SQL, or further uses of models may become incoherent. It is +necessary to clear caches when using ``CREATE``, ``UPDATE`` or ``DELETE`` in +SQL, but not ``SELECT`` (which simply reads the database). + +Clearing caches can be performed using the +:meth:`~openerp.api.Environment.invalidate_all` method of the +:class:`~openerp.api.Environment` object. + .. _reference/orm/model: -Model -===== +Model Reference +=============== .. - can't get autoattribute to import docstrings, so use regular attribute - no autoclassmethod @@ -121,7 +789,7 @@ Model .. automethod:: exists .. automethod:: filtered .. automethod:: sorted - .. automethod:: update + .. automethod:: mapped .. rubric:: Environment swapping @@ -234,7 +902,8 @@ Method decorators ================= .. automodule:: openerp.api - :members: one, multi, model, depends, constrains, onchange, returns + :members: one, multi, model, depends, constrains, onchange, returns, + v7, v8 .. _reference/orm/fields: @@ -499,4 +1168,3 @@ Domain criteria can be combined using logical operators in *prefix* form: (name is 'ABC') AND (language is NOT english) AND (country is Belgium OR Germany) - From f3b3776128dcaf0d1ebd8ae9dbf3a1d639595df7 Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Fri, 7 Nov 2014 17:39:57 +0100 Subject: [PATCH 23/37] [FIX] account: recompute date_due Add on_change to recompute the due date when the invoice date is modified (it was only done when modified the partner_id field Fixes #3413 --- addons/account/account_invoice_view.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/account/account_invoice_view.xml b/addons/account/account_invoice_view.xml index 79c6a7fca45..f081b7f0c26 100644 --- a/addons/account/account_invoice_view.xml +++ b/addons/account/account_invoice_view.xml @@ -178,7 +178,7 @@
- + @@ -327,7 +327,7 @@ - + Date: Sun, 9 Nov 2014 10:01:13 +0100 Subject: [PATCH 24/37] [FIX] stock_picking_wave: Missing _description --- addons/stock_picking_wave/stock_picking_wave.py | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/stock_picking_wave/stock_picking_wave.py b/addons/stock_picking_wave/stock_picking_wave.py index 0082d500d98..48779f6882c 100644 --- a/addons/stock_picking_wave/stock_picking_wave.py +++ b/addons/stock_picking_wave/stock_picking_wave.py @@ -4,6 +4,7 @@ from openerp.tools.translate import _ class stock_picking_wave(osv.osv): _name = "stock.picking.wave" + _description = "Picking Wave" _order = "name desc" _columns = { 'name': fields.char('Picking Wave Name', required=True, help='Name of the picking wave', copy=False), From 134716308266ff9f5304cf5b429d2df5ebd93454 Mon Sep 17 00:00:00 2001 From: Simon Lejeune Date: Mon, 10 Nov 2014 11:49:02 +0100 Subject: [PATCH 25/37] [FIX] Packaging: Debian: ignore root Makefile Commit 388732dfa6a1 introduced a makefile at the root of the project, and dh_auto_build is using this file instead of setup.py. Explicitely tell debhelper to not use the makefile. --- debian/rules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/rules b/debian/rules index 2538f9f61e2..ae227e8947a 100755 --- a/debian/rules +++ b/debian/rules @@ -2,4 +2,4 @@ # vim:noet: %: - dh ${@} \ No newline at end of file + dh ${@} -Spython_distutils \ No newline at end of file From ccba67ed536d11a47cadb4a996097497f3ff136f Mon Sep 17 00:00:00 2001 From: Frederic van der Essen Date: Mon, 10 Nov 2014 11:46:46 +0100 Subject: [PATCH 26/37] [FIX] point_of_sale: when loading models with a specified id, use read instead of search_read, to prevent inactive records from being skipped --- addons/point_of_sale/static/src/js/models.js | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index 4719b82e974..f2285b8a31e 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -131,12 +131,12 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal { model: 'res.users', fields: ['name','company_id'], - domain: function(self){ return [['id','=',self.session.uid]]; }, + ids: function(self){ return [self.session.uid]; }, loaded: function(self,users){ self.user = users[0]; }, },{ model: 'res.company', fields: [ 'currency_id', 'email', 'website', 'company_registry', 'vat', 'name', 'phone', 'partner_id' , 'country_id'], - domain: function(self){ return [['id','=',self.user.company_id[0]]]; }, + ids: function(self){ return [self.user.company_id[0]] }, loaded: function(self,companies){ self.company = companies[0]; }, },{ model: 'product.uom', @@ -222,17 +222,17 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal },{ model: 'stock.location', fields: [], - domain: function(self){ return [['id','=', self.config.stock_location_id[0]]]; }, + ids: function(self){ return [self.config.stock_location_id[0]]; }, loaded: function(self, locations){ self.shop = locations[0]; }, },{ model: 'product.pricelist', fields: ['currency_id'], - domain: function(self){ return [['id','=',self.config.pricelist_id[0]]]; }, + ids: function(self){ return [self.config.pricelist_id[0]]; }, loaded: function(self, pricelists){ self.pricelist = pricelists[0]; }, },{ model: 'res.currency', fields: ['symbol','position','rounding','accuracy'], - domain: function(self){ return [['id','=',self.pricelist.currency_id[0]]]; }, + ids: function(self){ return [self.pricelist.currency_id[0]]; }, loaded: function(self, currencies){ self.currency = currencies[0]; }, @@ -365,11 +365,17 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal var fields = typeof model.fields === 'function' ? model.fields(self,tmp) : model.fields; var domain = typeof model.domain === 'function' ? model.domain(self,tmp) : model.domain; var context = typeof model.context === 'function' ? model.context(self,tmp) : model.context; + var ids = typeof model.ids === 'function' ? model.ids(self,tmp) : model.ids; progress += progress_step; + if( model.model ){ - new instance.web.Model(model.model).query(fields).filter(domain).context(context).all() - .then(function(result){ + if (model.ids) { + var records = new instance.web.Model(model.model).call('read',[ids,fields],context); + } else { + var records = new instance.web.Model(model.model).query(fields).filter(domain).context(context).all() + } + records.then(function(result){ try{ // catching exceptions in model.loaded(...) $.when(model.loaded(self,result,tmp)) .then(function(){ load_model(index + 1); }, From 0d830893985a17f26f734dc8664033cc0c9c5307 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Mon, 10 Nov 2014 15:05:19 +0100 Subject: [PATCH 27/37] [FIX] /web/binary/{image,saveas}: use request.registry instead of deprecated `request.session.model` This makes the controllers work also for public users, e.g. in the frontend. --- addons/web/controllers/main.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index 2e44fffd312..e2c3d3092c8 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -1019,7 +1019,8 @@ class Binary(http.Controller): @http.route('/web/binary/image', type='http', auth="public") def image(self, model, id, field, **kw): last_update = '__last_update' - Model = request.session.model(model) + Model = request.registry[model] + cr, uid, context = request.cr, request.uid, request.context headers = [('Content-Type', 'image/png')] etag = request.httprequest.headers.get('If-None-Match') hashed_session = hashlib.md5(request.session_id).hexdigest() @@ -1032,15 +1033,15 @@ class Binary(http.Controller): if not id and hashed_session == etag: return werkzeug.wrappers.Response(status=304) else: - date = Model.read([id], [last_update], request.context)[0].get(last_update) + date = Model.read(cr, uid, [id], [last_update], context)[0].get(last_update) if hashlib.md5(date).hexdigest() == etag: return werkzeug.wrappers.Response(status=304) if not id: - res = Model.default_get([field], request.context).get(field) + res = Model.default_get(cr, uid, [field], context).get(field) image_base64 = res else: - res = Model.read([id], [last_update, field], request.context)[0] + res = Model.read(cr, uid, [id], [last_update, field], context)[0] retag = hashlib.md5(res.get(last_update)).hexdigest() image_base64 = res.get(field) @@ -1086,14 +1087,15 @@ class Binary(http.Controller): :param str filename_field: field holding the file's name, if any :returns: :class:`werkzeug.wrappers.Response` """ - Model = request.session.model(model) + Model = request.registry[model] + cr, uid, context = request.cr, request.uid, request.context fields = [field] if filename_field: fields.append(filename_field) if id: - res = Model.read([int(id)], fields, request.context)[0] + res = Model.read(cr, uid, [int(id)], fields, context)[0] else: - res = Model.default_get(fields, request.context) + res = Model.default_get(cr, uid, fields, context) filecontent = base64.b64decode(res.get(field, '')) if not filecontent: return request.not_found() From b8efdbdf395c3f82fa6c8ea483e8fa4d77f0fd2f Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Mon, 10 Nov 2014 16:20:23 +0100 Subject: [PATCH 28/37] [FIX] website_forum: avoid access error when closing spam/abuse questions Logging the user login is not necessary, but accessing it might cause access errors. Logging the UID is equivalent anyway, so let's not bother. --- addons/website_forum/models/forum.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/website_forum/models/forum.py b/addons/website_forum/models/forum.py index a9e192c8e65..4c36d68ef29 100644 --- a/addons/website_forum/models/forum.py +++ b/addons/website_forum/models/forum.py @@ -393,7 +393,7 @@ class Post(osv.Model): for post in self.browse(cr, uid, ids, context=context): if post.closed_reason_id.id in (reason_offensive, reason_spam): _logger.info('Upvoting user <%s>, reopening spam/offensive question', - post.create_uid.login) + post.create_uid) # TODO: in master, consider making this a tunable karma parameter self.pool['res.users'].add_karma(cr, SUPERUSER_ID, [post.create_uid.id], post.forum_id.karma_gen_question_downvote * -5, @@ -409,7 +409,7 @@ class Post(osv.Model): if reason_id in (reason_offensive, reason_spam): for post in self.browse(cr, uid, ids, context=context): _logger.info('Downvoting user <%s> for posting spam/offensive contents', - post.create_uid.login) + post.create_uid) # TODO: in master, consider making this a tunable karma parameter self.pool['res.users'].add_karma(cr, SUPERUSER_ID, [post.create_uid.id], post.forum_id.karma_gen_question_downvote * 5, From a2313b6311c096bdc1f1d006ddd1d23108c86628 Mon Sep 17 00:00:00 2001 From: Frederic van der Essen Date: Tue, 11 Nov 2014 16:18:27 +0100 Subject: [PATCH 29/37] [FIX] point_of_sale: correctly handle the rounding when the unit's rounding is set to zero --- addons/point_of_sale/static/src/js/models.js | 9 +++++++-- addons/web/static/src/js/formats.js | 8 ++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index f2285b8a31e..fadb4ba819c 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -689,8 +689,13 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal var quant = parseFloat(quantity) || 0; var unit = this.get_unit(); if(unit){ - this.quantity = round_pr(quant, unit.rounding); - this.quantityStr = this.quantity.toFixed(Math.ceil(Math.log(1.0 / unit.rounding) / Math.log(10))); + if (unit.rounding) { + this.quantity = round_pr(quant, unit.rounding); + this.quantityStr = this.quantity.toFixed(Math.ceil(Math.log(1.0 / unit.rounding) / Math.log(10))); + } else { + this.quantity = round_pr(quant, 1); + this.quantityStr = this.quantity.toFixed(0); + } }else{ this.quantity = quant; this.quantityStr = '' + this.quantity; diff --git a/addons/web/static/src/js/formats.js b/addons/web/static/src/js/formats.js index 3d7b574e56a..3f2e1351fd2 100644 --- a/addons/web/static/src/js/formats.js +++ b/addons/web/static/src/js/formats.js @@ -335,13 +335,13 @@ instance.web.auto_date_to_str = function(value, type) { * performs a half up rounding with arbitrary precision, correcting for float loss of precision * See the corresponding float_round() in server/tools/float_utils.py for more info * @param {Number} the value to be rounded - * @param {Number} a non zero precision parameter. eg: 0.01 rounds to two digits. + * @param {Number} a precision parameter. eg: 0.01 rounds to two digits. */ instance.web.round_precision = function(value, precision){ - if(!value){ + if (!value) { return 0; - }else if(!precision){ - throw new Error('round_precision(...): Cannot round value: '+value+' with a precision of zero (or undefined)'); + } else if (!precision || precision < 0) { + precision = 1; } var normalized_value = value / precision; var epsilon_magnitude = Math.log(Math.abs(normalized_value))/Math.log(2); From fade5ab2c27d31143b40d856c40f0fee3bd994df Mon Sep 17 00:00:00 2001 From: Frederic van der Essen Date: Tue, 11 Nov 2014 17:41:20 +0100 Subject: [PATCH 30/37] [FIX] point_of_sale: decimal precision was hardcoded to 2 in various places --- addons/point_of_sale/static/src/js/models.js | 16 +++++++++++++--- addons/point_of_sale/static/src/js/screens.js | 2 +- .../point_of_sale/static/src/js/widget_base.js | 7 ++++--- addons/point_of_sale/static/src/xml/pos.xml | 4 ++-- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index fadb4ba819c..44ff38e3587 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -235,6 +235,12 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal ids: function(self){ return [self.pricelist.currency_id[0]]; }, loaded: function(self, currencies){ self.currency = currencies[0]; + if (self.currency.rounding > 0) { + self.currency.decimals = Math.ceil(Math.log(1.0 / self.currency.rounding) / Math.log(10)); + } else { + self.currency.decimals = 0; + } + }, },{ model: 'product.packaging', @@ -789,7 +795,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal }, // changes the base price of the product for this orderline set_unit_price: function(price){ - this.price = round_di(parseFloat(price) || 0, 2); + this.price = round_di(parseFloat(price) || 0, this.pos.currency.decimals); this.trigger('change',this); }, get_unit_price: function(){ @@ -874,16 +880,20 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal this.cashregister = options.cashregister; this.name = this.cashregister.journal_id[1]; this.selected = false; + this.pos = options.pos; }, //sets the amount of money on this payment line set_amount: function(value){ - this.amount = round_di(parseFloat(value) || 0, 2); + this.amount = round_di(parseFloat(value) || 0, this.pos.currency.decimals); this.trigger('change:amount',this); }, // returns the amount of money on this paymentline get_amount: function(){ return this.amount; }, + get_amount_str: function(){ + return this.amount.toFixed(this.pos.currency.decimals); + }, set_selected: function(selected){ if(this.selected !== selected){ this.selected = selected; @@ -1008,7 +1018,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal }, addPaymentline: function(cashregister) { var paymentLines = this.get('paymentLines'); - var newPaymentline = new module.Paymentline({},{cashregister:cashregister}); + var newPaymentline = new module.Paymentline({},{cashregister:cashregister, pos:this.pos}); if(cashregister.journal.type !== 'cash'){ newPaymentline.set_amount( Math.max(this.getDueLeft(),0) ); } diff --git a/addons/point_of_sale/static/src/js/screens.js b/addons/point_of_sale/static/src/js/screens.js index 3a09233d3b2..0d3933e0b34 100644 --- a/addons/point_of_sale/static/src/js/screens.js +++ b/addons/point_of_sale/static/src/js/screens.js @@ -1130,7 +1130,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa paymentlines.bind('change:selected', this.rerender_paymentline, this); paymentlines.bind('change:amount', function(line){ if(!line.selected && line.node){ - line.node.value = line.amount.toFixed(2); + line.node.value = line.amount.toFixed(this.pos.currency.decimals); } this.update_payment_summary(); },this); diff --git a/addons/point_of_sale/static/src/js/widget_base.js b/addons/point_of_sale/static/src/js/widget_base.js index 1b6daaa9a5a..e58e50160a7 100644 --- a/addons/point_of_sale/static/src/js/widget_base.js +++ b/addons/point_of_sale/static/src/js/widget_base.js @@ -1,5 +1,8 @@ function openerp_pos_basewidget(instance, module){ //module is instance.point_of_sale + var round_di = instance.web.round_decimals; + var round_pr = instance.web.round_precision + // This is a base class for all Widgets in the POS. It exposes relevant data to the // templates : // - widget.currency : { symbol: '$' | '€' | ..., position: 'before' | 'after } @@ -25,12 +28,10 @@ function openerp_pos_basewidget(instance, module){ //module is instance.point_of this.currency = {symbol: '$', position: 'after', rounding: 0.01}; } - var decimals = Math.max(0,Math.ceil(Math.log(1.0 / this.currency.rounding) / Math.log(10))); this.format_currency = function(amount){ if(typeof amount === 'number'){ - amount = Math.round(amount*100)/100; - amount = amount.toFixed(decimals); + amount = round_di(amount,this.currency.decimals).toFixed(this.currency.decimals); } if(this.currency.position === 'after'){ return amount + ' ' + (this.currency.symbol || ''); diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml index 7832b2d0d0a..ef548830310 100644 --- a/addons/point_of_sale/static/src/xml/pos.xml +++ b/addons/point_of_sale/static/src/xml/pos.xml @@ -873,7 +873,7 @@
- + @@ -886,7 +886,7 @@ - + From d1fb79ad147e66a29a7e714c46ab4da7b4e8feea Mon Sep 17 00:00:00 2001 From: Frederic van der Essen Date: Tue, 11 Nov 2014 18:00:24 +0100 Subject: [PATCH 31/37] [FIX] point_of_sale: some pos order fields were missing the proper decimal precision --- addons/point_of_sale/point_of_sale.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/addons/point_of_sale/point_of_sale.py b/addons/point_of_sale/point_of_sale.py index 99c73fdda34..512e6aef3fa 100644 --- a/addons/point_of_sale/point_of_sale.py +++ b/addons/point_of_sale/point_of_sale.py @@ -682,7 +682,7 @@ class pos_order(osv.osv): 'date_order': fields.datetime('Order Date', readonly=True, select=True), 'user_id': fields.many2one('res.users', 'Salesman', help="Person who uses the the cash register. It can be a reliever, a student or an interim employee."), 'amount_tax': fields.function(_amount_all, string='Taxes', digits_compute=dp.get_precision('Account'), multi='all'), - 'amount_total': fields.function(_amount_all, string='Total', multi='all'), + 'amount_total': fields.function(_amount_all, string='Total', digits_compute=dp.get_precision('Account'), multi='all'), 'amount_paid': fields.function(_amount_all, string='Paid', states={'draft': [('readonly', False)]}, readonly=True, digits_compute=dp.get_precision('Account'), multi='all'), 'amount_return': fields.function(_amount_all, 'Returned', digits_compute=dp.get_precision('Account'), multi='all'), 'lines': fields.one2many('pos.order.line', 'order_id', 'Order Lines', states={'draft': [('readonly', False)]}, readonly=True, copy=True), @@ -1311,8 +1311,8 @@ class pos_order_line(osv.osv): 'product_id': fields.many2one('product.product', 'Product', domain=[('sale_ok', '=', True)], required=True, change_default=True), 'price_unit': fields.float(string='Unit Price', digits_compute=dp.get_precision('Account')), 'qty': fields.float('Quantity', digits_compute=dp.get_precision('Product UoS')), - 'price_subtotal': fields.function(_amount_line_all, multi='pos_order_line_amount', string='Subtotal w/o Tax', store=True), - 'price_subtotal_incl': fields.function(_amount_line_all, multi='pos_order_line_amount', string='Subtotal', store=True), + 'price_subtotal': fields.function(_amount_line_all, multi='pos_order_line_amount', digits_compute=dp.get_precision('Account'), string='Subtotal w/o Tax', store=True), + 'price_subtotal_incl': fields.function(_amount_line_all, multi='pos_order_line_amount', digits_compute=dp.get_precision('Account'), string='Subtotal', store=True), 'discount': fields.float('Discount (%)', digits_compute=dp.get_precision('Account')), 'order_id': fields.many2one('pos.order', 'Order Ref', ondelete='cascade'), 'create_date': fields.datetime('Creation Date', readonly=True), From 95e79c9f6f03f9350c247b3098bffc11bdf06f7b Mon Sep 17 00:00:00 2001 From: Frederic van der Essen Date: Tue, 11 Nov 2014 19:32:52 +0100 Subject: [PATCH 32/37] [FIX] point_of_sale: use 'Product Price' decimal precision for product prices --- addons/point_of_sale/static/src/js/models.js | 14 ++++++++-- .../static/src/js/widget_base.js | 28 ++++++++----------- addons/point_of_sale/static/src/js/widgets.js | 1 - addons/point_of_sale/static/src/xml/pos.xml | 6 ++-- 4 files changed, 26 insertions(+), 23 deletions(-) diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index 44ff38e3587..88d17b5d2bb 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -139,6 +139,15 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal ids: function(self){ return [self.user.company_id[0]] }, loaded: function(self,companies){ self.company = companies[0]; }, },{ + model: 'decimal.precision', + fields: ['name','digits'], + loaded: function(self,dps){ + self.dp = {}; + for (var i = 0; i < dps.length; i++) { + self.dp[dps[i].name] = dps[i].digits; + } + }, + },{ model: 'product.uom', fields: [], domain: null, @@ -795,12 +804,11 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal }, // changes the base price of the product for this orderline set_unit_price: function(price){ - this.price = round_di(parseFloat(price) || 0, this.pos.currency.decimals); + this.price = round_di(parseFloat(price) || 0, this.pos.dp['Product Price']); this.trigger('change',this); }, get_unit_price: function(){ - var rounding = this.pos.currency.rounding; - return round_pr(this.price,rounding); + return this.price; }, get_display_price: function(){ var rounding = this.pos.currency.rounding; diff --git a/addons/point_of_sale/static/src/js/widget_base.js b/addons/point_of_sale/static/src/js/widget_base.js index e58e50160a7..934cc130a6c 100644 --- a/addons/point_of_sale/static/src/js/widget_base.js +++ b/addons/point_of_sale/static/src/js/widget_base.js @@ -18,28 +18,24 @@ function openerp_pos_basewidget(instance, module){ //module is instance.point_of options = options || {}; this.pos = options.pos || (parent ? parent.pos : undefined); this.pos_widget = options.pos_widget || (parent ? parent.pos_widget : undefined); - this.build_currency_template(); }, - build_currency_template: function(){ + format_currency: function(amount,precision){ + var currency = (this.pos && this.pos.currency) ? this.pos.currency : {symbol:'$', position: 'after', rounding: 0.01, decimals: 2}; + var decimals = currency.decimals; - if(this.pos && this.pos.currency){ - this.currency = this.pos.currency; - }else{ - this.currency = {symbol: '$', position: 'after', rounding: 0.01}; + if (precision && (typeof this.pos.dp[precision]) !== undefined) { + decimals = this.pos.dp[precision]; } - - this.format_currency = function(amount){ - if(typeof amount === 'number'){ - amount = round_di(amount,this.currency.decimals).toFixed(this.currency.decimals); - } - if(this.currency.position === 'after'){ - return amount + ' ' + (this.currency.symbol || ''); - }else{ - return (this.currency.symbol || '') + ' ' + amount; - } + if (typeof amount === 'number') { + amount = round_di(amount,decimals).toFixed(decimals); } + if (currency.position === 'after') { + return amount + ' ' + (currency.symbol || ''); + } else { + return (currency.symbol || '') + ' ' + amount; + } }, show: function(){ this.$el.removeClass('oe_hidden'); diff --git a/addons/point_of_sale/static/src/js/widgets.js b/addons/point_of_sale/static/src/js/widgets.js index 41900fffa66..8e02ddd22d6 100644 --- a/addons/point_of_sale/static/src/js/widgets.js +++ b/addons/point_of_sale/static/src/js/widgets.js @@ -972,7 +972,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa $('.oe_web_client').off(); $('.openerp_webclient_container').off(); - self.build_currency_template(); self.renderElement(); self.$('.neworder-button').click(function(){ diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml index ef548830310..c9dd59305ba 100644 --- a/addons/point_of_sale/static/src/xml/pos.xml +++ b/addons/point_of_sale/static/src/xml/pos.xml @@ -746,12 +746,12 @@ - + - + @@ -777,7 +777,7 @@ at - + / From f55a45b0fbdd63a92b14676f736079c1f8401cb0 Mon Sep 17 00:00:00 2001 From: hiekmann Date: Mon, 10 Nov 2014 16:17:07 +0000 Subject: [PATCH 33/37] [FIX] payment_*: typo, cliking -> clicking --- addons/payment_adyen/data/adyen.xml | 2 +- addons/payment_buckaroo/data/buckaroo.xml | 2 +- addons/payment_ogone/data/ogone.xml | 2 +- addons/payment_paypal/data/paypal.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/addons/payment_adyen/data/adyen.xml b/addons/payment_adyen/data/adyen.xml index ec1808f5b7f..7d774d45064 100644 --- a/addons/payment_adyen/data/adyen.xml +++ b/addons/payment_adyen/data/adyen.xml @@ -9,7 +9,7 @@ test You will be redirected to the Adyen website after cliking on the payment button.

]]>
+

You will be redirected to the Adyen website after clicking on the payment button.

]]>
dummy dummy dummy diff --git a/addons/payment_buckaroo/data/buckaroo.xml b/addons/payment_buckaroo/data/buckaroo.xml index 9d100a59191..e0e3ad8b7c5 100644 --- a/addons/payment_buckaroo/data/buckaroo.xml +++ b/addons/payment_buckaroo/data/buckaroo.xml @@ -9,7 +9,7 @@ test You will be redirected to the Buckaroo website after cliking on the payment button.

]]>
+

You will be redirected to the Buckaroo website after clicking on the payment button.

]]>
dummy dummy diff --git a/addons/payment_ogone/data/ogone.xml b/addons/payment_ogone/data/ogone.xml index e24aace7ffd..bb9c9a59eea 100644 --- a/addons/payment_ogone/data/ogone.xml +++ b/addons/payment_ogone/data/ogone.xml @@ -9,7 +9,7 @@ test You will be redirected to the Ogone website after cliking on the payment button.

]]>
+

You will be redirected to the Ogone website after clicking on the payment button.

]]>
dummy dummy dummy diff --git a/addons/payment_paypal/data/paypal.xml b/addons/payment_paypal/data/paypal.xml index d66dee8a3b3..746af24126b 100644 --- a/addons/payment_paypal/data/paypal.xml +++ b/addons/payment_paypal/data/paypal.xml @@ -9,7 +9,7 @@ test You will be redirected to the Paypal website after cliking on the payment button.

]]>
+

You will be redirected to the Paypal website after clicking on the payment button.

]]>
dummy dummy dummy From 4d70b2ba8ba7a354d8ef0326c39aca2cea88a2b0 Mon Sep 17 00:00:00 2001 From: Yajo Date: Fri, 7 Nov 2014 13:54:49 +0100 Subject: [PATCH 34/37] [FIX] crm: Pending calls are also "To Do" --- addons/crm/crm_phonecall_view.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/crm/crm_phonecall_view.xml b/addons/crm/crm_phonecall_view.xml index 619fc7cadd6..69ddb7601c3 100644 --- a/addons/crm/crm_phonecall_view.xml +++ b/addons/crm/crm_phonecall_view.xml @@ -177,7 +177,7 @@ - + From 654ce2bb13f401ab7776b4ca937108e3b00de646 Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Wed, 12 Nov 2014 12:18:26 +0100 Subject: [PATCH 35/37] [FIX] stock: do not try to upper undefined picking name opw-617750 --- addons/stock/static/src/js/widgets.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/stock/static/src/js/widgets.js b/addons/stock/static/src/js/widgets.js index 8f5c78d94fe..18af2d8f974 100644 --- a/addons/stock/static/src/js/widgets.js +++ b/addons/stock/static/src/js/widgets.js @@ -484,7 +484,7 @@ function openerp_picking_widgets(instance){ var picking = pickings[i]; self.pickings_by_type[picking.picking_type_id[0]].push(picking); self.pickings_by_id[picking.id] = picking; - self.picking_search_string += '' + picking.id + ':' + picking.name.toUpperCase() + '\n' + self.picking_search_string += '' + picking.id + ':' + (picking.name ? picking.name.toUpperCase(): '') + '\n'; } }); From 3d88a60c2ea74b36f148b50e9dc63f854c59251f Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Wed, 12 Nov 2014 18:50:38 +0100 Subject: [PATCH 36/37] [FIX] account.invoice report: show customer description of payment term The payment term description (note) is meant to be shown to customers. Restore it in the default invoice report, as it was in 7.0. If empty, assume the payment term is meant to be hidden from customer, and do not show anything. By default the description includes the name of the payment term, so no need to keep both. --- addons/account/views/report_invoice.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/account/views/report_invoice.xml b/addons/account/views/report_invoice.xml index a250eb9ebf7..4fe4e3be0fd 100644 --- a/addons/account/views/report_invoice.xml +++ b/addons/account/views/report_invoice.xml @@ -131,9 +131,9 @@ Comment:

-

+

Payment Term: - +

Fiscal Position: From 83bd9ee16bf24b765e40676d9bfc74aae4f71cb8 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Wed, 12 Nov 2014 19:25:50 +0100 Subject: [PATCH 37/37] [FIX] account: give "Invoice" users access to "Invoice Analysis" Report. This access is required in order to display the amount invoiced on partner form --- addons/account/security/ir.model.access.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/account/security/ir.model.access.csv b/addons/account/security/ir.model.access.csv index 16ad4fee690..820dba6f4b2 100644 --- a/addons/account/security/ir.model.access.csv +++ b/addons/account/security/ir.model.access.csv @@ -68,7 +68,7 @@ access_report_aged_receivable,report.aged.receivable,model_report_aged_receivabl access_report_invoice_created,report.invoice.created,model_report_invoice_created,account.group_account_manager,1,1,1,1 access_report_account_type_sales,report.account_type.sales,model_report_account_type_sales,account.group_account_manager,1,1,1,1 access_report_account_sales,report.account.sales,model_report_account_sales,account.group_account_manager,1,1,1,1 -access_account_invoice_report,account.invoice.report,model_account_invoice_report,account.group_account_manager,1,1,1,1 +access_account_invoice_report,account.invoice.report,model_account_invoice_report,account.group_account_invoice,1,0,0,0 access_res_partner_group_account_manager,res_partner group_account_manager,model_res_partner,account.group_account_manager,1,0,0,0 access_account_invoice_accountant,account.invoice accountant,model_account_invoice,account.group_account_user,1,0,0,0 access_account_tax_code_accountant,account.tax.code accountant,model_account_tax_code,account.group_account_user,1,1,1,1