diff options
| author | Christophe Simonis <chs@odoo.com> | 2017-10-12 18:43:14 +0200 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2017-10-12 18:43:14 +0200 |
| commit | 945dd32eb02f91ae2e5d0ab6545704bd19e7f05a (patch) | |
| tree | 4b1a94fa6a259222996918a0995436e3225580d1 | |
| parent | ae9ed02f38039f17bf10777b28606d778876355d (diff) | |
| parent | 91e2264b284664067d2539d8f1d838809a0d65a2 (diff) | |
[MERGE] forward port branch saas-14 up to 91e2264b284
24 files changed, 154 insertions, 73 deletions
diff --git a/addons/account/models/account_payment.py b/addons/account/models/account_payment.py index bb5cfdd67eb..bda14f6b089 100644 --- a/addons/account/models/account_payment.py +++ b/addons/account/models/account_payment.py @@ -218,6 +218,19 @@ class account_payment(models.Model): # FIXME: ondelete='restrict' not working (eg. cancel a bank statement reconciliation with a payment) move_line_ids = fields.One2many('account.move.line', 'payment_id', readonly=True, copy=False, ondelete='restrict') + def open_payment_matching_screen(self): + # Open reconciliation view for customers/suppliers + action_context = {'company_ids': [self.company_id.id], 'partner_ids': [self.partner_id.commercial_partner_id.id]} + if self.partner_type == 'customer': + action_context.update({'mode': 'customers'}) + elif self.partner_type == 'supplier': + action_context.update({'mode': 'suppliers'}) + return { + 'type': 'ir.actions.client', + 'tag': 'manual_reconciliation_view', + 'context': action_context, + } + @api.one @api.depends('invoice_ids', 'payment_type', 'partner_type', 'partner_id') def _compute_destination_account_id(self): diff --git a/addons/account/models/partner.py b/addons/account/models/partner.py index d59883d99e1..bb90a2b2bfe 100644 --- a/addons/account/models/partner.py +++ b/addons/account/models/partner.py @@ -268,7 +268,7 @@ class ResPartner(models.Model): all_partner_ids = [] for partner in self: # price_total is in the company currency - all_partners_and_children[partner] = self.search([('id', 'child_of', partner.id)]).ids + all_partners_and_children[partner] = self.with_context(active_test=False).search([('id', 'child_of', partner.id)]).ids all_partner_ids += all_partners_and_children[partner] # searching account.invoice.report via the orm is comparatively expensive diff --git a/addons/hr_expense/models/hr_expense.py b/addons/hr_expense/models/hr_expense.py index fbf6b8ae755..ca045d82da6 100644 --- a/addons/hr_expense/models/hr_expense.py +++ b/addons/hr_expense/models/hr_expense.py @@ -284,7 +284,7 @@ class HrExpense(models.Model): account_move.append(move_line) # Calculate tax lines and adjust base line - taxes = expense.tax_ids.compute_all(expense.unit_amount, expense.currency_id, expense.quantity, expense.product_id) + taxes = expense.tax_ids.with_context(round=True).compute_all(expense.unit_amount, expense.currency_id, expense.quantity, expense.product_id) account_move[-1]['price'] = taxes['total_excluded'] account_move[-1]['tax_ids'] = [(6, 0, expense.tax_ids.ids)] for tax in taxes['taxes']: diff --git a/addons/hr_expense/wizard/hr_expense_register_payment.py b/addons/hr_expense/wizard/hr_expense_register_payment.py index b608b234144..6bd7e1d3b44 100644 --- a/addons/hr_expense/wizard/hr_expense_register_payment.py +++ b/addons/hr_expense/wizard/hr_expense_register_payment.py @@ -88,6 +88,10 @@ class HrExpenseRegisterPaymentWizard(models.TransientModel): for line in payment.move_line_ids + expense_sheet.account_move_id.line_ids: if line.account_id.internal_type == 'payable': account_move_lines_to_reconcile |= line - account_move_lines_to_reconcile.reconcile() + # DO NOT FORWARD-PORT! ONLY FOR v10 + if len(expense_sheet.expense_line_ids) > 1: + return payment.open_payment_matching_screen() + else: + account_move_lines_to_reconcile.reconcile() return {'type': 'ir.actions.act_window_close'} diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py index bf7d41ffc72..c25c8a36b30 100644 --- a/addons/mail/models/mail_thread.py +++ b/addons/mail/models/mail_thread.py @@ -174,10 +174,10 @@ class MailThread(models.AbstractModel): RIGHT JOIN mail_channel_partner cp ON (cp.channel_id = rel.mail_channel_id AND cp.partner_id = %s AND (cp.seen_message_id IS NULL OR cp.seen_message_id < msg.id)) - WHERE msg.model = %s AND msg.res_id in %s AND + WHERE msg.model = %s AND msg.res_id = ANY(%s) AND (msg.author_id IS NULL OR msg.author_id != %s) AND (msg.message_type != 'notification' OR msg.model != 'mail.channel')""", - (partner_id, self._name, tuple(self.ids) or (None,), partner_id,)) + (partner_id, self._name, list(self.ids), partner_id,)) for result in self._cr.fetchall(): res[result[0]] += 1 diff --git a/addons/pos_cache/models/pos_cache.py b/addons/pos_cache/models/pos_cache.py index fb0fb20641e..eed948b7981 100644 --- a/addons/pos_cache/models/pos_cache.py +++ b/addons/pos_cache/models/pos_cache.py @@ -23,10 +23,10 @@ class pos_cache(models.Model): @api.one def refresh_cache(self): - products = self.env['product.product'].search(self.get_product_domain()) + Product = self.env['product.product'].sudo(self.compute_user_id.id) + products = Product.search(self.get_product_domain()) prod_ctx = products.with_context(pricelist=self.config_id.pricelist_id.id, display_default_code=False, lang=self.compute_user_id.lang) - prod_ctx = prod_ctx.sudo(self.compute_user_id.id) res = prod_ctx.read(self.get_product_fields()) datas = { 'cache': base64.encodestring(cPickle.dumps(res)), diff --git a/addons/sale/models/account_invoice.py b/addons/sale/models/account_invoice.py index 57e2dc7d70a..1ed8cda461b 100644 --- a/addons/sale/models/account_invoice.py +++ b/addons/sale/models/account_invoice.py @@ -102,6 +102,5 @@ class AccountInvoiceLine(models.Model): 'invoice_line_id', 'order_line_id', string='Sales Order Lines', readonly=True, copy=False) layout_category_id = fields.Many2one('sale.layout_category', string='Section') - layout_category_sequence = fields.Integer( - related='layout_category_id.sequence', - string='Layout Sequence', store=True) + layout_category_sequence = fields.Integer(string='Layout Sequence') + # TODO: remove layout_category_sequence in master or make it work properly diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py index 344bd0a05da..ae96b2323f4 100644 --- a/addons/sale/models/sale.py +++ b/addons/sale/models/sale.py @@ -802,8 +802,8 @@ class SaleOrderLine(models.Model): procurement_ids = fields.One2many('procurement.order', 'sale_line_id', string='Procurements') layout_category_id = fields.Many2one('sale.layout_category', string='Section') - layout_category_sequence = fields.Integer(related='layout_category_id.sequence', string='Layout Sequence', store=True) - # Store is intentionally set in order to keep the "historic" order. + layout_category_sequence = fields.Integer(string='Layout Sequence') + # TODO: remove layout_category_sequence in master or make it work properly @api.multi def _prepare_invoice_line(self, qty): diff --git a/addons/stock/models/stock_quant.py b/addons/stock/models/stock_quant.py index eac6318e0d7..5932f9558af 100644 --- a/addons/stock/models/stock_quant.py +++ b/addons/stock/models/stock_quant.py @@ -682,7 +682,8 @@ class QuantPackage(models.Model): def action_view_picking(self): action = self.env.ref('stock.action_picking_tree_all').read()[0] - pickings = self.env['stock.pack.operation'].search([('result_package_id', 'in', self.ids)]).mapped('picking_id') + domain = ['|', ('result_package_id', 'in', self.ids), ('package_id', 'in', self.ids)] + pickings = self.env['stock.pack.operation'].search(domain).mapped('picking_id') action['domain'] = [('id', 'in', pickings.ids)] return action diff --git a/addons/web/i18n/es_EC.po b/addons/web/i18n/es_EC.po index f9e2bfa8c05..6017a7f5f2f 100644 --- a/addons/web/i18n/es_EC.po +++ b/addons/web/i18n/es_EC.po @@ -2221,7 +2221,7 @@ msgstr "" #: code:addons/web/static/src/js/views/form_widgets.js:1199 #, python-format msgid "The selected file exceed the maximum file size of %s." -msgstr "El archivo seleccionado supera el tamaño máximo de% s." +msgstr "El archivo seleccionado supera el tamaño máximo de %s." #. module: web #. openerp-web diff --git a/addons/web/i18n/hi.po b/addons/web/i18n/hi.po index 8bd0cafa5f3..b4439effdaa 100644 --- a/addons/web/i18n/hi.po +++ b/addons/web/i18n/hi.po @@ -1240,7 +1240,7 @@ msgstr "" #: code:addons/web/static/src/js/widgets/loading.js:44 #, python-format msgid "Loading (%d)" -msgstr "लोड हो रहा है (% d)" +msgstr "लोड हो रहा है (%d)" #. module: web #. openerp-web diff --git a/addons/website_hr_recruitment/controllers/main.py b/addons/website_hr_recruitment/controllers/main.py index 71968b3d850..a74fd9d4631 100644 --- a/addons/website_hr_recruitment/controllers/main.py +++ b/addons/website_hr_recruitment/controllers/main.py @@ -6,16 +6,17 @@ from odoo.addons.website.models.website import slug from odoo.http import request +# NB: DO NOT FORWARD PORT THE FALSY LEAVES IN 11.0 class WebsiteHrRecruitment(http.Controller): @http.route([ '/jobs', - '/jobs/country/<model("res.country"):country>', - '/jobs/department/<model("hr.department"):department>', - '/jobs/country/<model("res.country"):country>/department/<model("hr.department"):department>', + '''/jobs/country/<model("res.country", "[(0, '=', 1)]"):country>''', + '''/jobs/department/<model("hr.department", "[(0, '=', 1)]"):department>''', + '''/jobs/country/<model("res.country"):country>/department/<model("hr.department", "[(0, '=', 1)]"):department>''', '/jobs/office/<int:office_id>', - '/jobs/country/<model("res.country"):country>/office/<int:office_id>', - '/jobs/department/<model("hr.department"):department>/office/<int:office_id>', - '/jobs/country/<model("res.country"):country>/department/<model("hr.department"):department>/office/<int:office_id>', + '''/jobs/country/<model("res.country", "[(0, '=', 1)]"):country>/office/<int:office_id>''', + '''/jobs/department/<model("hr.department", "[(0, '=', 1)]"):department>/office/<int:office_id>''', + '''/jobs/country/<model("res.country"):country>/department/<model("hr.department", "[(0, '=', 1)]"):department>/office/<int:office_id>''', ], type='http', auth="public", website=True) def jobs(self, country=None, department=None, office_id=None, **kwargs): env = request.env(context=dict(request.env.context, show_address=True, no_tag_br=True)) diff --git a/addons/website_portal/static/src/less/website_portal.less b/addons/website_portal/static/src/less/website_portal.less index 3db190771f3..f89774ab957 100644 --- a/addons/website_portal/static/src/less/website_portal.less +++ b/addons/website_portal/static/src/less/website_portal.less @@ -7,6 +7,14 @@ tr th { text-align: center; } + + @media (max-width: @screen-xs-max) { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + -ms-overflow-style: -ms-autohiding-scrollbar; + } } .o_my_home_content > .page-header > a:hover { diff --git a/addons/website_quote/models/sale_order.py b/addons/website_quote/models/sale_order.py index d5f3f2483ab..b3e17b42827 100644 --- a/addons/website_quote/models/sale_order.py +++ b/addons/website_quote/models/sale_order.py @@ -111,15 +111,20 @@ class SaleOrder(models.Model): order_lines = [(5, 0, 0)] for line in template.quote_line: + discount = 0 if self.pricelist_id: price = self.pricelist_id.with_context(uom=line.product_uom_id.id).get_product_price(line.product_id, 1, False) + if self.pricelist_id.discount_policy == 'without_discount' and line.price_unit: + discount = (line.price_unit - price) / line.price_unit * 100 + price = line.price_unit + else: price = line.price_unit data = { 'name': line.name, 'price_unit': price, - 'discount': line.discount, + 'discount': 100 - ((100 - discount) * (100 - line.discount)/100), 'product_uom_qty': line.product_uom_qty, 'product_id': line.product_id.id, 'layout_category_id': line.layout_category_id, diff --git a/addons/website_sale/models/product.py b/addons/website_sale/models/product.py index 1b017ff67d1..6c50e86c2b1 100644 --- a/addons/website_sale/models/product.py +++ b/addons/website_sale/models/product.py @@ -192,7 +192,9 @@ class Product(models.Model): def _website_price(self): qty = self._context.get('quantity', 1.0) partner = self.env.user.partner_id - pricelist = self.env['website'].get_current_website().get_current_pricelist() + current_website = self.env['website'].get_current_website() + pricelist = current_website.get_current_pricelist() + company_id = current_website.company_id context = dict(self._context, pricelist=pricelist.id, partner=partner) self2 = self.with_context(context) if self._context != context else self @@ -200,7 +202,7 @@ class Product(models.Model): ret = self.env.user.has_group('sale.group_show_price_subtotal') and 'total_excluded' or 'total_included' for p, p2 in zip(self, self2): - taxes = partner.property_account_position_id.map_tax(p.taxes_id) + taxes = partner.property_account_position_id.map_tax(p.taxes_id.sudo().filtered(lambda x: x.company_id == company_id)) p.website_price = taxes.compute_all(p2.price, pricelist.currency_id, quantity=qty, product=p2, partner=partner)[ret] p.website_public_price = taxes.compute_all(p2.lst_price, quantity=qty, product=p2, partner=partner)[ret] diff --git a/addons/website_sale/static/src/css/website_sale.css b/addons/website_sale/static/src/css/website_sale.css index e2604b1b796..8f71e5bbfbd 100644 --- a/addons/website_sale/static/src/css/website_sale.css +++ b/addons/website_sale/static/src/css/website_sale.css @@ -336,6 +336,10 @@ min-width: 76px; } +.oe_cart > .oe_structure { + clear: both; +} + div#payment_method div.list-group { margin-left: 40px; } diff --git a/addons/website_sale/static/src/css/website_sale.sass b/addons/website_sale/static/src/css/website_sale.sass index e723493b5c6..3c1abfeb323 100644 --- a/addons/website_sale/static/src/css/website_sale.sass +++ b/addons/website_sale/static/src/css/website_sale.sass @@ -296,6 +296,8 @@ .oe_cart table td:first-child min-width: 76px +.oe_cart > .oe_structure + clear: both div#payment_method div.list-group diff --git a/addons/website_sale/views/templates.xml b/addons/website_sale/views/templates.xml index e1157fd9de4..42fc4b30623 100644 --- a/addons/website_sale/views/templates.xml +++ b/addons/website_sale/views/templates.xml @@ -84,7 +84,7 @@ </div> <div class="oe_product_image"> <a itemprop="url" t-att-href="keep('/shop/product/%s' % slug(product), page=(pager['page']['num'] if pager['page']['num']>1 else None))"> - <span itemprop="image" t-field="product.image" t-options="{'widget': 'image', 'resize': None if product_image_big else '300x300', 'zoom': 'image'}" t-att-alt="product.name" /> + <span itemprop="image" t-attf-content="{{request.httprequest.url_root}}web/image/product.template/{{product.id}}/image" t-field="product.image" t-options="{'widget': 'image', 'resize': None if product_image_big else '300x300', 'zoom': 'image'}" t-att-alt="product.name" /> </a> </div> <t t-if="show_publish"> @@ -99,7 +99,7 @@ <section> <h5> <strong> - <a itemprop="name" t-att-href="keep('/shop/product/%s' % slug(product), page=(pager['page']['num'] if pager['page']['num']>1 else None))" t-field="product.name" /> + <a itemprop="name" t-att-href="keep('/shop/product/%s' % slug(product), page=(pager['page']['num'] if pager['page']['num']>1 else None))" t-att-content="product.name" t-field="product.name" /> </strong> </h5> <div itemprop="offers" itemscope="itemscope" itemtype="http://schema.org/Offer" class="product_price" t-if="product.product_variant_ids"> @@ -406,7 +406,7 @@ </div> <div class="row"> <div class="col-sm-7"> - <span itemprop="image" t-field="product.image" t-options="{'widget': 'image', 'class': 'product_detail_img', 'alt-field': 'name', 'zoom': 'image'}" groups="!website_sale.group_website_multi_image"/> + <span itemprop="image" t-attf-content="{{request.httprequest.url_root}}web/image/product.template/{{product.id}}/image" t-field="product.image" t-options="{'widget': 'image', 'class': 'product_detail_img', 'alt-field': 'name', 'zoom': 'image'}" groups="!website_sale.group_website_multi_image"/> <t groups="website_sale.group_website_multi_image"> <t t-set="variant_img" t-value="product.product_variant_id.image_variant"/> <t t-set="image_ids" t-value="product.product_image_ids"/> @@ -450,7 +450,7 @@ </div> <div class="col-sm-5 col-lg-4 col-lg-offset-1" id="product_details"> <h1 itemprop="name" t-field="product.name">Product Name</h1> - <span itemprop="url" style="display:none;" t-esc="'/shop/product/%s' % slug(product)" /> + <span itemprop="url" style="display:none;" t-esc="'%sshop/product/%s' % (request.httprequest.url_root, slug(product))"/> <t t-set="attribute_value_ids" t-value="get_attribute_value_ids(product)"/> <form action="/shop/cart/update" class="js_add_cart_variants" t-att-data-attribute_value_ids="attribute_value_ids" method="POST"> <input type="hidden" name="csrf_token" t-att-value="request.csrf_token()" /> diff --git a/doc/cla/corporate/jarsa.md b/doc/cla/corporate/jarsa.md index 105359f0bb6..b82292ff2d1 100644 --- a/doc/cla/corporate/jarsa.md +++ b/doc/cla/corporate/jarsa.md @@ -13,3 +13,8 @@ List of contributors: Alan Ramos alan.ramos@jarsa.com.mx https://github.com/alan196 Luis Triana luis.triana@jarsa.com.mx https://github.com/xluiisx Luis Triana luistriana.28@gmail.com https://github.com/xluiisx +Oscar Garza oscar.garza@jarsa.com.mx https://github.com/ougc27 +Alexis Reza alexis.reza@jarsa.com.mx https://github.com/AlexisReza +Isabel Esparza isabel.esparza@jarsa.com.mx https://github.com/IsabelEsparza +Miguel Ruiz miguel.ruiz@jarsa.com.mx https://github.com/lmgr0312 +Sarai Osorio sarai.osorio@jarsa.com.mx https://github.com/SaraiOsorio diff --git a/doc/cla/individual/carlosdauden.md b/doc/cla/individual/carlosdauden.md new file mode 100644 index 00000000000..a672fcaf988 --- /dev/null +++ b/doc/cla/individual/carlosdauden.md @@ -0,0 +1,9 @@ +Spain, 2015-05-28 + +I hereby agree to the terms of the Odoo Individual Contributor License Agreement v1.0. + +I declare that I am authorized and able to make this agreement and sign this declaration. + +Signed, + +Carlos Dauden carlos.dauden@tecnativa.com https://github.com/carlosdauden diff --git a/odoo/addons/base/ir/ir_qweb/qweb.py b/odoo/addons/base/ir/ir_qweb/qweb.py index 89281963de7..725fb1b0e40 100644 --- a/odoo/addons/base/ir/ir_qweb/qweb.py +++ b/odoo/addons/base/ir/ir_qweb/qweb.py @@ -1107,7 +1107,26 @@ class QWeb(object): def _compile_directive_esc(self, el, options): field_options = self._compile_widget_options(el, 'esc') - content = self._compile_widget(el, el.attrib.pop('t-esc'), field_options, 'escape') + content = self._compile_widget(el, el.attrib.pop('t-esc'), field_options) + if not field_options: + # if content is not False and if content is not None: + # content = escape(unicodifier(content)) + content.append(self._if_content_is_not_Falsy([ + ast.Assign( + targets=[ast.Name(id='content', ctx=ast.Store())], + value=ast.Call( + func=ast.Name(id='escape', ctx=ast.Load()), + args=[ast.Call( + func=ast.Name(id='unicodifier', ctx=ast.Load()), + args=[ast.Name(id='content', ctx=ast.Load())], keywords=[], + starargs=None, kwargs=None + )], + keywords=[], + starargs=None, kwargs=None + ) + )], + [] + )) return content + self._compile_widget_value(el, options) def _compile_directive_raw(self, el, options): @@ -1115,33 +1134,8 @@ class QWeb(object): content = self._compile_widget(el, el.attrib.pop('t-raw'), field_options) return content + self._compile_widget_value(el, options) + # escape attribute is deprecated and will remove after v11 def _compile_widget(self, el, expression, field_options, escape=None): - # if isinstance(value, basestring): - # value = escape(unicodifier(value)) - escaped = ast.If( - test=ast.Call( - func=ast.Name(id='isinstance', ctx=ast.Load()), - args=[ - ast.Name(id='content', ctx=ast.Load()), - ast.Name(id='basestring', ctx=ast.Load()) - ], keywords=[], - starargs=None, kwargs=None - ), - body=[ast.Assign( - targets=[ast.Name(id='content', ctx=ast.Store())], - value=ast.Call( - func=ast.Name(id=escape, ctx=ast.Load()), - args=[ast.Call( - func=ast.Name(id='unicodifier', ctx=ast.Load()), - args=[ast.Name(id='content', ctx=ast.Load())], keywords=[], - starargs=None, kwargs=None - )], keywords=[], - starargs=None, kwargs=None - ) if escape else ast.Name(id='content', ctx=ast.Load()) - )], - orelse=[] - ) - if field_options: return [ # value = t-(esc|raw) @@ -1149,7 +1143,6 @@ class QWeb(object): targets=[ast.Name(id='content', ctx=ast.Store())], value=self._compile_expr0(expression) ), - escaped, # t_attrs, content, force_display = self._get_widget(value, expression, tagName, field options, template options, values) ast.Assign( targets=[ast.Tuple(elts=[ @@ -1193,8 +1186,7 @@ class QWeb(object): self._compile_expr0(expression), ast.Name(id='None', ctx=ast.Load()), ], ctx=ast.Load()) - ), - escaped + ) ] # for backward compatibility to remove after v10 diff --git a/odoo/addons/base/res/res.lang.csv b/odoo/addons/base/res/res.lang.csv index 44b1406b753..e04af8271b7 100644 --- a/odoo/addons/base/res/res.lang.csv +++ b/odoo/addons/base/res/res.lang.csv @@ -21,7 +21,7 @@ "base.lang_fr_BE","French (BE) / Français (BE)","fr_BE","fr_BE","Left-to-Right","[3,0]",",",".","%d/%m/%Y","%H:%M:%S","True" "base.lang_fr_CA","French (CA) / Français (CA)","fr_CA","fr_CA","Left-to-Right","[3,0]",","," ","%Y-%m-%d","%H:%M:%S","True" "base.lang_fr_CH","French (CH) / Français (CH)","fr_CH","fr_CH","Left-to-Right","[3,0]",".","'","%d. %m. %Y","%H:%M:%S","True" -"base.lang_fr","French / Français","fr_FR","fr","Left-to-Right","[3,0]",","," ","%d/%m/%Y","%H:%M:%S","True" +"base.lang_fr","French / Français","fr_FR","fr","Left-to-Right","[3,0]",","," ","%d/%m/%Y","%H:%M:%S","True" "base.lang_gl_ES","Galician / Galego","gl_ES","gl_ES","Left-to-Right","[]",",",,"%d/%m/%Y","%H:%M:%S","True" "base.lang_ka_GE","Georgian / ქართული ენა","ka_GE","ka_GE","Left-to-Right","[3,0]",",",".","%m/%d/%Y","%H:%M:%S","True" "base.lang_de","German / Deutsch","de_DE","de","Left-to-Right","[3,0]",",",".","%d.%m.%Y","%H:%M:%S","True" diff --git a/odoo/fields.py b/odoo/fields.py index 39d4bfde956..608adcfd045 100644 --- a/odoo/fields.py +++ b/odoo/fields.py @@ -60,6 +60,8 @@ def copy_cache(records, env): while todo: model_name = next(iter(todo)) record_ids = todo.pop(model_name) - done[model_name] + if not record_ids: + continue done[model_name].update(record_ids) for name, field in src[model_name]._fields.items(): src_cache = src.cache[field] @@ -71,6 +73,10 @@ def copy_cache(records, env): if field.relational and isinstance(value, tuple): todo[field.comodel_name].update(value) +def first(records): + """ Return the first record in ``records``, with the same prefetching. """ + return next(iter(records)) if len(records) > 1 else records + def resolve_mro(model, name, predicate): """ Return the list of successively overridden values of attribute ``name`` @@ -567,12 +573,41 @@ class Field(object): """ Compute the related field ``self`` on ``records``. """ # when related_sudo, bypass access rights checks when reading values others = records.sudo() if self.related_sudo else records - for record, other in zip(records, others): - if not record.id and record.env != other.env: - # draft records: copy record's cache to other's cache first - copy_cache(record, other.env) - other, field = self.traverse_related(other) - record[self.name] = other[field.name] + # copy the cache of draft records into others' cache + if records.env != others.env: + copy_cache(records - records.filtered('id'), others.env) + # + # Traverse fields one by one for all records, in order to take advantage + # of prefetching for each field access. In order to clarify the impact + # of the algorithm, consider traversing 'foo.bar' for records a1 and a2, + # where 'foo' is already present in cache for a1, a2. Initially, both a1 + # and a2 are marked for prefetching. As the commented code below shows, + # traversing all fields one record at a time will fetch 'bar' one record + # at a time. + # + # b1 = a1.foo # mark b1 for prefetching + # v1 = b1.bar # fetch/compute bar for b1 + # b2 = a2.foo # mark b2 for prefetching + # v2 = b2.bar # fetch/compute bar for b2 + # + # On the other hand, traversing all records one field at a time ensures + # maximal prefetching for each field access. + # + # b1 = a1.foo # mark b1 for prefetching + # b2 = a2.foo # mark b2 for prefetching + # v1 = b1.bar # fetch/compute bar for b1, b2 + # v2 = b2.bar # value already in cache + # + # This difference has a major impact on performance, in particular in + # the case where 'bar' is a computed field that takes advantage of batch + # computation. + # + values = list(others) + for name in self.related[:-1]: + values = [first(value[name]) for value in values] + # assign final values to records + for record, value in zip(records, values): + record[self.name] = value[self.related_field.name] def _inverse_related(self, records): """ Inverse the related field ``self`` on ``records``. """ diff --git a/odoo/models.py b/odoo/models.py index e6862d70e15..352f0709d05 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -3376,14 +3376,6 @@ class BaseModel(object): id_new, = cr.fetchone() self = self.browse(id_new) - if self.env.lang and self.env.lang != 'en_US': - # add translations for self.env.lang - for name, val in vals.iteritems(): - field = self._fields[name] - if field.store and field.column_type and field.translate is True: - tname = "%s,%s" % (self._name, name) - self.env['ir.translation']._set_ids(tname, 'model', self.env.lang, self.ids, val, val) - if self._parent_store and not self._context.get('defer_parent_store_computation'): if self.pool._init: self.pool._init_parent[self._name] = True @@ -3445,6 +3437,15 @@ class BaseModel(object): self.recompute() self.check_access_rule('create') + + if self.env.lang and self.env.lang != 'en_US': + # add translations for self.env.lang + for name, val in vals.iteritems(): + field = self._fields[name] + if field.store and field.column_type and field.translate is True: + tname = "%s,%s" % (self._name, name) + self.env['ir.translation']._set_ids(tname, 'model', self.env.lang, self.ids, val, val) + return id_new # TODO: ameliorer avec NULL |
