diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py index cdf686b42b02df70752ae3f5f07769c522328483..715941ce02fb9d5041cddd913cc19a980a54c4e2 100644 --- a/addons/account/models/account_bank_statement.py +++ b/addons/account/models/account_bank_statement.py @@ -831,6 +831,9 @@ class AccountBankStatementLine(models.Model): } st_line.process_reconciliation(new_aml_dicts=[vals]) + def _get_communication(self, payment_method_id): + return self.name or '' + def process_reconciliation(self, counterpart_aml_dicts=None, payment_aml_rec=None, new_aml_dicts=None): """ Match statement lines with existing payments (eg. checks) and/or payables/receivables (eg. invoices and credit notes) and/or new move lines (eg. write-offs). If any new journal item needs to be created (via new_aml_dicts or counterpart_aml_dicts), a new journal entry will be created and will contain those @@ -928,7 +931,7 @@ class AccountBankStatementLine(models.Model): 'state': 'reconciled', 'currency_id': currency.id, 'amount': abs(total), - 'communication': self.name or '', + 'communication': self._get_communication(payment_methods[0] if payment_methods else False), 'name': self.statement_id.name, }) diff --git a/addons/account/models/partner.py b/addons/account/models/partner.py index 25e6449c7af27a8b9b6afada025644c476b9db16..db8c4918b81fcce79e9829ff887207fc750a4239 100644 --- a/addons/account/models/partner.py +++ b/addons/account/models/partner.py @@ -278,7 +278,7 @@ class ResPartner(models.Model): # generate where clause to include multicompany rules where_query = account_invoice_report._where_calc([ - ('partner_id', 'in', all_partner_ids), ('state', 'not in', ['draft', 'cancel']), ('company_id', '=', self.env.user.company_id.id), + ('partner_id', 'in', all_partner_ids), ('state', 'not in', ['draft', 'cancel']), ('type', 'in', ('out_invoice', 'out_refund')) ]) account_invoice_report._apply_ir_rules(where_query, 'read') diff --git a/addons/account/report/account_invoice_report.py b/addons/account/report/account_invoice_report.py index 4753ce92a8636148750eb01d4c72c5e2c4a6b6f2..62c2fe786a1fb0e752b17ed51c95e0d88e330700 100644 --- a/addons/account/report/account_invoice_report.py +++ b/addons/account/report/account_invoice_report.py @@ -135,7 +135,7 @@ class AccountInvoiceReport(models.Model): JOIN ( -- Temporary table to decide if the qty should be added or retrieved (Invoice vs Credit Note) SELECT id,(CASE - WHEN ai.type::text = ANY (ARRAY['out_refund'::character varying::text, 'in_invoice'::character varying::text]) + WHEN ai.type::text = ANY (ARRAY['in_refund'::character varying::text, 'in_invoice'::character varying::text]) THEN -1 ELSE 1 END) AS sign diff --git a/addons/base_address_city/models/res_partner.py b/addons/base_address_city/models/res_partner.py index e3e3ad574e0d2c43973b21c5d9c4a0768749fe60..e92867fabb488c363bb9746c808ca289d9a463ee 100644 --- a/addons/base_address_city/models/res_partner.py +++ b/addons/base_address_city/models/res_partner.py @@ -20,10 +20,10 @@ class Partner(models.Model): @api.model def _fields_view_get_address(self, arch): arch = super(Partner, self)._fields_view_get_address(arch) - if not self._context.get('no_address_format'): - return arch # render the partner address accordingly to address_view_id doc = etree.fromstring(arch) + if doc.xpath("//field[@name='city_id']"): + return arch for city_node in doc.xpath("//field[@name='city']"): replacement_xml = """ <div> diff --git a/addons/base_automation/models/base_automation.py b/addons/base_automation/models/base_automation.py index 268ae7e76f08dfce63bd07a75334be3fd7a8da90..bef0b88914dbe912d7d196734fa3ce5f09b61d09 100644 --- a/addons/base_automation/models/base_automation.py +++ b/addons/base_automation/models/base_automation.py @@ -307,7 +307,7 @@ class BaseAutomation(models.Model): if action.trg_date_calendar_id and action.trg_date_range_type == 'day': return action.trg_date_calendar_id.plan_days( action.trg_date_range, - day_date=fields.Datetime.from_string(record_dt), + fields.Datetime.from_string(record_dt), compute_leaves=True, ) else: diff --git a/addons/calendar/tests/test_calendar.py b/addons/calendar/tests/test_calendar.py index e10976af980c7ed974f248fbf4d7be49ba6e1f01..12547c8f695ac6af0ae8e06e2f550c160468a4ce 100644 --- a/addons/calendar/tests/test_calendar.py +++ b/addons/calendar/tests/test_calendar.py @@ -25,6 +25,20 @@ class TestCalendar(TransactionCase): 'name': 'Technical Presentation' }) + def test_calender_simple_event(self): + m = self.CalendarEvent.create({ + 'name': "Test compute", + 'start': '2017-07-12 14:30:00', + 'allday': False, + 'stop': '2017-07-12 15:00:00', + }) + + self.assertEqual( + (m.start_datetime, m.stop_datetime), + (u'2017-07-12 14:30:00', u'2017-07-12 15:00:00'), + "Sanity check" + ) + def test_calender_event(self): # Now I will set recurrence for this event to occur monday and friday of week data = { diff --git a/addons/event_sale/models/event.py b/addons/event_sale/models/event.py index 9af02529ef8d3ecdeec5c83712c56ea53a80b0b3..c503ea7d44b46d3a3993448d1e28a704061d4972 100644 --- a/addons/event_sale/models/event.py +++ b/addons/event_sale/models/event.py @@ -5,6 +5,7 @@ from odoo import api, fields, models, _ from odoo.exceptions import ValidationError, UserError from odoo.addons import decimal_precision as dp +from odoo.tools import float_is_zero class EventType(models.Model): @@ -222,7 +223,8 @@ class EventRegistration(models.Model): information.append((_('Name'), self.name)) information.append((_('Ticket'), self.event_ticket_id.name or _('None'))) order = self.sale_order_id.sudo() - if not order: + order_line = self.sale_order_line_id.sudo() + if not order or float_is_zero(order_line.price_total, precision_digits=order.currency_id.rounding): payment_status = _('Free') elif not order.invoice_ids or any(invoice.state != 'paid' for invoice in order.invoice_ids): payment_status = _('To pay') diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py index d383c8096ea985f8d7c6b60d2a8bf6ca4f80fc9e..fd7362699684d524c711e2714540bcfa7b5d661f 100644 --- a/addons/mail/models/mail_thread.py +++ b/addons/mail/models/mail_thread.py @@ -177,10 +177,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/project/views/project_views.xml b/addons/project/views/project_views.xml index 2b7f311a4f52bbbd366eb8e6bf61e86d7d81e9f6..d288ef43862c45b23bc33173e23cbdddd084e78d 100644 --- a/addons/project/views/project_views.xml +++ b/addons/project/views/project_views.xml @@ -132,7 +132,7 @@ </div> </div> <notebook> - <page string="Settings"> + <page name="settings" string="Settings"> <group> <field name="user_id" string="Project Manager" attrs="{'readonly':[('active','=',False)]}"/> @@ -149,7 +149,7 @@ </group> </group> </page> - <page string="Emails" attrs="{'invisible': [('alias_domain', '=', False)]}"> + <page name="emails" string="Emails" attrs="{'invisible': [('alias_domain', '=', False)]}"> <group name="group_alias"> <label for="alias_name" string="Email Alias"/> <div name="alias_def"> @@ -443,7 +443,7 @@ <field name="description" type="html"/> <div class="oe_clear"/> </page> - <page string="Extra Info"> + <page name="extra_info" string="Extra Info"> <group> <group> <field name="sequence" groups="base.group_no_one"/> diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py index 2481a79c1d84aaf32bfd0348c1bad26f8edbf322..4c8bee5cd81eca195cb144d84ff808026b37c8a8 100644 --- a/addons/sale/models/sale.py +++ b/addons/sale/models/sale.py @@ -451,13 +451,13 @@ class SaleOrder(models.Model): @api.multi def action_draft(self): orders = self.filtered(lambda s: s.state in ['cancel', 'sent']) - orders.write({ + return orders.write({ 'state': 'draft', }) @api.multi def action_cancel(self): - self.write({'state': 'cancel'}) + return self.write({'state': 'cancel'}) @api.multi def action_quotation_send(self): @@ -508,7 +508,7 @@ class SaleOrder(models.Model): @api.multi def action_done(self): - self.write({'state': 'done'}) + return self.write({'state': 'done'}) @api.multi def action_unlock(self): diff --git a/addons/web/static/src/js/chrome/search_menus.js b/addons/web/static/src/js/chrome/search_menus.js index eee7303ffbd4eff72a301f714fafbae5629c3c07..fe2ce5f3c32135bf22eedc3c5ff17b55fc7e6cd9 100644 --- a/addons/web/static/src/js/chrome/search_menus.js +++ b/addons/web/static/src/js/chrome/search_menus.js @@ -415,7 +415,7 @@ return Widget.extend({ this.groupableFields = []; var groupable_types = ['many2one', 'char', 'boolean', 'selection', 'date', 'datetime']; _.each(fields, function (field, name) { - if (field.store && _.contains(groupable_types, field.type)) { + if (field.sortable && _.contains(groupable_types, field.type)) { self.groupableFields.push(_.extend({}, field, {name: name})); } }); diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js index dbbafe4432cc1b8b97d510968cb6306cb5f37f5d..ed5f03b4f9b1c3ccd40eafe44218c3b954ab4c27 100644 --- a/addons/web/static/src/js/fields/relational_fields.js +++ b/addons/web/static/src/js/fields/relational_fields.js @@ -372,9 +372,14 @@ var FieldMany2One = AbstractField.extend({ } // create and edit ... if (create_enabled && !self.nodeOptions.no_create_edit) { + var createAndEditAction = function () { + // Clear the value in case the user clicks on discard + self.$('input').val(''); + return self._searchCreatePopup("form", false, self._createContext(search_val)); + }; values.push({ label: _t("Create and Edit..."), - action: self._searchCreatePopup.bind(self, "form", false, self._createContext(search_val)), + action: createAndEditAction, classname: 'o_m2o_dropdown_option', }); } else if (values.length === 0) { diff --git a/addons/web/static/src/js/widgets/domain_selector.js b/addons/web/static/src/js/widgets/domain_selector.js index 82cca87f4df15a8860c322fb15456f281d9330e2..782128994db236fd3b8252c71933c282687c63e6 100644 --- a/addons/web/static/src/js/widgets/domain_selector.js +++ b/addons/web/static/src/js/widgets/domain_selector.js @@ -805,7 +805,7 @@ var DomainLeaf = DomainNode.extend({ if (_.isBoolean(this.value)) { this.value = ""; } else if (_.isObject(this.value) && !_.isArray(this.value)) { // Can be object if parsed to x2x representation - this.value = this.value.id || ""; + this.value = this.value.id || value || ""; } } diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js index 0c910e5b4def4b81645cc5fa073711eb8a75581b..7a0937084c3c3c2c4ccfcbb9c729eee842db1331 100644 --- a/addons/web/static/tests/fields/relational_fields_tests.js +++ b/addons/web/static/tests/fields/relational_fields_tests.js @@ -1491,7 +1491,7 @@ QUnit.module('relational_fields', { QUnit.test('pressing ENTER on a \'no_quick_create\' many2one should not trigger M2ODialog', function (assert) { var done = assert.async(); - assert.expect(1); + assert.expect(2); var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; @@ -1529,6 +1529,10 @@ QUnit.module('relational_fields', { $input.blur(); assert.strictEqual($('.modal').length, 1, "should have one modal in body"); + // Check that discarding clears $input + $('.modal .o_form_button_cancel').click(); + assert.strictEqual($input.val(), '', + "the field should be empty"); form.destroy(); relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; done(); diff --git a/addons/web/static/tests/widgets/domain_selector_tests.js b/addons/web/static/tests/widgets/domain_selector_tests.js index 77d8eeb24da0fc64ab625e14d69e3a725ec87a8e..55ddc971642f167deb291bf36daad1d30d361a9c 100644 --- a/addons/web/static/tests/widgets/domain_selector_tests.js +++ b/addons/web/static/tests/widgets/domain_selector_tests.js @@ -174,6 +174,26 @@ QUnit.module('DomainSelector', { domainSelector.destroy(); }); + + QUnit.test("building a domain with a m2o without following the relation", function (assert) { + assert.expect(1); + + var $target = $("#qunit-fixture"); + + // Create the domain selector and its mock environment + var domainSelector = new DomainSelector(null, "partner", [["product_id", "ilike", 1]], { + debugMode: true, + readonly: false, + }); + testUtils.addMockEnvironment(domainSelector, {data: this.data}); + domainSelector.appendTo($target); + + domainSelector.$('.o_domain_leaf_value_input').val('pad').trigger('input').trigger('change'); + assert.strictEqual(domainSelector.$('.o_domain_debug_input').val(), '[["product_id","ilike","pad"]]', + "string should have been allowed as m2o value"); + + domainSelector.destroy(); + }); }); }); }); diff --git a/addons/website_mail_channel/controllers/main.py b/addons/website_mail_channel/controllers/main.py index f3e5b51847ea094718faf508f27910161de6a4fd..678defa34ef28e8a4750a897b628ab80d6544cb5 100644 --- a/addons/website_mail_channel/controllers/main.py +++ b/addons/website_mail_channel/controllers/main.py @@ -6,7 +6,7 @@ import werkzeug from datetime import datetime from dateutil import relativedelta -from odoo import http, fields, tools +from odoo import http, fields, tools, _ from odoo.http import request from odoo.addons.http_routing.models.ir_http import slug @@ -228,8 +228,16 @@ class MailGroup(http.Controller): def confirm_unsubscribe(self, channel, partner_id, token, **kw): subscriber = request.env['mail.channel.partner'].search([('channel_id', '=', channel.id), ('partner_id', '=', partner_id)]) if not subscriber: - # not registered, maybe already unsubsribed - return request.render('website_mail_channel.invalid_token_subscription') + # FIXME: remove try/except in master + try: + return request.render( + 'website_mail_channel.not_subscribed', { + 'partner_id': partner_id + }) + except ValueError: + return _("The address %s is already unsubscribed or was never subscribed to any mailing list") % ( + partner_id.email + ) subscriber_token = channel._generate_action_token(partner_id, action='unsubscribe') if token != subscriber_token: diff --git a/addons/website_mail_channel/views/website_mail_channel_templates.xml b/addons/website_mail_channel/views/website_mail_channel_templates.xml index 5ed8a25e9d685c42c7a9e4402815e5ebfbc73f6d..e225d107b3c8c7d963b5c160418ae8405acb5002 100644 --- a/addons/website_mail_channel/views/website_mail_channel_templates.xml +++ b/addons/website_mail_channel/views/website_mail_channel_templates.xml @@ -338,4 +338,19 @@ </t> </template> +<template id="not_subscribed" name="Email address was not subscribed"> + <t t-call="website.layout"> + <div id="wrap" class="oe_structure oe_empty"> + <div class="container"> + <p> + The address <t t-esc="partner_id.email"/> is already + unsubscribed or was never subscribed to the mailing + list, you may want to check that the address was + correct. + </p> + </div> + </div> + </t> +</template> + </odoo> diff --git a/doc/cla/corporate/equitania.md b/doc/cla/corporate/equitania.md new file mode 100644 index 0000000000000000000000000000000000000000..8dedd6fd05ccb7effb3a70bc457e40dbec68cf47 --- /dev/null +++ b/doc/cla/corporate/equitania.md @@ -0,0 +1,21 @@ +Germany, 2017-09-11 + +Equitania Software GmbH agrees to the terms of the +Odoo Corporate Contributor License Agreement v1.0. + +I declare that I am authorized and able to make this agreement and sign this +declaration. + +Signed, + +Martin Schmid m.schmid@equitania.com https://github.com/eqms + +List of contributors: + +Julia Eberle j.eberle@equitania.de https://github.com/eqje +Paul Exler p.exler@equitania.de https://github.com/eqpe +Oliver Hercht o.hercht@equitania.de https://github.com/eqoh +Johann Müller j.mueller@equitania.de https://github.com/eqjm +Robin Prijatel r.prijatel@equitania.de https://github.com/eqrp +Michal Sodomka m.sodomka@equitania.de https://github.com/eqmiso + diff --git a/doc/cla/corporate/merchise.md b/doc/cla/corporate/merchise.md index 6cc3e022152f9cc114caa1319f00648bde8cc0ce..d8d5e75f8305632617e91c0c71769d101277d45b 100644 --- a/doc/cla/corporate/merchise.md +++ b/doc/cla/corporate/merchise.md @@ -18,3 +18,5 @@ Henrik Pestano henrik@merchise.org https://github.com/hpestano Oraldo Jacinto Simón oraldo@merchise.org Sergio Valdés sergiov@merchise.org Medardo RodrÃguez med@merchise.org https://github.com/med-merchise +Abel Firvida firvida@merchise.org +Abel Firvida abel@merchise.org diff --git a/doc/cla/corporate/quartile.md b/doc/cla/corporate/quartile.md new file mode 100644 index 0000000000000000000000000000000000000000..b43892e12279cc6e56767e2904ffd35e845b1eb6 --- /dev/null +++ b/doc/cla/corporate/quartile.md @@ -0,0 +1,14 @@ +Hong Kong, 2017-09-18 + +Quartile Limited agrees to the terms of the Odoo Corporate Contributor License Agreement v1.0. + +I declare that I am authorized and able to make this agreement and sign this declaration. + +Signed, + +Yoshi Tashiro tashiro@quartile.co https://github.com/yostashiro + +List of contributors: + +Yoshi Tashiro tashiro@quartile.co https://github.com/yostashiro +Tim Lai tl@quartile.co https://github.com/TimLai125 diff --git a/doc/cla/corporate/sewisoft.md b/doc/cla/corporate/sewisoft.md new file mode 100644 index 0000000000000000000000000000000000000000..6b76b2feca1cd0911f0411d2411d92649827d3ea --- /dev/null +++ b/doc/cla/corporate/sewisoft.md @@ -0,0 +1,16 @@ +Germany, 2017-07-25 + +sewisoft UG agrees to the terms of the Odoo Corporate +Contributor License Agreement v1.0. + +I declare that I am authorized and able to make this agreement and sign this +declaration. + +Signed, + +Günter Selbert guenter.selbert@sewisoft.de https://github.com/Guenzn + +List of contributors: + +Günter Selbert guenter.selbert@sewisoft.de https://github.com/Guenzn +Stefan Wild stefan.wild@sewisoft.de https://github.com/wildi1 diff --git a/doc/cla/corporate/syleam.md b/doc/cla/corporate/syleam.md index 75ac0025efe7d8132e52bcb65551b539229fd890..7c2e09760d0f151a772e18ac60dd09b03cccfacd 100644 --- a/doc/cla/corporate/syleam.md +++ b/doc/cla/corporate/syleam.md @@ -14,4 +14,7 @@ List of contributors: Sébastien Lange sebastien.lange@syleam.fr https://github.com/alnslang Sylvain Garancher sylvain.garancher@syleam.fr https://github.com/sylvain-garancher -Alexandre Moreau alexandre.moreau@syleam.fr https://github.com/a-moreau +Alexandre Moreau alexandre.moreau@syleam.fr https://github.com/a-moreau (Up to 2017-08-31) +Christopher Tribbeck chris.tribbeck@syleam.fr https://github.com/ctribbeck +Jérémy Cormier jeremy.cormier@syleam.fr https://github.com/Corbiezorq +Samuel Piquet samuel.piquet@syleam.fr https://github.com/sa3m diff --git a/doc/cla/corporate/tvtmarine-automation.md b/doc/cla/corporate/tvtmarine-automation.md index 8b5b59d501b2011f802167c65e726d71d6bc4752..de0906f033162bcc0f4480ef8705d8bdeea98853 100644 --- a/doc/cla/corporate/tvtmarine-automation.md +++ b/doc/cla/corporate/tvtmarine-automation.md @@ -14,4 +14,6 @@ List of contributors: David Tran david.tran@tvtmarine.com https://github.com/davidtranhp Long Do dhlong.1209@gmail.com https://github.com/hoanglong87 -Hao Hoang hao.hoang@ma.tvtmarine.com https://github.com/hoanghao2001 \ No newline at end of file +Hao Hoang hao.hoang@ma.tvtmarine.com https://github.com/hoanghao2001 +Tommy Tran tu.tran@tvtmarine.com https://github.com/tutran81 +Emily Ha emily.ha@tvtmarine.com https://github.com/emilyha \ No newline at end of file diff --git a/doc/cla/corporate/vauxoo.md b/doc/cla/corporate/vauxoo.md index 096c835ef552158779616cde48094f48dfe3f7cf..017ffe08bbad84da20a6517ad445214915b7f146 100644 --- a/doc/cla/corporate/vauxoo.md +++ b/doc/cla/corporate/vauxoo.md @@ -38,3 +38,4 @@ Gabriela Mogollon gmogollon@vauxoo.com https://github.com/GavyMG Jesus Zapata jesus@vauxoo.com https://github.com/JesusZapata Germana Oliveira germana@vauxoo.com https://github.com/goliveirab Mariano Fernandez mariano@vauxoo.com https://github.com/Batuto +Edgar Rivero edgar@vauxoo.com https://github.com/egrivero diff --git a/doc/cla/corporate/xoe-corp.md b/doc/cla/corporate/xoe-corp.md new file mode 100644 index 0000000000000000000000000000000000000000..ef449e25d72cd3b69a13b0ab5b028000438a8344 --- /dev/null +++ b/doc/cla/corporate/xoe-corp.md @@ -0,0 +1,15 @@ +Colombia, 2016-04-20 + +Xoe Corp SAS. agrees to the terms of the Odoo Corporate Contributor License +Agreement v1.0. + +I declare that I am authorized and able to make this agreement and sign this +declaration. + +Signed, + +David Arnold dar@xoe.solutions https://github.com/blaggacao + +List of contributors: + +David Arnold dar@devco.co https://github.com/blaggacao diff --git a/doc/cla/individual/bazemoreb.md b/doc/cla/individual/bazemoreb.md new file mode 100644 index 0000000000000000000000000000000000000000..c800a883ceecdec605078a468720fe8d1a5115a0 --- /dev/null +++ b/doc/cla/individual/bazemoreb.md @@ -0,0 +1,11 @@ +USA, 2017-10-01 + +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, + +Brandon Bazemore brandonb@audian.com https://github.com/bazemoreb diff --git a/doc/cla/individual/blaggacao.md b/doc/cla/individual/blaggacao.md new file mode 100644 index 0000000000000000000000000000000000000000..30b2fee69a0e4a1b0f4d5603dec9cf017aab2914 --- /dev/null +++ b/doc/cla/individual/blaggacao.md @@ -0,0 +1,12 @@ +Colombia, 2017-09-01 + +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, + +David Arnold dar@xoe.solutions https://github.com/blaggacao + diff --git a/doc/cla/individual/danger89.md b/doc/cla/individual/danger89.md new file mode 100644 index 0000000000000000000000000000000000000000..11c9e89616c4936acef98e40c54e5221000d5ac0 --- /dev/null +++ b/doc/cla/individual/danger89.md @@ -0,0 +1,11 @@ +The Netherlands, 2017-10-02 + +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, + +Melroy van den Berg webmaster1989@gmail.com https://github.com/danger89 diff --git a/doc/cla/individual/hansmi.md b/doc/cla/individual/hansmi.md new file mode 100644 index 0000000000000000000000000000000000000000..ff47950308312de4d8f4ebe626914c623b5e30c0 --- /dev/null +++ b/doc/cla/individual/hansmi.md @@ -0,0 +1,11 @@ +Switzerland, 2017-09-15 + +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, + +Michael Hanselmann public@hansmi.ch https://github.com/hansmi diff --git a/odoo/addons/base/res/res_currency_data.xml b/odoo/addons/base/res/res_currency_data.xml index 75efdb752a92c915809926c1ce15825bef688b6d..d158a91ce9b262af0d0c8ff87952dc95235c05ea 100644 --- a/odoo/addons/base/res/res_currency_data.xml +++ b/odoo/addons/base/res/res_currency_data.xml @@ -99,6 +99,7 @@ <field name="name">IDR</field> <field name="symbol">Rp</field> <field name="rounding">0.01</field> + <field name="position">before</field> <field name="active" eval="False"/> <field name="currency_unit_label">Rupiah</field> <field name="currency_subunit_label">Sen</field> diff --git a/odoo/addons/test_new_api/tests/test_new_fields.py b/odoo/addons/test_new_api/tests/test_new_fields.py index f99f9b466eb65e09cc7d830fda2e7ca9f35717a7..73ba36f481343df74dcdc5bba7e46e3ac9872dd6 100644 --- a/odoo/addons/test_new_api/tests/test_new_fields.py +++ b/odoo/addons/test_new_api/tests/test_new_fields.py @@ -712,6 +712,30 @@ class TestFields(common.TransactionCase): self.assertFalse(discussion.very_important_messages) self.assertFalse(message.exists()) + def test_70_x2many_write(self): + discussion = self.env.ref('test_new_api.discussion_0') + Message = self.env['test_new_api.message'] + # There must be 3 messages, 0 important + self.assertEqual(len(discussion.messages), 3) + self.assertEqual(len(discussion.important_messages), 0) + self.assertEqual(len(discussion.very_important_messages), 0) + discussion.important_messages = [(0, 0, { + 'body': 'What is the answer?', + 'important': True, + })] + # There must be 4 messages, 1 important + self.assertEqual(len(discussion.messages), 4) + self.assertEqual(len(discussion.important_messages), 1) + self.assertEqual(len(discussion.very_important_messages), 1) + discussion.very_important_messages |= Message.new({ + 'body': '42', + 'important': True, + }) + # There must be 5 messages, 2 important + self.assertEqual(len(discussion.messages), 5) + self.assertEqual(len(discussion.important_messages), 2) + self.assertEqual(len(discussion.very_important_messages), 2) + class TestX2many(common.TransactionCase): def test_search_many2many(self): diff --git a/odoo/fields.py b/odoo/fields.py index 780a1c318bca2d6e10470a60d80e75cf495ab253..bef9ba4302a17bec915d4e3d01b9475949f9830e 100644 --- a/odoo/fields.py +++ b/odoo/fields.py @@ -39,18 +39,23 @@ Default = object() # default value for __init__() methods def copy_cache(records, env): """ Recursively copy the cache of ``records`` to the environment ``env``. """ - src, dst = records.env.cache, env.cache - todo, done = set(records), set() + src = records.env + todo = defaultdict(set) # {model_name: ids} + done = defaultdict(set) # {model_name: ids} + todo[records._name].update(records._ids) while todo: - record = todo.pop() - if record not in done: - done.add(record) - target = record.with_env(env) - for field in src.get_fields(record): - value = src.get(record, field) - dst.set(target, field, value) - if value and field.type in ('many2one', 'one2many', 'many2many', 'reference'): - todo.update(field.convert_to_record(value, record)) + model_name = next(iter(todo)) + record_ids = todo.pop(model_name) - done[model_name] + done[model_name].update(record_ids) + for name, field in src[model_name]._fields.items(): + src_cache = src.cache[field] + dst_cache = env.cache[field] + for record_id in record_ids: + if record_id in src_cache: + # copy the cached value as such + value = dst_cache[record_id] = src_cache[record_id] + if field.relational and isinstance(value, tuple): + todo[field.comodel_name].update(value) def resolve_mro(model, name, predicate): @@ -2245,14 +2250,13 @@ class One2many(_RelationalMulti): elif act[0] == 6: record = records[-1] comodel.browse(act[2]).write({inverse: record.id}) - query = "SELECT id FROM %s WHERE %s=%%s AND id <> ALL(%%s)" % (comodel._table, inverse) - comodel._cr.execute(query, (record.id, act[2] or [0])) - lines = comodel.browse([row[0] for row in comodel._cr.fetchall()]) + domain = self.domain(records) if callable(self.domain) else self.domain + domain = domain + [(inverse, 'in', records.ids), ('id', 'not in', act[2] or [0])] inverse_field = comodel._fields[inverse] if inverse_field.ondelete == 'cascade': - lines.unlink() + comodel.search(domain).unlink() else: - lines.write({inverse: False}) + comodel.search(domain).write({inverse: False}) class Many2many(_RelationalMulti):