diff options
| author | Christophe Simonis <chs@odoo.com> | 2017-11-09 19:16:05 +0100 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2017-11-09 19:16:05 +0100 |
| commit | de93645fe3c85883d2f08677afad51456815fa6e (patch) | |
| tree | 618ad9d3144a272e082aee1fc4b824f97a13a59f /addons/web | |
| parent | acd26691eef2930b03c2d3fb228110a1a6cc5f12 (diff) | |
| parent | 82add438f21085a799f1d2d86a44d523c4fd2690 (diff) | |
[MERGE] forward port branch saas-16 up to 82add438f21
Diffstat (limited to 'addons/web')
10 files changed, 213 insertions, 33 deletions
diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js index 2d2a5d5ea4d..e5a7cc3da2f 100644 --- a/addons/web/static/src/js/fields/relational_fields.js +++ b/addons/web/static/src/js/fields/relational_fields.js @@ -503,9 +503,11 @@ var FieldMany2One = AbstractField.extend({ title: _t("Open: ") + self.string, view_id: view_id, readonly: !self.can_write, - on_saved: function () { - self._setValue(self.value.data, {forceChange: true}); - self.trigger_up('reload', {db_id: self.value.id}); + on_saved: function (record, changed) { + if (changed) { + self._setValue(self.value.data, {forceChange: true}); + self.trigger_up('reload', {db_id: self.value.id}); + } }, }).open(); }); diff --git a/addons/web/static/src/js/services/data_manager.js b/addons/web/static/src/js/services/data_manager.js index 32d526e160d..e99f9ebb6f2 100644 --- a/addons/web/static/src/js/services/data_manager.js +++ b/addons/web/static/src/js/services/data_manager.js @@ -424,7 +424,7 @@ return core.Class.extend({ var deps = fieldsInfo[node.attrs.name].fieldDependencies; for (var dependency_name in deps) { if (!(dependency_name in fieldsInfo)) { - fieldsInfo[dependency_name] = {'name': dependency_name, 'type': deps[dependency_name].type}; + fieldsInfo[dependency_name] = {'name': dependency_name, 'type': deps[dependency_name].type, 'options': deps[dependency_name].options || {}}; } } } diff --git a/addons/web/static/src/js/views/basic/basic_model.js b/addons/web/static/src/js/views/basic/basic_model.js index 4412d29c455..4028bff322a 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -896,6 +896,9 @@ var BasicModel = AbstractModel.extend({ return def.resolve(changedFields); } + def.then(function () { + record._isDirty = false; + }); // in the case of a write, only perform the RPC if there are changes to save if (method === 'create' || changedFields.length) { var args = method === 'write' ? [[record.data.id], changes] : [changes]; @@ -917,7 +920,6 @@ var BasicModel = AbstractModel.extend({ // Erase changes as they have been applied record._changes = {}; - record._isDirty = false; // Update the data directly or reload them if (shouldReload) { @@ -947,8 +949,8 @@ var BasicModel = AbstractModel.extend({ */ addFieldsInfo: function (recordID, viewInfo) { var record = this.localData[recordID]; - record.fields = _.defaults(record.fields, viewInfo.fields); - record.fieldsInfo = _.defaults(record.fieldsInfo, viewInfo.fieldsInfo); + record.fields = _.extend({}, record.fields, viewInfo.fields); + record.fieldsInfo = _.extend({}, record.fieldsInfo, viewInfo.fieldsInfo); }, /** * Manually sets a resource as dirty. This is used to notify that a field diff --git a/addons/web/static/src/js/views/list/list_controller.js b/addons/web/static/src/js/views/list/list_controller.js index 460ad5de100..67cc6271509 100644 --- a/addons/web/static/src/js/views/list/list_controller.js +++ b/addons/web/static/src/js/views/list/list_controller.js @@ -355,8 +355,8 @@ var ListController = BasicController.extend({ // trigger a click on the main bus, which would be then caught by the // list editable renderer and would unselect the newly created row event.stopPropagation(); - - if (this.editable) { + var state = this.model.get(this.handle, {raw: true}); + if (this.editable && !state.groupedBy.length) { this._addRecord(); } else { this.trigger_up('switch_view', {view_type: 'form', res_id: undefined}); diff --git a/addons/web/static/src/js/views/view_dialogs.js b/addons/web/static/src/js/views/view_dialogs.js index fe38fae753e..a33f6f740b8 100644 --- a/addons/web/static/src/js/views/view_dialogs.js +++ b/addons/web/static/src/js/views/view_dialogs.js @@ -81,9 +81,9 @@ var FormViewDialog = ViewDialog.extend({ * @param {Object} [options.fields_view] optional form fields_view * @param {boolean} [options.readonly=false] only applicable when not in * creation mode - * @param {function} [options.on_save] callback to execute when clicking on - * 'Save' (form view's 'saveRecord' by default) - * @param {function} [options.on_saved] callback executed after on_save + * @param {function} [options.on_saved] callback executed after saving a + * record. It will be called with the record data, and a boolean which + * indicates if something was changed * @param {BasicModel} [options.model] if given, it will be used instead of * a new form view model * @param {string} [options.recordID] if given, the model has to be given as @@ -214,24 +214,16 @@ var FormViewDialog = ViewDialog.extend({ _save: function () { var self = this; - var def; - if (this.options.on_save) { - if (this.form_view.canBeSaved()) { - return $.Deferred().reject(); - } - def = this.options.on_save(this.form_view.model.get(this.form_view.handle)); - } else { - def = this.form_view.saveRecord(this.form_view.handle, { + return this.form_view.saveRecord(this.form_view.handle, { stayInEdit: true, reload: false, savePoint: this.shouldSaveLocally, viewType: 'form', - }); - } - return $.when(def).then(function () { + }).then(function (changedFields) { // record might have been changed by the save (e.g. if this was a new record, it has an // id now), so don't re-use the copy obtained before the save - self.on_saved(self.form_view.model.get(self.form_view.handle)); + var record = self.form_view.model.get(self.form_view.handle); + self.on_saved(record, !!changedFields.length); }); }, }); @@ -402,7 +394,8 @@ var SelectCreateDialog = ViewDialog.extend({ var results = pyeval.eval_domains_and_contexts({ domains: [this.domain].concat(domains), contexts: [this.context].concat(contexts), - group_by_seq: groupbys || [] + group_by_seq: groupbys || [], + eval_context: this.getSession().user_context, }); var context = _.omit(results.context, function (value, key) { return key.indexOf('search_default_') === 0; }); return { diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js index f665fa160e4..2e8499b31e1 100644 --- a/addons/web/static/tests/fields/relational_fields_tests.js +++ b/addons/web/static/tests/fields/relational_fields_tests.js @@ -137,6 +137,7 @@ QUnit.module('relational_fields', { partner_ids: [], turtle_ref: 'product,37', }], + onchanges: {}, }, user: { fields: { @@ -212,6 +213,98 @@ QUnit.module('relational_fields', { form.destroy(); }); + QUnit.test('editing a many2one, but not changing anything', function (assert) { + assert.expect(2); + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '<form string="Partners">' + + '<sheet>' + + '<field name="trululu"/>' + + '</sheet>' + + '</form>', + archs: { + 'partner,false,form': '<form string="Partners"><field name="display_name"/></form>', + }, + res_id: 1, + mockRPC: function (route, args) { + if (args.method === 'get_formview_id') { + assert.deepEqual(args.args[0], [4], "should call get_formview_id with correct id"); + return $.when(false); + } + return this._super(route, args); + }, + viewOptions: { + ids: [1, 2], + }, + }); + + form.$buttons.find('.o_form_button_edit').click(); + + // click on the external button (should do an RPC) + form.$('.o_external_button').click(); + // save and close modal + $('.modal .modal-footer .btn-primary:first').click(); + // save form + form.$buttons.find('.o_form_button_save').click(); + // click next on pager + form.pager.$('.o_pager_next').click(); + + // this checks that the view did not ask for confirmation that the + // record is dirty + assert.strictEqual(form.pager.$el.text().trim(), '2 / 2', + 'pager should be at second page'); + form.destroy(); + }); + + QUnit.test('editing a many2one (with form view opened with external button)', function (assert) { + assert.expect(1); + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '<form string="Partners">' + + '<sheet>' + + '<field name="trululu"/>' + + '</sheet>' + + '</form>', + archs: { + 'partner,false,form': '<form string="Partners"><field name="foo"/></form>', + }, + res_id: 1, + mockRPC: function (route, args) { + if (args.method === 'get_formview_id') { + return $.when(false); + } + return this._super(route, args); + }, + viewOptions: { + ids: [1, 2], + }, + }); + + form.$buttons.find('.o_form_button_edit').click(); + + // click on the external button (should do an RPC) + form.$('.o_external_button').click(); + + $('.modal input[name="foo"]').val('brandon').trigger('input'); + + // save and close modal + $('.modal .modal-footer .btn-primary:first').click(); + // save form + form.$buttons.find('.o_form_button_save').click(); + // click next on pager + form.pager.$('.o_pager_next').click(); + + // this checks that the view did not ask for confirmation that the + // record is dirty + assert.strictEqual(form.pager.$el.text().trim(), '2 / 2', + 'pager should be at second page'); + form.destroy(); + }); + QUnit.test('many2ones in form views with show_adress', function (assert) { assert.expect(4); var form = createView({ @@ -2587,6 +2680,45 @@ QUnit.module('relational_fields', { form.destroy(); }); + QUnit.test('edition of one2many field, with onchange and not inline sub view', function (assert) { + assert.expect(2); + + this.data.turtle.onchanges.turtle_int = function (obj) { + obj.turtle_foo = String(obj.turtle_int); + }; + this.data.partner.onchanges.turtles = function () {}; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '<form string="Partners">' + + '<field name="turtles"/>' + + '</form>', + archs: { + 'turtle,false,list': '<tree><field name="turtle_foo"/></tree>', + 'turtle,false,form': '<form><group><field name="turtle_foo"/><field name="turtle_int"/></group></form>', + }, + mockRPC: function (route, args) { + return this._super.apply(this, arguments); + }, + res_id: 1, + }); + form.$buttons.find('.o_form_button_edit').click(); + form.$('.o_field_x2many_list_row_add a').click(); + $('input[name="turtle_int"]').val('5').trigger('input'); + $('.modal-footer button.btn-primary').first().click(); + assert.strictEqual(form.$('tbody tr:eq(1) td.o_data_cell').text(), '5', + 'should display 5 in the foo field'); + form.$('tbody tr:eq(1) td.o_data_cell').click(); + + $('input[name="turtle_int"]').val('3').trigger('input'); + $('.modal-footer button.btn-primary').first().click(); + assert.strictEqual(form.$('tbody tr:eq(1) td.o_data_cell').text(), '3', + 'should now display 3 in the foo field'); + form.destroy(); + }); + QUnit.test('sorting one2many fields', function (assert) { assert.expect(4); diff --git a/addons/web/static/tests/helpers/mock_server.js b/addons/web/static/tests/helpers/mock_server.js index f7d3f2fb307..3556fb946aa 100644 --- a/addons/web/static/tests/helpers/mock_server.js +++ b/addons/web/static/tests/helpers/mock_server.js @@ -32,9 +32,6 @@ var MockServer = Class.extend({ if (!('name' in model.fields)) { model.fields.name = {string: "Name", type: "char", default: "name"}; } - for (var fieldName in model.onchanges) { - model.fields[fieldName].onChange = "1"; - } model.records = model.records || []; for (var i = 0; i < model.records.length; i++) { @@ -282,7 +279,7 @@ var MockServer = Class.extend({ // add onchanges if (name in onchanges) { - field.onChange="1"; + node.attrs.on_change="1"; } }); return { diff --git a/addons/web/static/tests/views/basic_model_tests.js b/addons/web/static/tests/views/basic_model_tests.js index 65962870be7..492dfd4224c 100644 --- a/addons/web/static/tests/views/basic_model_tests.js +++ b/addons/web/static/tests/views/basic_model_tests.js @@ -203,6 +203,7 @@ QUnit.module('Views', { QUnit.test('basic onchange', function (assert) { assert.expect(5); + this.data.partner.fields.foo.onChange = true; this.data.partner.onchanges.foo = function (obj) { obj.bar = obj.foo.length; }; @@ -240,6 +241,7 @@ QUnit.module('Views', { QUnit.test('onchange with a many2one', function (assert) { assert.expect(5); + this.data.partner.fields.product_id.onChange = true; this.data.partner.onchanges.product_id = function (obj) { if (obj.product_id === 37) { obj.foo = "space lollipop"; @@ -280,6 +282,7 @@ QUnit.module('Views', { QUnit.test('onchange on a one2many not in view (fieldNames)', function (assert) { assert.expect(6); + this.data.partner.fields.foo.onChange = true; this.data.partner.onchanges.foo = function (obj) { obj.bar = obj.foo.length; obj.product_ids = []; @@ -425,6 +428,7 @@ QUnit.module('Views', { QUnit.test('onchange on a char with an unchanged many2one', function (assert) { assert.expect(2); + this.data.partner.fields.foo.onChange = true; this.data.partner.onchanges.foo = function (obj) { obj.foo = obj.foo + " alligator"; }; @@ -453,6 +457,7 @@ QUnit.module('Views', { QUnit.test('onchange on a char with another many2one not set to a value', function (assert) { assert.expect(2); this.data.partner.records[0].product_id = false; + this.data.partner.fields.foo.onChange = true; this.data.partner.onchanges.foo = function (obj) { obj.foo = obj.foo + " alligator"; }; @@ -1153,6 +1158,7 @@ QUnit.module('Views', { assert.expect(4); this.data.partner.fields.total.default = 50; + this.data.partner.fields.product_ids.onChange = true; this.data.partner.onchanges.product_ids = function (obj) { obj.total += 100; }; @@ -1497,6 +1503,7 @@ QUnit.module('Views', { assert.expect(6); this.params.fieldNames = ['foo', 'bar']; + this.data.partner.fields.foo.onChange = true; this.data.partner.onchanges.foo = function (obj) { obj.bar = obj.foo.length; }; @@ -1562,6 +1569,7 @@ QUnit.module('Views', { assert.expect(6); this.params.fieldNames = ['foo', 'bar']; + this.data.partner.fields.foo.onChange = true; this.data.partner.onchanges.foo = function (obj) { obj.bar = obj.foo.length; }; @@ -2138,6 +2146,7 @@ QUnit.module('Views', { }; _.extend(this.data.partner.fields, newFields); + this.data.partner.fields.foobool.onChange = true; this.data.partner.onchanges.foobool = function (obj) { if (obj.foobool) { obj.foobool2 = true; diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js index 32e3ad0046a..242eb2f62d6 100644 --- a/addons/web/static/tests/views/list_tests.js +++ b/addons/web/static/tests/views/list_tests.js @@ -2628,7 +2628,7 @@ QUnit.module('Views', { // Editable grouped list views are not supported, so the purpose of this // test is to check that when a list view is grouped, its editable // attribute is ignored - assert.expect(4); + assert.expect(5); var list = createView({ View: ListView, @@ -2636,8 +2636,9 @@ QUnit.module('Views', { data: this.data, arch: '<tree editable="top"><field name="foo"/><field name="bar"/></tree>', intercepts: { - switch_view: function () { - assert.step('switch view'); + switch_view: function (event) { + var resID = event.data.res_id || false; + assert.step('switch view ' + event.data.view_type + ' ' + resID); }, }, }); @@ -2649,9 +2650,15 @@ QUnit.module('Views', { // reload with groupBy list.reload({groupBy: ['bar']}); + + // clicking on record should open the form view list.$('.o_group_header:first').click(); list.$('.o_data_cell:first').click(); - assert.verifySteps(['switch view'], 'one switch view should have been requested'); + + // clicking on create button should open the form view + list.$buttons.find('.o_list_button_add').click(); + assert.verifySteps(['switch view form 1', 'switch view form false'], + 'two switch view to form should have been requested'); list.destroy(); }); diff --git a/addons/web/static/tests/views/view_dialogs_tests.js b/addons/web/static/tests/views/view_dialogs_tests.js index 43bf4ee6694..7a107c70346 100644 --- a/addons/web/static/tests/views/view_dialogs_tests.js +++ b/addons/web/static/tests/views/view_dialogs_tests.js @@ -140,6 +140,44 @@ QUnit.module('Views', { dialog.destroy(); }); + QUnit.test('SelectCreateDialog correctly evaluates domains', function (assert) { + assert.expect(1); + + var parent = createParent({ + data: this.data, + archs: { + 'partner,false,list': + '<tree string="Partner">' + + '<field name="display_name"/>' + + '<field name="foo"/>' + + '</tree>', + 'partner,false,search': + '<search>' + + '<field name="foo"/>' + + '</search>', + }, + mockRPC: function (route, args) { + if (route === '/web/dataset/search_read') { + assert.deepEqual(args.domain, [['id', '=', 2]], + "should have correctly evaluated the domain"); + } + return this._super.apply(this, arguments); + }, + session: { + user_context: {uid: 2}, + }, + }); + + var dialog = new dialogs.SelectCreateDialog(parent, { + no_create: true, + readonly: true, + res_model: 'partner', + domain: "[['id', '=', uid]]", + }).open(); + + dialog.destroy(); + }); + QUnit.test('SelectCreateDialog list view in readonly', function (assert) { assert.expect(1); |
