diff options
| author | Christophe Simonis <chs@odoo.com> | 2018-04-16 19:50:50 +0200 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2018-04-16 19:50:50 +0200 |
| commit | 74033eb007749c88b84ea883b006a5877355306e (patch) | |
| tree | 8f8b6cb22cc2bf6046ba04e388ea29855a33c597 /addons/web/static/src/js/views | |
| parent | 71cdefd9f41881d9720433d5fc7732759098f9a4 (diff) | |
| parent | 90f8f17fb4fc795c2c12d68d21900e81f3d7a223 (diff) | |
[MERGE] forward port branch saas-11.2 up to 90f8f17fb4f
Diffstat (limited to 'addons/web/static/src/js/views')
7 files changed, 129 insertions, 37 deletions
diff --git a/addons/web/static/src/js/views/basic/basic_controller.js b/addons/web/static/src/js/views/basic/basic_controller.js index 8a9b3713f15..b4d00b6bb76 100644 --- a/addons/web/static/src/js/views/basic/basic_controller.js +++ b/addons/web/static/src/js/views/basic/basic_controller.js @@ -314,7 +314,7 @@ var BasicController = AbstractController.extend(FieldManagerMixin, { * * @private */ - _disableButtons: function () { + _disableButtons: function () { if (this.$buttons) { this.$buttons.find('button').attr('disabled', true); } @@ -346,10 +346,8 @@ var BasicController = AbstractController.extend(FieldManagerMixin, { return; } self.model.discardChanges(recordID); - if (self.model.isNew(recordID)) { - if (self.model.canBeAbandoned(recordID)) { - self._abandonRecord(recordID); - } + if (self.model.canBeAbandoned(recordID)) { + self._abandonRecord(recordID); return; } return self._confirmSave(recordID); @@ -360,7 +358,7 @@ var BasicController = AbstractController.extend(FieldManagerMixin, { * * @private */ - _enableButtons: function () { + _enableButtons: function () { if (this.$buttons) { this.$buttons.find('button').removeAttr('disabled'); } 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 4cf6f9fa27b..962bb2899e1 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -386,11 +386,12 @@ var BasicModel = AbstractModel.extend({ duplicateRecord: function (recordID) { var self = this; var record = this.localData[recordID]; + var context = this._getContext(record); return this._rpc({ model: record.model, method: 'copy', args: [record.data.id], - context: this._getContext(record), + context: context, }) .then(function (res_id) { var index = record.res_ids.indexOf(record.res_id); @@ -402,6 +403,7 @@ var BasicModel = AbstractModel.extend({ res_id: res_id, res_ids: record.res_ids.slice(0), viewType: record.viewType, + context: context, }); }); }, @@ -578,10 +580,15 @@ var BasicModel = AbstractModel.extend({ return _t("New"); }, /** - * Returns true if a record can be abandoned from a list datapoint. + * Returns true if a record can be abandoned. + * + * A record can be abandonned if it is a new record, except if + * this datapoint has been specifically tagged as "do not abandon". + * + * Example: * - * A record cannot be abandonned if it has been registered as "added" - * in the parent's savepoint, otherwise it can be abandonned. + * - Discard record from "Add an item" => "New" record => abandon + * - Discard record from `default_get`/`onchange` => do not abandon * * This is useful when discarding changes on this record, as it means that * we must keep the record even if some fields are invalids (e.g. required @@ -591,15 +598,7 @@ var BasicModel = AbstractModel.extend({ * @returns {boolean} */ canBeAbandoned: function (id) { - var data = this.localData[id]; - var parent = this.localData[data.parentID]; - var abandonable = true; - if (parent) { - abandonable = !_.some(parent._savePoint, function (entry) { - return entry.operation === 'ADD' && entry.id === id; - }); - } - return abandonable; + return !this.localData[id]._noAbandon && this.isNew(id); }, /** * Returns true if a record is dirty. A record is considered dirty if it has @@ -960,11 +959,11 @@ var BasicModel = AbstractModel.extend({ * @returns {Deferred} * Resolved with the list of field names (whose value has been modified) */ - save: function (record_id, options) { + save: function (recordID, options) { var self = this; return this.mutex.exec(function () { options = options || {}; - var record = self.localData[record_id]; + var record = self.localData[recordID]; if (options.savePoint) { self._visitChildren(record, function (rec) { var newValue = rec._changes || rec.data; @@ -982,7 +981,7 @@ var BasicModel = AbstractModel.extend({ }); } var shouldReload = 'reload' in options ? options.reload : true; - var method = self.isNew(record_id) ? 'create' : 'write'; + var method = self.isNew(recordID) ? 'create' : 'write'; if (record._changes) { // id never changes, and should not be written delete record._changes.id; @@ -1553,6 +1552,7 @@ var BasicModel = AbstractModel.extend({ list._cache[rec.res_id] = rec.id; } + rec._noAbandon = true; list._changes.push({operation: 'ADD', id: rec.id}); if (command[0] === 1) { list._changes.push({operation: 'UPDATE', id: rec.id}); @@ -2029,7 +2029,7 @@ var BasicModel = AbstractModel.extend({ .then(function () { return $.when( self._fetchX2Manys(record, options), - self._fetchReferences(record) + self._fetchReferences(record, options) ).then(function () { return self._postprocess(record, options); }); @@ -2076,10 +2076,10 @@ var BasicModel = AbstractModel.extend({ * @param {Object} record * @returns {Deferred} */ - _fetchReferences: function (record) { + _fetchReferences: function (record, options) { var self = this; var defs = []; - var fieldNames = record.getFieldNames(); + var fieldNames = options && options.fieldNames || record.getFieldNames(); _.each(fieldNames, function (fieldName) { var field = record.fields[fieldName]; if (field.type === 'reference') { @@ -3199,6 +3199,9 @@ var BasicModel = AbstractModel.extend({ * @param {Object} [options] * @param {string[]} [options.fieldNames] the fields to fetch for a record * @param {boolean} [options.onlyGroups=false] + * @param {boolean} [options.keepEmptyGroups=false] if set, the groups not + * present in the read_group anymore (empty groups) will stay in the + * datapoint (used to mimic the kanban renderer behaviour for example) * @returns {Deferred} */ _load: function (dataPoint, options) { @@ -3351,9 +3354,29 @@ var BasicModel = AbstractModel.extend({ */ _makeDefaultRecord: function (modelName, params) { var self = this; + + var determineExtraFields = function() { + // Fields that are present in the originating view, that need to be initialized + // Hence preventing their value to crash when getting back to the originating view + var parentRecord = self.localData[params.parentID]; + + var originView = parentRecord && parentRecord.fieldsInfo; + if (!originView || !originView[parentRecord.viewType]) + return []; + + var fieldsFromOrigin = _.filter(Object.keys(originView[parentRecord.viewType]), + function(fieldname) { + return params.fields[fieldname] !== undefined; + }); + + return fieldsFromOrigin; + } + var fieldNames = Object.keys(params.fieldsInfo[params.viewType]); var fields_key = _.without(fieldNames, '__last_update'); + var extraFields = determineExtraFields(); + return this._rpc({ model: modelName, method: 'default_get', @@ -3371,7 +3394,7 @@ var BasicModel = AbstractModel.extend({ viewType: params.viewType, }); - return self.applyDefaultValues(record.id, result) + return self.applyDefaultValues(record.id, result, {fieldNames: _.union(fieldNames, extraFields)}) .then(function () { var def = $.Deferred(); self._performOnChange(record, fields_key).always(function () { @@ -3605,6 +3628,7 @@ var BasicModel = AbstractModel.extend({ parentID: x2manyList.id, viewType: viewType, }); + r._noAbandon = true; x2manyList._changes.push({operation: 'ADD', id: r.id}); x2manyList._cache[r.res_id] = r.id; @@ -3634,6 +3658,8 @@ var BasicModel = AbstractModel.extend({ } else if (_.contains(['one2many', 'many2many'], fieldType)) { var x2mCommands = commands[0][2][fieldName]; defs.push(self._processX2ManyCommands(r, fieldName, x2mCommands)); + } else { + r._changes[fieldName] = self._parseServerValue(field, r._changes[fieldName]); } } } @@ -3832,6 +3858,16 @@ var BasicModel = AbstractModel.extend({ defs.push(self._load(newGroup, options)); } }); + if (options && options.keepEmptyGroups) { + // Find the groups that were available in a previous + // readGroup but are not there anymore. + // Note that these groups are put after existing groups so + // the order is not conserved. A sort *might* be useful. + var emptyGroupsIDs = _.difference(_.pluck(previousGroups, 'id'), list.data); + _.each(emptyGroupsIDs, function (groupID) { + list.data.push(groupID); + }); + } return $.when.apply($, defs).then(function () { // generate the res_ids of the main list, being the concatenation // of the fetched res_ids in each group 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 6a2010708fc..b847d7ca391 100644 --- a/addons/web/static/src/js/views/calendar/calendar_model.js +++ b/addons/web/static/src/js/views/calendar/calendar_model.js @@ -262,7 +262,10 @@ return AbstractModel.extend({ * @param {Moment} start */ setDate: function (start) { - this.data.start_date = this.data.end_date = this.data.target_date = this.data.highlight_date = start; + // keep highlight/target_date in localtime + this.data.highlight_date = this.data.target_date = start.clone(); + // set dates in UTC with timezone applied manually + this.data.start_date = this.data.end_date = start; this.data.start_date.utc().add(this.getSession().getTZOffset(this.data.start_date), 'minutes'); switch (this.data.scale) { diff --git a/addons/web/static/src/js/views/form/form_controller.js b/addons/web/static/src/js/views/form/form_controller.js index 084c9ae4579..e66194dcf99 100644 --- a/addons/web/static/src/js/views/form/form_controller.js +++ b/addons/web/static/src/js/views/form/form_controller.js @@ -15,7 +15,7 @@ var FormController = BasicController.extend({ bounce_edit: '_onBounceEdit', button_clicked: '_onButtonClicked', do_action: '_onDoAction', - freeze_order: '_onFreezeOrder', + edited_list: '_onEditedList', open_one2many_record: '_onOpenOne2ManyRecord', open_record: '_onOpenRecord', toggle_column_order: '_onToggleColumnOrder', @@ -109,6 +109,7 @@ var FormController = BasicController.extend({ * @todo convert to new style */ on_attach_callback: function () { + this._super.apply(this, arguments); this.autofocus(); }, /** @@ -529,11 +530,15 @@ var FormController = BasicController.extend({ * in a x2many list view * * @private - * @param {OdooEvent} event + * @param {OdooEvent} ev + * @param {integer} ev.id of the list to freeze while editing a line */ - _onFreezeOrder: function (event) { - event.stopPropagation(); - this.model.freezeOrder(event.data.id); + _onEditedList: function (ev) { + ev.stopPropagation(); + if (ev.data.id) { + this.model.save(ev.data.id, {savePoint: true}); + } + this.model.freezeOrder(ev.data.id); }, /** * Set the focus on the first primary button of the controller (likely Edit) diff --git a/addons/web/static/src/js/views/graph/graph_renderer.js b/addons/web/static/src/js/views/graph/graph_renderer.js index bc11bb675df..1fbd6572a9a 100644 --- a/addons/web/static/src/js/views/graph/graph_renderer.js +++ b/addons/web/static/src/js/views/graph/graph_renderer.js @@ -74,10 +74,9 @@ return AbstractRenderer.extend({ * Render the chart. * * Note that This method is synchronous, but the actual rendering is done - * asynchronously (in a setTimeout). The reason for that is that nvd3/d3 - * needs to be in the DOM to correctly render itself. So, we trick Odoo by - * returning immediately, then wait a tiny interval before actually - * displaying the data. + * asynchronously. The reason for that is that nvd3/d3 needs to be in the + * DOM to correctly render itself. So, we trick Odoo by returning + * immediately, then we render the chart when the widget is in the DOM. * * @override * @private 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 5f758953658..9be13fb0ae6 100644 --- a/addons/web/static/src/js/views/kanban/kanban_controller.js +++ b/addons/web/static/src/js/views/kanban/kanban_controller.js @@ -10,6 +10,7 @@ odoo.define('web.KanbanController', function (require) { var BasicController = require('web.BasicController'); var Context = require('web.Context'); var core = require('web.core'); +var Domain = require('web.Domain'); var view_dialogs = require('web.view_dialogs'); var _t = core._t; @@ -190,10 +191,57 @@ var KanbanController = BasicController.extend({ resIDs: record.res_ids, }, on_closed: function () { + var recordModel = self.model.localData[record.id]; + var group = self.model.localData[recordModel.parentID]; + var parent = self.model.localData[group.parentID]; + self.model.reload(record.id).then(function (db_id) { var data = self.model.get(db_id); var kanban_record = event.target; kanban_record.update(data); + + // Check if we still need to display the record. Some fields of the domain are + // not guaranteed to be in data. This is for example the case if the action + // contains a domain on a field which is not in the Kanban view. Therefore, + // we need to handle multiple cases based on 3 variables: + // domInData: all domain fields are in the data + // activeInDomain: 'active' is already in the domain + // activeInData: 'active' is available in the data + + var domain = (parent ? parent.domain : group.domain) || []; + var domInData = _.every(domain, function (d) { + return d[0] in data.data; + }); + var activeInDomain = _.pluck(domain, 0).indexOf('active') !== -1; + var activeInData = 'active' in data.data; + + // Case # | domInData | activeInDomain | activeInData + // 1 | true | true | true => no domain change + // 2 | true | true | false => not possible + // 3 | true | false | true => add active in domain + // 4 | true | false | false => no domain change + // 5 | false | true | true => no evaluation + // 6 | false | true | false => no evaluation + // 7 | false | false | true => replace domain + // 8 | false | false | false => no evaluation + + // There are 3 cases which cannot be evaluated since we don't have all the + // necessary information. The complete solution would be to perform a RPC in + // these cases, but this is out of scope. A simpler one is to do a try / catch. + + if (domInData && !activeInDomain && activeInData) { + domain = domain.concat([['active', '=', true]]); + } else if (!domInData && !activeInDomain && activeInData) { + domain = [['active', '=', true]]; + } + try { + var visible = new Domain(domain).compute(data.evalContext); + } catch (e) { + return; + } + if (!visible) { + kanban_record.destroy(); + } }); }, }); diff --git a/addons/web/static/src/js/views/kanban/kanban_model.js b/addons/web/static/src/js/views/kanban/kanban_model.js index 15d6284cfad..411d4cc7b14 100644 --- a/addons/web/static/src/js/views/kanban/kanban_model.js +++ b/addons/web/static/src/js/views/kanban/kanban_model.js @@ -419,7 +419,10 @@ var KanbanModel = BasicModel.extend({ while (element) { if (element.progressBar) { return def.then(function (data) { - return self._load(element, {onlyGroups: true}).then(function () { + return self._load(element, { + keepEmptyGroups: true, + onlyGroups: true, + }).then(function () { return data; }); }); |
