diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py index cef6496c35203c92e5cf57bcd434b6a23a41be94..1ee7104dc8dc9791b09e3278a3d8926a35338733 100644 --- a/addons/mail/wizard/mail_compose_message.py +++ b/addons/mail/wizard/mail_compose_message.py @@ -353,7 +353,6 @@ class MailComposer(models.TransientModel): - normal mode: return rendered values /!\ for x2many field, this onchange return command instead of ids """ - attachment_ids = [] if template_id and composition_mode == 'mass_mail': template = self.env['mail.template'].browse(template_id) fields = ['subject', 'body_html', 'email_from', 'reply_to', 'mail_server_id'] @@ -369,6 +368,7 @@ class MailComposer(models.TransientModel): values = self.generate_email_for_composer(template_id, [res_id])[res_id] # transform attachments into attachment_ids; not attached to the document because this will # be done further in the posting process, allowing to clean database if email not send + attachment_ids = [] Attachment = self.env['ir.attachment'] for attach_fname, attach_datas in values.pop('attachments', []): data_attach = { @@ -380,6 +380,8 @@ class MailComposer(models.TransientModel): 'type': 'binary', # override default_type from context, possibly meant for another model! } attachment_ids.append(Attachment.create(data_attach).id) + if values.get('attachment_ids', []) or attachment_ids: + values['attachment_ids'] = [(5,)] + values.get('attachment_ids', []) + attachment_ids else: default_values = self.with_context(default_composition_mode=composition_mode, default_model=model, default_res_id=res_id).default_get(['composition_mode', 'model', 'res_id', 'parent_id', 'partner_ids', 'subject', 'body', 'email_from', 'reply_to', 'attachment_ids', 'mail_server_id']) values = dict((key, default_values[key]) for key in ['subject', 'body', 'partner_ids', 'email_from', 'reply_to', 'attachment_ids', 'mail_server_id'] if key in default_values) @@ -391,10 +393,7 @@ class MailComposer(models.TransientModel): # ORM handle the assignation of command list on new onchange (api.v8), # this force the complete replacement of x2many field with # command and is compatible with onchange api.v7 - attachment_ids += values.pop('attachment_ids' , []) values = self._convert_to_write(values) - if attachment_ids: - values.update(attachment_ids=[(6, 0, attachment_ids)]) return {'value': values} diff --git a/addons/test_mail/tests/test_mail_template.py b/addons/test_mail/tests/test_mail_template.py index 55ddbb7b667aad9a416bb2c687b09f17d8bcbd02..bb4a96470d0bbf8f4fb777eb5ce529c9fad9bcad 100644 --- a/addons/test_mail/tests/test_mail_template.py +++ b/addons/test_mail/tests/test_mail_template.py @@ -75,7 +75,7 @@ class TestMailTemplate(BaseFunctionalTest, MockEmails, TestRecipients): static attachments are not duplicated and while reports are re-generated, and that intermediary attachments are dropped.""" - composer = self.env['mail.compose.message'].create({}) + composer = self.env['mail.compose.message'].with_context(default_attachment_ids=[]).create({}) report_template = self.env.ref('web.action_report_externalpreview') template_1 = self.email_template.copy({ 'report_template': report_template.id, @@ -85,20 +85,20 @@ class TestMailTemplate(BaseFunctionalTest, MockEmails, TestRecipients): 'report_template': report_template.id, }) - onchange_templates = [template_1, template_2, template_1] - attachments_onchange = [] + onchange_templates = [template_1, template_2, template_1, False] + attachments_onchange = [composer.attachment_ids] # template_1 has two static attachments and one dynamically generated report, # template_2 only has the report, so we should get 3, 1, 3 attachments - attachment_numbers = [3, 1, 3] + attachment_numbers = [0, 3, 1, 3, 0] - for template in onchange_templates: - onchange = composer.onchange_template_id( - template.id, 'comment', 'mail.test', self.test_record.id - ) - values = composer._convert_to_record(composer._convert_to_cache(onchange['value'])) - attachments = values['attachment_ids'] - composer.attachment_ids = attachments # we apply the onchange - attachments_onchange.append(attachments) + with self.env.do_in_onchange(): + for template in onchange_templates: + onchange = composer.onchange_template_id( + template.id if template else False, 'comment', self.test_record._name, self.test_record.id + ) + values = composer._convert_to_record(composer._convert_to_cache(onchange['value'])) + attachments_onchange.append(values['attachment_ids']) + composer.update(onchange['value']) self.assertEqual( [len(attachments) for attachments in attachments_onchange], @@ -106,7 +106,7 @@ class TestMailTemplate(BaseFunctionalTest, MockEmails, TestRecipients): ) self.assertTrue( - len(attachments_onchange[0] & attachments_onchange[2]) == 2, + len(attachments_onchange[1] & attachments_onchange[3]) == 2, "The two static attachments on the template should be common to the two onchanges" ) diff --git a/addons/web/static/src/js/views/list/list_editable_renderer.js b/addons/web/static/src/js/views/list/list_editable_renderer.js index a0ce8d31af4afaa664800cf7e9027e8ae13dc4ea..4b8dd3e1d8832b80ae8fe701108ba270d0c505b8 100644 --- a/addons/web/static/src/js/views/list/list_editable_renderer.js +++ b/addons/web/static/src/js/views/list/list_editable_renderer.js @@ -556,48 +556,48 @@ ListRenderer.include({ _resequence: function (event, ui) { var self = this; var movedRecordID = ui.item.data('id'); - var rows = this.state.data; - var row = _.findWhere(rows, {id: movedRecordID}); - var index0 = rows.indexOf(row); - var index1 = ui.item.index(); - var lower = Math.min(index0, index1); - var upper = Math.max(index0, index1) + 1; - - var order = _.findWhere(self.state.orderedBy, {name: self.handleField}); - var asc = !order || order.asc; - var reorderAll = false; - var sequence = (asc ? -1 : 1) * Infinity; - - // determine if we need to reorder all lines - _.each(rows, function (row, index) { - if ((index < lower || index >= upper) && - ((asc && sequence >= row.data[self.handleField]) || - (!asc && sequence <= row.data[self.handleField]))) { - reorderAll = true; - } - sequence = row.data[self.handleField]; - }); + self.unselectRow().then(function () { + var rows = self.state.data; + var row = _.findWhere(rows, {id: movedRecordID}); + var index0 = rows.indexOf(row); + var index1 = ui.item.index(); + var lower = Math.min(index0, index1); + var upper = Math.max(index0, index1) + 1; + + var order = _.findWhere(self.state.orderedBy, {name: self.handleField}); + var asc = !order || order.asc; + var reorderAll = false; + var sequence = (asc ? -1 : 1) * Infinity; + + // determine if we need to reorder all lines + _.each(rows, function (row, index) { + if ((index < lower || index >= upper) && + ((asc && sequence >= row.data[self.handleField]) || + (!asc && sequence <= row.data[self.handleField]))) { + reorderAll = true; + } + sequence = row.data[self.handleField]; + }); - if (reorderAll) { - rows = _.without(rows, row); - rows.splice(index1, 0, row); - } else { - rows = rows.slice(lower, upper); - rows = _.without(rows, row); - if (index0 > index1) { - rows.unshift(row); + if (reorderAll) { + rows = _.without(rows, row); + rows.splice(index1, 0, row); } else { - rows.push(row); + rows = rows.slice(lower, upper); + rows = _.without(rows, row); + if (index0 > index1) { + rows.unshift(row); + } else { + rows.push(row); + } } - } - var sequences = _.pluck(_.pluck(rows, 'data'), self.handleField); - var rowIDs = _.pluck(rows, 'id'); + var sequences = _.pluck(_.pluck(rows, 'data'), self.handleField); + var rowIDs = _.pluck(rows, 'id'); - if (!asc) { - rowIDs.reverse(); - } - this.unselectRow().then(function () { + if (!asc) { + rowIDs.reverse(); + } self.trigger_up('resequence', { rowIDs: rowIDs, offset: _.min(sequences), diff --git a/addons/web/static/tests/views/form_tests.js b/addons/web/static/tests/views/form_tests.js index f0f0da04e9481e580c8ea10ee47370e5e3416fa9..df94fbe106613713b4bfa7ba7929485b9d3df388 100644 --- a/addons/web/static/tests/views/form_tests.js +++ b/addons/web/static/tests/views/form_tests.js @@ -7525,6 +7525,68 @@ QUnit.module('Views', { .trigger($.Event('keydown', {which: $.ui.keyCode.ESCAPE})); form.destroy(); }); + + QUnit.test('resequence list lines when discardable lines are present', function (assert) { + assert.expect(8); + + var onchangeNum = 0; + + this.data.partner.onchanges = { + p: function (obj) { + onchangeNum++; + obj.foo = obj.p.length.toString(); + }, + }; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '<form>' + + '<field name="foo"/>' + + '<field name="p"/>' + + '</form>', + archs: { + 'partner,false,list': + '<tree editable="bottom">' + + '<field name="int_field" widget="handle"/>' + + '<field name="display_name" required="1"/>' + + '</tree>', + }, + }); + + assert.strictEqual(onchangeNum, 1, "one onchange happens when form is opened"); + assert.strictEqual(form.$('[name="foo"]').val(), "0", "onchange worked there is 0 line"); + + // Add one line + form.$('.o_field_x2many_list_row_add a').click(); + form.$('.o_field_one2many input:first').focus(); + form.$('.o_field_one2many input:first').val('first line').trigger('input'); + form.$('input[name="foo"]').click(); + assert.strictEqual(onchangeNum, 2, "one onchange happens when a line is added"); + assert.strictEqual(form.$('[name="foo"]').val(), "1", "onchange worked there is 1 line"); + + // Drag and drop second line before first one (with 1 draft and invalid line) + form.$('.o_field_x2many_list_row_add a').click(); + testUtils.dragAndDrop( + form.$('.ui-sortable-handle').eq(0), + form.$('.o_data_row').last(), + {position: 'bottom'} + ); + assert.strictEqual(onchangeNum, 3, "one onchange happens when lines are resequenced") + assert.strictEqual(form.$('[name="foo"]').val(), "1", "onchange worked there is 1 line"); + + // Add a second line + form.$('.o_field_x2many_list_row_add a').click(); + form.$('.o_field_one2many input:first').focus(); + form.$('.o_field_one2many input:first').val('second line').trigger('input'); + form.$('input[name="foo"]').click(); + assert.strictEqual(onchangeNum, 4, "one onchange happens when a line is added"); + assert.strictEqual(form.$('[name="foo"]').val(), "2", "onchange worked there is 2 lines"); + + form.destroy(); + }); + QUnit.test('if the focus is on the discard button, hitting ESCAPE should discard', function (assert) { assert.expect(1); diff --git a/addons/website_sale_wishlist/static/src/js/website_sale_wishlist.js b/addons/website_sale_wishlist/static/src/js/website_sale_wishlist.js index 153527096a5f4a1dfaeb9e851ecad7866ec40201..a5dbda94698aea90ca9ea64fa90e8289fb36f22f 100644 --- a/addons/website_sale_wishlist/static/src/js/website_sale_wishlist.js +++ b/addons/website_sale_wishlist/static/src/js/website_sale_wishlist.js @@ -138,7 +138,7 @@ var ProductWishlist = Widget.extend({ // can be hidden if empty $('#my_cart').removeClass('hidden'); website_sale_utils.animate_clone($('#my_cart'), tr, 25, 40); - return this.add_to_cart(product, tr.find('qty').val() || 1); + return this.add_to_cart(product, tr.find('input[name="add_qty"]').val() || 1); }, wishlist_mv: function(e){ var tr = $(e.currentTarget).parents('tr'); @@ -146,7 +146,7 @@ var ProductWishlist = Widget.extend({ $('#my_cart').removeClass('hidden'); website_sale_utils.animate_clone($('#my_cart'), tr, 25, 40); - var adding_deffered = this.add_to_cart(product, tr.find('qty').val() || 1); + var adding_deffered = this.add_to_cart(product, tr.find('input[name="add_qty"]').val() || 1); this.wishlist_rm(e, adding_deffered); return adding_deffered; },