diff options
| author | Christophe Simonis <chs@odoo.com> | 2017-07-20 11:48:49 +0200 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2017-07-20 11:48:49 +0200 |
| commit | fa3e89064a503c95ab2b283e5d5b39ca182f8f47 (patch) | |
| tree | d638f0cb99fc973169d670ff0bafd1b9474b4771 /addons/web/static | |
| parent | 0ae679e433ee4c5ca32ae1302d87d7fb33e0c78d (diff) | |
| parent | 104dfe3c07d36a6487f045f31d0999cc429cb5bb (diff) | |
[MERGE] forward port branch saas-16 up to 104dfe3c07d
Diffstat (limited to 'addons/web/static')
23 files changed, 374 insertions, 129 deletions
diff --git a/addons/web/static/src/js/chrome/view_manager.js b/addons/web/static/src/js/chrome/view_manager.js index 9b431d3e57a..a4a1d65f7b6 100644 --- a/addons/web/static/src/js/chrome/view_manager.js +++ b/addons/web/static/src/js/chrome/view_manager.js @@ -53,7 +53,8 @@ var ViewManager = Widget.extend(ControlPanelMixin, { */ on_attach_callback: function() { this.is_in_DOM = true; - if (this.active_view && this.active_view.controller.on_attach_callback) { + var controller = this.active_view && this.active_view.controller; + if (controller && this.active_view.controller.on_attach_callback) { this.active_view.controller.on_attach_callback(); } }, @@ -579,10 +580,11 @@ var ViewManager = Widget.extend(ControlPanelMixin, { // Wrong default_* and search_default_* values will no give the expected result // Wrong group_by values will simply fail and forbid rendering of the destination view var ncontext = new Context( - _.object(_.reject(_.pairs(_.extend({}, action_data.context, self.env.context)), function(pair) { + _.object(_.reject(_.pairs(self.env.context), function(pair) { return pair[0].match('^(?:(?:default_|search_default_|show_).+|.+_view_ref|group_by|group_by_no_leaf|active_id|active_ids)$') !== null; })) ); + ncontext.add(action_data.context || {}); ncontext.add({active_model: self.env.modelName}); if (res_ids) { ncontext.add({ diff --git a/addons/web/static/src/js/fields/abstract_field.js b/addons/web/static/src/js/fields/abstract_field.js index 4ae4eec9c05..fa2f37a123f 100644 --- a/addons/web/static/src/js/fields/abstract_field.js +++ b/addons/web/static/src/js/fields/abstract_field.js @@ -65,9 +65,12 @@ var AbstractField = Widget.extend({ * @constructor * @param {Widget} parent * @param {string} name The field name defined in the model - * @param {Object} record A record object (result of the get method of a basic model) + * @param {Object} record A record object (result of the get method of + * a basic model) * @param {Object} [options] * @param {string} [options.mode=readonly] should be 'readonly' or 'edit' + * @param {string} [options.standalone=false] this flag indicates that the + * field does not have a controller to delegate to */ init: function (parent, name, record, options) { this._super(parent); @@ -157,6 +160,10 @@ var AbstractField = Widget.extend({ // calls to the format (resp. parse) function. this.formatOptions = {}; this.parseOptions = {}; + + // standalone is a flag indicating that the field does not have a + // controller to delegate actions to + this.standalone = options.standalone; }, /** * When a field widget is appended to the DOM, its start method is called, diff --git a/addons/web/static/src/js/fields/field_utils.js b/addons/web/static/src/js/fields/field_utils.js index ac4c06454ea..0a44a52e315 100644 --- a/addons/web/static/src/js/fields/field_utils.js +++ b/addons/web/static/src/js/fields/field_utils.js @@ -29,12 +29,20 @@ var _t = core._t; /** * @todo Really? it returns a jqueryElement... We should try to move this to a * module with dom helpers functions, such as web.dom, maybe. And replace this - * with a function that returns a string + * with a function that returns a string so we can get rid of the forceString. * * @param {boolean} value - * @returns {jQueryElement} + * @param {Object} [field] + * a description of the field (note: this parameter is ignored) + * @param {Object} [options] additional options + * @param {boolean} [options.forceString=false] if true, returns a string +* representation of the boolean rather than a jQueryElement + * @returns {jQueryElement | string} */ -function formatBoolean(value) { +function formatBoolean(value, field, options) { + if (options && options.forceString) { + return value ? _t('True') : _t('False'); + } var $input = $('<input/>', { type: 'checkbox', }).prop({ diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js index 14512c02912..7522bb3bc37 100644 --- a/addons/web/static/src/js/fields/relational_fields.js +++ b/addons/web/static/src/js/fields/relational_fields.js @@ -223,21 +223,25 @@ var FieldMany2One = AbstractField.extend({ _quickCreate: function (name) { var self = this; var slowCreate = this._searchCreatePopup.bind(this, "form", false, this._createContext(name)); + var action = function () { + return self._rpc({ + model: self.field.relation, + method: 'name_create', + args: [name], + context: self.record.getContext(self.recordParams), + }).then(function (result) { + if (self.mode === "edit") { + self.reinitialize({id: result[0], display_name: result[1]}); + } + }, slowCreate); + }; + if (this.nodeOptions.quick_create) { - this.trigger_up('mutexify', { - action: function () { - return self._rpc({ - model: self.field.relation, - method: 'name_create', - args: [name], - context: self.record.getContext(self.recordParams), - }).then(function (result) { - if (self.mode === "edit") { - self.reinitialize({id: result[0], display_name: result[1]}); - } - }, slowCreate); - }, - }); + if (this.standalone) { + action(); + } else { + this.trigger_up('mutexify', {action: action}); + } } else { slowCreate(); } diff --git a/addons/web/static/src/js/views/abstract_model.js b/addons/web/static/src/js/views/abstract_model.js index 27d13a12b06..4ea6b217e65 100644 --- a/addons/web/static/src/js/views/abstract_model.js +++ b/addons/web/static/src/js/views/abstract_model.js @@ -15,6 +15,7 @@ odoo.define('web.AbstractModel', function (require) { */ var Class = require('web.Class'); +var fieldUtils = require('web.field_utils'); var mixins = require('web.mixins'); var ServicesMixin = require('web.ServicesMixin'); @@ -63,6 +64,30 @@ var AbstractModel = Class.extend(mixins.EventDispatcherMixin, ServicesMixin, { reload: function (params) { return $.when(); }, + /** + * Processes date(time) and selection field values sent by the server. + * Converts data(time) values to moment instances. + * Converts false values of selection fields to 0 if 0 is a valid key, + * because the server doesn't make a distinction between false and 0, and + * always sends false when value is 0. + * + * @param {Object} field the field description + * @param {*} value + * @returns {*} the processed value + */ + _parseServerValue: function (field, value) { + if (field.type === 'date' || field.type === 'datetime') { + // process date(time): convert into a moment instance + value = fieldUtils.parse[field.type](value, field, {isUTC: true}); + } else if (field.type === 'selection' && value === false) { + // process selection: convert false to 0, if 0 is a valid key + var hasKey0 = _.find(field.selection, function (option) { + return option[0] === 0; + }); + value = hasKey0 ? 0 : value; + } + return value; + }, }); return AbstractModel; 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 5fb77204ec7..75a0ad02971 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -84,10 +84,9 @@ odoo.define('web.BasicModel', function (require) { var AbstractModel = require('web.AbstractModel'); var concurrency = require('web.concurrency'); -var core = require('web.core'); var Context = require('web.Context'); +var core = require('web.core'); var Domain = require('web.Domain'); -var fieldUtils = require('web.field_utils'); var session = require('web.session'); var _t = core._t; @@ -2370,8 +2369,8 @@ var BasicModel = AbstractModel.extend({ * if given, this field's context is added to the context, instead of * the element's context (except if options.full is true) * @param {boolean} [options.full=false] - * if true and fieldName given in options, the element's context - * is added to the context + * if true or nor fieldName or additionalContext given in options, + * the element's context is added to the context * @returns {Object} the evaluated context */ _getContext: function (element, options) { @@ -2379,7 +2378,7 @@ var BasicModel = AbstractModel.extend({ var context = new Context(session.user_context); context.set_eval_context(this._getEvalContext(element)); - if (options.full || !options.fieldName) { + if (options.full || !(options.fieldName || options.additionalContext)) { context.add(element.context); } if (options.fieldName) { @@ -2656,8 +2655,14 @@ var BasicModel = AbstractModel.extend({ var type = params.type || ('domain' in params && 'list') || 'record'; var res_id, value; var res_ids = params.res_ids || []; + var data = params.data || (type === 'record' ? {} : []); if (type === 'record') { - res_id = params.res_id || (params.data && params.data.id) || _.uniqueId('virtual_'); + res_id = params.res_id || (params.data && params.data.id); + if (res_id) { + data.id = res_id; + } else { + res_id = _.uniqueId('virtual_'); + } } else { var isValueArray = params.value instanceof Array; res_id = isValueArray ? params.value[0] : undefined; @@ -2675,7 +2680,7 @@ var BasicModel = AbstractModel.extend({ aggregateValues: params.aggregateValues || {}, context: params.context || {}, count: params.count || res_ids.length, - data: params.data || (type === 'record' ? {} : []), + data: data, domain: params.domain || [], fields: fields, fieldsInfo: params.fieldsInfo, @@ -2941,30 +2946,6 @@ var BasicModel = AbstractModel.extend({ }); }, /** - * Processes date(time) and selection field values sent by the server. - * Converts data(time) values to moment instances. - * Converts false values of selection fields to 0 if 0 is a valid key, - * because the server doesn't make a distinction between false and 0, and - * always sends false when value is 0. - * - * @param {Object} field the field description - * @param {*} value - * @returns {*} the processed value - */ - _parseServerValue: function (field, value) { - if (field.type === 'date' || field.type === 'datetime') { - // process date(time): convert into a moment instance - value = fieldUtils.parse[field.type](value, field, {isUTC: true}); - } else if (field.type === 'selection' && value === false) { - // process selection: convert false to 0, if 0 is a valid key - var hasKey0 = _.find(field.selection, function (option) { - return option[0] === 0; - }); - value = hasKey0 ? 0 : value; - } - return value; - }, - /** * This method is quite important: it is supposed to perform the /onchange * rpc and apply the result. * @@ -3087,7 +3068,8 @@ var BasicModel = AbstractModel.extend({ lazy: true, }) .then(function (groups) { - var rawGroupBy = list.groupedBy[0].split(':')[0]; + var groupByField = list.groupedBy[0]; + var rawGroupBy = groupByField.split(':')[0]; var previousGroups = _.map(list.data, function (groupID) { return self.localData[groupID]; }); @@ -3098,13 +3080,13 @@ var BasicModel = AbstractModel.extend({ _.each(groups, function (group) { var aggregateValues = {}; _.each(group, function (value, key) { - if (_.contains(fields, key) && key !== list.groupedBy[0]) { + if (_.contains(fields, key) && key !== groupByField) { aggregateValues[key] = value; } }); // When a view is grouped, we need to display the name of each group in // the 'title'. - var value = group[rawGroupBy]; + var value = group[groupByField]; if (list.fields[rawGroupBy].type === "selection") { var choice = _.find(list.fields[rawGroupBy].selection, function (c) { return c[0] === value; diff --git a/addons/web/static/src/js/views/calendar/calendar_model.js b/addons/web/static/src/js/views/calendar/calendar_model.js index ded7938605f..73868a93fdb 100644 --- a/addons/web/static/src/js/views/calendar/calendar_model.js +++ b/addons/web/static/src/js/views/calendar/calendar_model.js @@ -406,12 +406,13 @@ return AbstractModel.extend({ }); } return eventsDef.then(function (events) { - self.data.data = _.map(events, self._recordToCalendarEvent.bind(self)); - return $.when( - self._loadColors(self.data, self.data.data), - self._loadRecordsToFilters(self.data, self.data.data) - ); - }); + self._parseServerData(events); + self.data.data = _.map(events, self._recordToCalendarEvent.bind(self)); + return $.when( + self._loadColors(self.data, self.data.data), + self._loadRecordsToFilters(self.data, self.data.data) + ); + }); }); }, /** @@ -566,12 +567,25 @@ return AbstractModel.extend({ if (filter.filters.length && (filter.filters[0].avatar_model in to_read)) { _.each(filter.filters, function (f) { f.label = to_read[f.avatar_model][f.value]; - }); + }); } }); }); }, /** + * parse the server values to javascript framwork + * + * @param {Object} data the server data to parse + */ + _parseServerData: function (data) { + var self = this; + _.each(data, function(event) { + _.each(self.fieldNames, function (fieldName) { + event[fieldName] = self._parseServerValue(self.fields[fieldName], event[fieldName]); + }); + }); + }, + /** * Transform OpenERP event object to fullcalendar event object */ _recordToCalendarEvent: function (evt) { @@ -583,11 +597,11 @@ return AbstractModel.extend({ attendees = []; if (!all_day) { - date_start = fieldUtils.parse.datetime(evt[this.mapping.date_start]); - date_stop = this.mapping.date_stop ? fieldUtils.parse.datetime(evt[this.mapping.date_stop]) : null; + date_start = evt[this.mapping.date_start].clone(); + date_stop = this.mapping.date_stop ? evt[this.mapping.date_stop].clone() : null; } else { - date_start = fieldUtils.parse.datetime(evt[this.mapping.date_start].split(' ')[0]); - date_stop = this.mapping.date_stop ? fieldUtils.parse.datetime(evt[this.mapping.date_stop].split(' ')[0]) : null; + date_start = evt[this.mapping.date_start].clone().startOf('day'); + date_stop = this.mapping.date_stop ? evt[this.mapping.date_stop].clone().startOf('day') : null; } if (!date_stop && date_delay) { @@ -605,8 +619,8 @@ return AbstractModel.extend({ 'record': evt, 'start': date_start, 'end': date_stop, - 'r_start':date_start.clone(), - 'r_end':date_stop.clone(), + 'r_start': date_start, + 'r_end': date_stop, 'title': the_title, 'allDay': this.mapping.all_day && evt[this.mapping.all_day] || false, 'id': evt.id, diff --git a/addons/web/static/src/js/views/calendar/calendar_renderer.js b/addons/web/static/src/js/views/calendar/calendar_renderer.js index a4930c87d37..b61236131c6 100644 --- a/addons/web/static/src/js/views/calendar/calendar_renderer.js +++ b/addons/web/static/src/js/views/calendar/calendar_renderer.js @@ -288,7 +288,7 @@ return AbstractRenderer.extend({ if (field.type === "one2many" || field.type === "many2many") { return field_utils.format[field.type]({data: record[fieldName]}, field); } else { - return field_utils.format[field.type](record[fieldName], field); + return field_utils.format[field.type](record[fieldName], field, {forceString: true}); } }, /** diff --git a/addons/web/static/src/js/views/graph/graph_model.js b/addons/web/static/src/js/views/graph/graph_model.js index 5948e07a54f..8a7f5da6d5d 100644 --- a/addons/web/static/src/js/views/graph/graph_model.js +++ b/addons/web/static/src/js/views/graph/graph_model.js @@ -60,12 +60,13 @@ return AbstractModel.extend({ this.modelName = params.modelName; this.chart = { data: [], - groupedBy: params.groupBys, + groupedBy: params.groupedBy.length ? params.groupedBy : params.groupBys, measure: params.measure, mode: params.mode, domain: params.domain, context: params.context, }; + this.defaultGroupedBy = params.groupedBy; return this._loadGraph(); }, /** @@ -88,11 +89,7 @@ return AbstractModel.extend({ this.chart.domain = params.domain; } if ('groupBy' in params) { - if (!params.groupBy.length) { - this.chart.groupedBy = this.initialGroupBys; - } else { - this.chart.groupedBy = params.groupBy; - } + this.chart.groupedBy = params.groupBy.length ? params.groupBy : this.defaultGroupedBy; } if ('measure' in params) { this.chart.measure = params.measure; diff --git a/addons/web/static/src/js/views/kanban/kanban_controller.js b/addons/web/static/src/js/views/kanban/kanban_controller.js index b5c60fe9ab3..2592e3d96ec 100644 --- a/addons/web/static/src/js/views/kanban/kanban_controller.js +++ b/addons/web/static/src/js/views/kanban/kanban_controller.js @@ -164,9 +164,10 @@ var KanbanController = BasicController.extend({ _onAddColumn: function (event) { var self = this; this.model.createGroup(event.data.value, this.handle).then(function () { - self.update({}, {reload: false}); + return self.update({}, {reload: false}); + }).then(function () { self._updateButtons(); - self.trigger_up('scrollTo', {selector: '.o_column_quick_create'}); + self.renderer.quickCreateToggleFold(); }); }, /** diff --git a/addons/web/static/src/js/views/kanban/kanban_quick_create.js b/addons/web/static/src/js/views/kanban/kanban_quick_create.js index 5e40352b26d..e81a290bf2d 100644 --- a/addons/web/static/src/js/views/kanban/kanban_quick_create.js +++ b/addons/web/static/src/js/views/kanban/kanban_quick_create.js @@ -192,7 +192,7 @@ var RecordQuickCreate = AbstractQuickCreate.extend({ var ColumnQuickCreate = AbstractQuickCreate.extend({ template: 'KanbanView.ColumnQuickCreate', events: _.extend({}, AbstractQuickCreate.prototype.events, { - 'click': '_onClick', + 'click': 'toggleFold', 'click input': '_onInputClicked', 'focusout': '_onFocusout', }), @@ -214,6 +214,22 @@ var ColumnQuickCreate = AbstractQuickCreate.extend({ }, //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * Toggle fold/unfold the Column quick create widget + */ + toggleFold: function () { + this.folded = !this.folded; + this._update(); + if (!this.folded) { + this.$input.focus(); + this.trigger_up('scrollTo', {selector: '.o_column_quick_create'}); + } + }, + + //-------------------------------------------------------------------------- // Private //-------------------------------------------------------------------------- @@ -243,18 +259,6 @@ var ColumnQuickCreate = AbstractQuickCreate.extend({ /** * @private - * @param {MouseEvent} event - */ - _onClick: function () { - this.folded = !this.folded; - this._update(); - if (!this.folded) { - this.$input.focus(); - this.trigger_up('scrollTo', {selector: '.o_column_quick_create'}); - } - }, - /** - * @private */ _onFocusout: function () { var hasFocus = this.$(':focus').length > 0; diff --git a/addons/web/static/src/js/views/kanban/kanban_renderer.js b/addons/web/static/src/js/views/kanban/kanban_renderer.js index 87275260b01..ea2f0270017 100644 --- a/addons/web/static/src/js/views/kanban/kanban_renderer.js +++ b/addons/web/static/src/js/views/kanban/kanban_renderer.js @@ -113,6 +113,12 @@ var KanbanRenderer = BasicRenderer.extend({ this.widgets[0].addQuickCreate(); }, /** + * Toggle fold/unfold the Column quick create widget + */ + quickCreateToggleFold: function () { + this.quickCreate.toggleFold(); + }, + /** * Removes a widget (record if ungrouped, column if grouped) from the view. * * @param {Widget} widget the instance of the widget to remove @@ -244,8 +250,8 @@ var KanbanRenderer = BasicRenderer.extend({ // Enable column quickcreate if (this.columnOptions.group_creatable) { - var quickCreate = new ColumnQuickCreate(this); - quickCreate.appendTo(fragment); + this.quickCreate = new ColumnQuickCreate(this); + this.quickCreate.appendTo(fragment); } } diff --git a/addons/web/static/src/js/views/list/list_renderer.js b/addons/web/static/src/js/views/list/list_renderer.js index a917781b580..802f2f03a09 100644 --- a/addons/web/static/src/js/views/list/list_renderer.js +++ b/addons/web/static/src/js/views/list/list_renderer.js @@ -377,6 +377,10 @@ var ListRenderer = BasicRenderer.extend({ $cells.unshift($('<td>')); } var name = group.value === undefined ? _t('Undefined') : group.value; + var groupBy = this.state.groupedBy[groupLevel]; + if (group.fields[groupBy.split(':')[0]].type !== 'boolean') { + name = name || _t('Undefined'); + } var $th = $('<th>') .addClass('o_group_name') .text(name + ' (' + group.count + ')'); diff --git a/addons/web/static/src/js/views/pivot/pivot_model.js b/addons/web/static/src/js/views/pivot/pivot_model.js index 49c00663d47..d74af7fee22 100644 --- a/addons/web/static/src/js/views/pivot/pivot_model.js +++ b/addons/web/static/src/js/views/pivot/pivot_model.js @@ -229,11 +229,12 @@ var PivotModel = AbstractModel.extend({ this.data = { domain: params.domain, context: _.extend({}, session.user_context, params.context), - groupedBy: params.rowGroupBys, + groupedBy: params.groupedBy, colGroupBys: params.colGroupBys, measures: params.measures, sorted_column: {}, }; + this.defaultGroupedBy = params.groupedBy; return this._loadData(); }, /** @@ -250,7 +251,7 @@ var PivotModel = AbstractModel.extend({ this.data.domain = this.initialDomain; } if ('groupBy' in params) { - this.data.groupedBy = params.groupBy; + this.data.groupedBy = params.groupBy.length ? params.groupBy : this.defaultGroupedBy; } if (!this.data.has_data) { return this._loadData(); diff --git a/addons/web/static/src/less/list_view.less b/addons/web/static/src/less/list_view.less index a09b8e7ac0c..edb4c184408 100644 --- a/addons/web/static/src/less/list_view.less +++ b/addons/web/static/src/less/list_view.less @@ -127,7 +127,7 @@ } } > .o_field_translate { - position: absolute; + .o-position-absolute(0, 5px); width: 16px; // to have some space on the right } .o_input { diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js index aacbb744252..1a7badd353f 100644 --- a/addons/web/static/tests/fields/relational_fields_tests.js +++ b/addons/web/static/tests/fields/relational_fields_tests.js @@ -1,11 +1,13 @@ odoo.define('web.relational_fields_tests', function (require) { "use strict"; +var BasicModel = require('web.BasicModel'); var concurrency = require('web.concurrency'); var FormView = require('web.FormView'); var ListView = require('web.ListView'); var relationalFields = require('web.relational_fields'); var testUtils = require('web.test_utils'); +var Widget = require('web.Widget'); var createView = testUtils.createView; @@ -361,7 +363,6 @@ QUnit.module('relational_fields', { form.destroy(); }); - QUnit.test('many2one readonly fields with option "no_open"', function (assert) { assert.expect(1); @@ -564,10 +565,71 @@ QUnit.module('relational_fields', { form.destroy(); }); + QUnit.test('many2one field with option standalone', function (assert) { + assert.expect(3); + var done = assert.async(); + + var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; + + var fixture = $('#qunit-fixture'); + var self = this; + + var model = testUtils.createModel({ + Model: BasicModel, + data: this.data, + }); + + model.makeRecord('coucou', [{ + name: 'partner_id', + relation: 'partner', + type: 'many2one', + value: [1, 'first partner'], + }], { + partner_id: { + options: { + no_open: true, + }, + }, + }).then(function (recordID) { + var record = model.get(recordID); + var parent = new Widget(); + testUtils.addMockEnvironment(parent, { + data: self.data, + mockRPC: function (route, args) { + assert.step(args.method); + return this._super.apply(this, arguments); + }, + }); + + var relField = new relationalFields.FieldMany2One(parent, + 'partner_id', + record, + { + mode: 'edit', + standalone: true, + }); + + relField.appendTo(fixture); + $('input.o_input').val('xyzzrot').trigger('input'); + + concurrency.delay(0).then(function () { + var $dropdown = $('input.o_input').autocomplete('widget'); + $dropdown.find('.o_m2o_dropdown_option:contains(Create)') + .first().mouseenter().click(); + assert.verifySteps(['name_search', 'name_create']); + parent.destroy(); + model.destroy(); + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; + done(); + }); + }); + }); + // QUnit.test('onchange on a many2one to a different model', function (assert) { - // This test is commented because the mock server does not give the correct response. - // It should return a couple [id, display_name], but I don't know the logic used - // by the server, so it's hard to emulate it correctly + // This test is commented because the mock server does not give the correct response. + // It should return a couple [id, display_name], but I don't know the logic used + // by the server, so it's hard to emulate it correctly // assert.expect(2); // this.data.partner.records[0].product_id = 41; @@ -995,14 +1057,16 @@ QUnit.module('relational_fields', { assert.deepEqual( args.kwargs.context, {hey: "ho", hello: "world", test: "yop"}, - 'the field attr context should have been used for the ' - + 'RPC (evaluated and merged with the session one)'); + 'the field attr context should have been used for the ' + + 'RPC (evaluated and merged with the session one)'); + return $.when([]); } if (args.method === 'name_search' && args.model === 'partner') { assert.deepEqual(args.kwargs.args, [['id', 'in', [12]]], 'the field attr domain should have been used for the RPC (and evaluated)'); assert.deepEqual(args.kwargs.context, {hey: 'ho', timmy: [[6, false, [12]]]}, 'the field attr context should have been used for the RPC (and evaluated)'); + return $.when([]); } return this._super.apply(this, arguments); }, @@ -3652,6 +3716,34 @@ QUnit.module('relational_fields', { form.destroy(); }); + QUnit.test('id in one2many obtained in onchange is properly set', function (assert) { + assert.expect(1); + + this.data.partner.onchanges.turtles = function (obj) { + obj.turtles = [ + [5], + [1, 3, {turtle_foo: "kawa"}] + ]; + }; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '<form string="Partners">' + + '<field name="turtles">' + + '<tree>' + + '<field name="id"/>' + + '<field name="turtle_foo"/>' + + '</tree>' + + '</field>' + + '</form>', + }); + + assert.strictEqual(form.$('tr.o_data_row').text(), '3kawa', + "should have properly displayed id and foo field"); + form.destroy(); + }); + QUnit.test('sub form view with a required field', function (assert) { assert.expect(2); this.data.partner.fields.foo.required = true; diff --git a/addons/web/static/tests/helpers/mock_server.js b/addons/web/static/tests/helpers/mock_server.js index cab291ec012..06b85dd8f9e 100644 --- a/addons/web/static/tests/helpers/mock_server.js +++ b/addons/web/static/tests/helpers/mock_server.js @@ -465,25 +465,7 @@ var MockServer = Class.extend({ _mockNameSearch: function (model, args, _kwargs) { var str = args && typeof args[0] === 'string' ? args[0] : _kwargs.name; var domain = (args && args[1]) || _kwargs.args || []; - var dom = domain[0]; - var records = this.data[model].records; - if (dom) { - records = _.filter(records, function (record) { - var value = record[dom[0]]; - if (value instanceof Array) { - value = value[0]; - } - if (dom[1] === 'not in') { - return !_.contains(dom[2], value); - } else if (dom[1] === 'in') { - return _.contains(dom[2], value); - } else if (dom[1] === '==') { - return dom[2] == value; - } else if (dom[1] === '!=') { - return dom[2] != value; - } - }); - } + var records = this._getRecords(model, domain); if (str.length) { records = _.filter(records, function (record) { return record.display_name.indexOf(str) !== -1; @@ -632,7 +614,9 @@ var MockServer = Class.extend({ var fieldName = groupByField.split(':')[0]; var aggregateFunction = groupByField.split(':')[1] || 'month'; if (fields[fieldName].type === 'date') { - if (aggregateFunction === 'day') { + if (!val) { + return false; + } else if (aggregateFunction === 'day') { return moment(val).format('YYYY-MM-DD'); } else { return moment(val).format('MMMM YYYY'); diff --git a/addons/web/static/tests/views/calendar_tests.js b/addons/web/static/tests/views/calendar_tests.js index 011843eefee..dfc42c6f746 100644 --- a/addons/web/static/tests/views/calendar_tests.js +++ b/addons/web/static/tests/views/calendar_tests.js @@ -3,6 +3,7 @@ odoo.define('web.calendar_tests', function (require) { var CalendarView = require('web.CalendarView'); var CalendarRenderer = require('web.CalendarRenderer'); +var fieldUtils = require('web.field_utils'); var testUtils = require('web.test_utils'); var session = require('web.session'); @@ -431,6 +432,8 @@ QUnit.module('Views', { 'mode="week" '+ 'readonly_form_view_id="1">'+ '<field name="name"/>'+ + '<field name="start"/>'+ + '<field name="allday"/>'+ '</calendar>', archs: archs, viewOptions: { @@ -478,14 +481,14 @@ QUnit.module('Views', { $('.modal button.btn:contains(Create)').trigger('click'); var $newevent = calendar.$('.fc-event:contains(new event)'); - assert.strictEqual($newevent.text().replace(/[\s\n\r]+/g, ''), "08:00-10:00newevent", - "should display the new event with time and title"); + assert.strictEqual($newevent.text().replace(/[\s\n\r]+/g, ''), "08:00-10:00newevent12/13/201608:00:00False", + "should display the new event with time, title and additional fields"); assert.deepEqual($newevent.data('fcSeg').event.record, { display_name: "new event", - start: "2016-12-13 06:00:00", - stop: "2016-12-13 08:00:00", + start: fieldUtils.parse.datetime("2016-12-13 06:00:00", this.data.event.fields.start, {isUTC: true}), + stop: fieldUtils.parse.datetime("2016-12-13 08:00:00", this.data.event.fields.stop, {isUTC: true}), allday: false, name: "new event", id: 1 @@ -616,8 +619,8 @@ QUnit.module('Views', { assert.deepEqual($newevent.data('fcSeg').event.record, { display_name: "new event", - start: "2016-12-13 06:00:00", - stop: "2016-12-13 08:00:00", + start: fieldUtils.parse.datetime("2016-12-13 06:00:00", this.data.event.fields.start, {isUTC: true}), + stop: fieldUtils.parse.datetime("2016-12-13 08:00:00", this.data.event.fields.stop, {isUTC: true}), allday: false, name: "new event", id: 1 @@ -691,8 +694,8 @@ QUnit.module('Views', { assert.deepEqual($newevent.data('fcSeg').event.record, { display_name: "new event", - start: "2016-12-14 00:00:00", - stop: "2016-12-15 00:00:00", + start: fieldUtils.parse.datetime("2016-12-14 00:00:00", this.data.event.fields.start, {isUTC: true}), + stop: fieldUtils.parse.datetime("2016-12-15 00:00:00", this.data.event.fields.stop, {isUTC: true}), allday: true, name: "new event", id: 1 diff --git a/addons/web/static/tests/views/form_tests.js b/addons/web/static/tests/views/form_tests.js index 09ddcc20d46..183e5958b62 100644 --- a/addons/web/static/tests/views/form_tests.js +++ b/addons/web/static/tests/views/form_tests.js @@ -3555,10 +3555,13 @@ QUnit.module('Views', { '</sheet>' + '</form>', res_id: 2, + viewOptions: { + context: {some_context: true}, + }, intercepts: { execute_action: function (e) { assert.deepEqual(e.data.action_data.context, {test: 2}, - "button context should have been evaluated and given to the action"); + "button context should have been evaluated and given to the action, without previous context"); }, }, }); diff --git a/addons/web/static/tests/views/graph_tests.js b/addons/web/static/tests/views/graph_tests.js index 7a7d22d96d6..33b2fd8ffbd 100644 --- a/addons/web/static/tests/views/graph_tests.js +++ b/addons/web/static/tests/views/graph_tests.js @@ -344,6 +344,34 @@ QUnit.module('Views', { done(); }); }); + + QUnit.test('correctly use group_by key from the context', function (assert) { + var done = assert.async(); + assert.expect(2); + + var graph = createView({ + View: GraphView, + model: 'foo', + data: this.data, + arch: '<graph><field name="product_id" /></graph>', + groupBy: ['color_id'], + viewOptions: { + context: { + graph_measure: 'foo', + graph_mode: 'line', + }, + }, + }); + return concurrency.delay(0).then(function () { + assert.strictEqual(graph.$('text:contains(xphone)').length, 0, + 'should not contain a text element with product in legend'); + assert.strictEqual(graph.$('text:contains(red)').length, 1, + 'should contain a text element with color in legend'); + graph.destroy(); + done(); + }); + }); + }); }); diff --git a/addons/web/static/tests/views/kanban_tests.js b/addons/web/static/tests/views/kanban_tests.js index 89ec104fc41..dd0a52868f6 100644 --- a/addons/web/static/tests/views/kanban_tests.js +++ b/addons/web/static/tests/views/kanban_tests.js @@ -1626,6 +1626,35 @@ QUnit.module('Views', { "column should contain 1 records"); kanban.destroy(); }); + + QUnit.test('don\'t fold column quick create after creation', function (assert) { + assert.expect(2); + + var kanban = createView({ + View: KanbanView, + model: 'partner', + data: this.data, + arch: '<kanban on_create="quick_create">' + + '<field name="product_id"/>' + + '<templates><t t-name="kanban-box">' + + '<div><field name="foo"/></div>' + + '</t></templates>' + + '</kanban>', + groupBy: ['product_id'], + }); + + // add a new column + kanban.$('.o_kanban_group:first .o_kanban_quick_add').click(); + var $quickCreate = kanban.$('.o_kanban_quick_create'); + $quickCreate.find('input').val('new partner'); + $quickCreate.find('button.o_kanban_add').click(); + assert.strictEqual(this.data.partner.records.length, 5, + "should have created a 'new partner' column"); + + assert.strictEqual(kanban.$('.o_kanban_add:visible').length, 1, + "the add button should still be visible"); + kanban.destroy(); + }); }); }); diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js index 09109d92b84..e3d8acfe84a 100644 --- a/addons/web/static/tests/views/list_tests.js +++ b/addons/web/static/tests/views/list_tests.js @@ -2466,6 +2466,23 @@ QUnit.module('Views', { list.destroy(); }); + + QUnit.test('list grouped by date:month', function (assert) { + assert.expect(1); + + var list = createView({ + View: ListView, + model: 'foo', + data: this.data, + arch: '<tree><field name="date"/></tree>', + groupBy: ['date:month'], + }); + + assert.strictEqual(list.$('tbody').text(), "January 2017 (1)Undefined (3)", + "the group names should be correct"); + + list.destroy(); + }); }); }); diff --git a/addons/web/static/tests/views/pivot_tests.js b/addons/web/static/tests/views/pivot_tests.js index c3acdb52399..267437845b0 100644 --- a/addons/web/static/tests/views/pivot_tests.js +++ b/addons/web/static/tests/views/pivot_tests.js @@ -825,4 +825,38 @@ QUnit.module('Views', { pivot.destroy(); }); + QUnit.test('correctly use group_by key from the context', function (assert) { + assert.expect(7); + + var pivot = createView({ + View: PivotView, + model: 'partner', + data: this.data, + arch: '<pivot>' + + '<field name="customer" type="col" />' + + '<field name="foo" type="measure" />' + + '</pivot>', + groupBy: ['product_id'], + }); + + assert.strictEqual(pivot.$('thead .o_pivot_header_cell_opened').length, 1, + 'column: should have one opened header'); + assert.strictEqual(pivot.$('thead .o_pivot_header_cell_closed:contains(First)').length, 1, + 'column: should display one closed header with "First"'); + assert.strictEqual(pivot.$('thead .o_pivot_header_cell_closed:contains(Second)').length, 1, + 'column: should display one closed header with "Second"'); + + assert.strictEqual(pivot.$('tbody .o_pivot_header_cell_opened').length, 1, + 'row: should have one opened header'); + assert.strictEqual(pivot.$('tbody .o_pivot_header_cell_closed:contains(xphone)').length, 1, + 'row: should display one closed header with "xphone"'); + assert.strictEqual(pivot.$('tbody .o_pivot_header_cell_closed:contains(xpad)').length, 1, + 'row: should display one closed header with "xpad"'); + + assert.strictEqual(pivot.$('tbody tr:first td:nth(3)').text(), '32', + 'selected measure should be foo, with total 32'); + + pivot.destroy(); + }); + });}); |
