[FIX] web: display a translation alert when saving the form view
In the form view, when the user saves a record (with a multilang instance), an alert is displayed to display all modified translatable fields.
This commit is contained in:
@@ -123,6 +123,7 @@ you should use:
|
||||
*
|
||||
* @override method from field manager mixin
|
||||
* @param {string} id
|
||||
* @returns {Deferred}
|
||||
*/
|
||||
_confirmSave: function (id) {
|
||||
|
||||
|
||||
@@ -56,14 +56,7 @@ var TranslatableFieldMixin = {
|
||||
* @private
|
||||
*/
|
||||
_onTranslate: function () {
|
||||
this._rpc({
|
||||
route: '/web/dataset/call_button',
|
||||
params: {
|
||||
model: 'ir.translation',
|
||||
method: 'translate_fields',
|
||||
args: [this.model, this.res_id, this.name, this.record.getContext()],
|
||||
}
|
||||
}).then(this.do_action.bind(this));
|
||||
this.trigger_up('translate', {fieldName: this.name, id: this.dataPointID});
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -19,7 +19,8 @@ var _t = core._t;
|
||||
var BasicController = AbstractController.extend(FieldManagerMixin, {
|
||||
custom_events: _.extend({}, AbstractController.prototype.custom_events, FieldManagerMixin.custom_events, {
|
||||
reload: '_onReload',
|
||||
sidebar_data_asked: '_onSidebarDataAsked'
|
||||
sidebar_data_asked: '_onSidebarDataAsked',
|
||||
translate: '_onTranslate',
|
||||
}),
|
||||
/**
|
||||
* @override
|
||||
@@ -169,6 +170,8 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
|
||||
* @param {string} [recordID] - default to main recordID
|
||||
* @param {Object} [options]
|
||||
* @returns {Deferred}
|
||||
* Resolved with the list of field names (whose value has been modified)
|
||||
* Rejected if the record can't be saved
|
||||
*/
|
||||
saveRecord: function (recordID, options) {
|
||||
// Some field widgets can't detect (all) their changes immediately or
|
||||
@@ -367,6 +370,8 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
|
||||
* if true, the record will only be 'locally' saved: its changes
|
||||
* will move from the _changes key to the data key
|
||||
* @returns {Deferred}
|
||||
* Resolved with the list of field names (whose value has been modified)
|
||||
* Rejected if the record can't be saved
|
||||
*/
|
||||
_saveRecord: function (recordID, options) {
|
||||
recordID = recordID || this.handle;
|
||||
@@ -376,22 +381,24 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
|
||||
savePoint: false,
|
||||
});
|
||||
|
||||
var def = $.Deferred();
|
||||
// Check if the view is in a valid state for saving
|
||||
// Note: it is the model's job to do nothing if there is nothing to save
|
||||
if (this.canBeSaved(recordID)) {
|
||||
def = this.model.save(recordID, { // Save then leave edit mode
|
||||
var self = this;
|
||||
var def = this.model.save(recordID, { // Save then leave edit mode
|
||||
reload: options.reload,
|
||||
savePoint: options.savePoint,
|
||||
});
|
||||
} else {
|
||||
def.reject(); // Cannot be saved, do nothing at all
|
||||
}
|
||||
|
||||
if (options.stayInEdit) {
|
||||
if (!options.stayInEdit) {
|
||||
def = def.then(function (fieldNames) {
|
||||
return self._confirmSave(recordID).then(function () {
|
||||
return fieldNames;
|
||||
});
|
||||
});
|
||||
}
|
||||
return def;
|
||||
} else {
|
||||
return def.then(this._confirmSave.bind(this, recordID));
|
||||
return $.Deferred().reject(); // Cannot be saved
|
||||
}
|
||||
},
|
||||
/**
|
||||
@@ -498,6 +505,23 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
|
||||
var sidebarEnv = this._getSidebarEnv();
|
||||
event.data.callback(sidebarEnv);
|
||||
},
|
||||
/**
|
||||
* open the translation view for the current field
|
||||
*
|
||||
* @private
|
||||
* @param {OdooEvent} event
|
||||
*/
|
||||
_onTranslate: function (event) {
|
||||
var record = this.model.get(event.data.id, {raw: true});
|
||||
this._rpc({
|
||||
route: '/web/dataset/call_button',
|
||||
params: {
|
||||
model: 'ir.translation',
|
||||
method: 'translate_fields',
|
||||
args: [record.model, record.res_id, event.data.fieldName, record.getContext()],
|
||||
}
|
||||
}).then(this.do_action.bind(this));
|
||||
},
|
||||
});
|
||||
|
||||
return BasicController;
|
||||
|
||||
@@ -708,6 +708,7 @@ var BasicModel = AbstractModel.extend({
|
||||
* be 'locally' saved: its changes written in a _savePoint key that can
|
||||
* be restored later by call discardChanges with option rollback to true
|
||||
* @returns {Deferred}
|
||||
* Resolved with the list of field names (whose value has been modified)
|
||||
*/
|
||||
save: function (record_id, options) {
|
||||
var self = this;
|
||||
@@ -723,7 +724,6 @@ var BasicModel = AbstractModel.extend({
|
||||
rec._savePoint = _.extend({}, newValue);
|
||||
}
|
||||
});
|
||||
return $.when();
|
||||
}
|
||||
var shouldReload = 'reload' in options ? options.reload : true;
|
||||
var method = self.isNew(record_id) ? 'create' : 'write';
|
||||
@@ -742,10 +742,17 @@ var BasicModel = AbstractModel.extend({
|
||||
});
|
||||
}
|
||||
|
||||
var def = $.Deferred();
|
||||
var changedFields = Object.keys(changes);
|
||||
|
||||
if (options.savePoint) {
|
||||
return def.resolve(changedFields);
|
||||
}
|
||||
|
||||
// in the case of a write, only perform the RPC if there are changes to save
|
||||
if (method === 'create' || Object.keys(changes).length) {
|
||||
if (method === 'create' || changedFields.length) {
|
||||
var args = method === 'write' ? [[record.data.id], changes] : [changes];
|
||||
return self._rpc({
|
||||
self._rpc({
|
||||
model: record.model,
|
||||
method: method,
|
||||
args: args,
|
||||
@@ -759,23 +766,26 @@ var BasicModel = AbstractModel.extend({
|
||||
record.count++;
|
||||
}
|
||||
|
||||
// Update the data directly or reload them
|
||||
var def;
|
||||
if (shouldReload) {
|
||||
def = self._fetchRecord(record);
|
||||
} else {
|
||||
_.extend(record.data, record._changes);
|
||||
}
|
||||
var _changes = record._changes;
|
||||
|
||||
// Erase changes as they have been applied
|
||||
record._changes = {};
|
||||
record._isDirty = false;
|
||||
|
||||
return def;
|
||||
// Update the data directly or reload them
|
||||
if (shouldReload) {
|
||||
self._fetchRecord(record).then(function (record) {
|
||||
def.resolve(changedFields);
|
||||
});
|
||||
} else {
|
||||
_.extend(record.data, _changes);
|
||||
def.resolve(changedFields);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return $.when(record_id);
|
||||
def.resolve(changedFields);
|
||||
}
|
||||
return def;
|
||||
});
|
||||
},
|
||||
/**
|
||||
|
||||
@@ -168,6 +168,35 @@ var FormController = BasicController.extend({
|
||||
this._updateSidebar();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Show a warning message if the user modified a translated field. For each
|
||||
* field, the notification provides a link to edit the field's translations.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
saveRecord: function () {
|
||||
var result = this._super.apply(this, arguments);
|
||||
if (_t.database.multi_lang) {
|
||||
var self = this;
|
||||
result.then(function (changedFields) {
|
||||
if (!changedFields.length) {
|
||||
return changedFields;
|
||||
}
|
||||
var fields = self.renderer.state.fields;
|
||||
var alertFields = [];
|
||||
for (var k = 0; k < changedFields.length; k++) {
|
||||
var field = fields[changedFields[k]];
|
||||
if (field.translate) {
|
||||
alertFields.push(field);
|
||||
}
|
||||
}
|
||||
if (alertFields.length) {
|
||||
self.renderer.displayTranslationAlert(alertFields);
|
||||
}
|
||||
});
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
@@ -180,6 +209,7 @@ var FormController = BasicController.extend({
|
||||
* @private
|
||||
* @override method from field manager mixin
|
||||
* @param {string} id
|
||||
* @returns {Deferred}
|
||||
*/
|
||||
_confirmSave: function (id) {
|
||||
if (id === this.handle) {
|
||||
|
||||
@@ -10,6 +10,9 @@ var qweb = core.qweb;
|
||||
|
||||
var FormRenderer = BasicRenderer.extend({
|
||||
className: "o_form_view",
|
||||
events: _.extend({}, BasicRenderer.prototype.events, {
|
||||
'click .o_notification_box .oe_field_translate': '_onTranslate',
|
||||
}),
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
@@ -48,6 +51,25 @@ var FormRenderer = BasicRenderer.extend({
|
||||
});
|
||||
return fieldNames;
|
||||
},
|
||||
/**
|
||||
* Show a warning message if the user modified a translated field. For each
|
||||
* field, the notification provides a link to edit the field's translations.
|
||||
*
|
||||
* @param {Object[]} alertFields: field list
|
||||
*/
|
||||
displayTranslationAlert: function (alertFields) {
|
||||
this.$('.o_notification_box').remove();
|
||||
var $notification = $(qweb.render('notification-box', {type: 'info'}))
|
||||
.append(qweb.render('translation-alert', {
|
||||
fields: alertFields,
|
||||
lang: _t.database.parameters.name
|
||||
}));
|
||||
if (this.$('.o_form_statusbar').length) {
|
||||
this.$('.o_form_statusbar').after($notification);
|
||||
} else {
|
||||
this.$el.prepend($notification);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @see BasicRenderer.confirmChange
|
||||
*
|
||||
@@ -771,6 +793,16 @@ var FormRenderer = BasicRenderer.extend({
|
||||
this._activatePreviousFieldWidget(this.state, index);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* open the translation view for the current field
|
||||
*
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onTranslate: function (event) {
|
||||
event.preventDefault();
|
||||
this.trigger_up('translate', {fieldName: event.target.name, id: this.state.id});
|
||||
},
|
||||
});
|
||||
|
||||
return FormRenderer;
|
||||
|
||||
@@ -127,7 +127,7 @@ var KanbanModel = BasicModel.extend({
|
||||
}
|
||||
return this.notifyChanges(recordID, changes).then(function () {
|
||||
return self.save(recordID);
|
||||
}).then(function (result) {
|
||||
}).then(function () {
|
||||
// Remove record from its current group
|
||||
var old_group;
|
||||
for (var i = 0; i < parent.count; i++) {
|
||||
@@ -140,7 +140,7 @@ var KanbanModel = BasicModel.extend({
|
||||
}
|
||||
}
|
||||
// Add record to its new group
|
||||
new_group.data.push(result.id);
|
||||
new_group.data.push(recordID);
|
||||
new_group.count++;
|
||||
return [old_group.id, new_group.id];
|
||||
});
|
||||
|
||||
@@ -1219,7 +1219,7 @@
|
||||
</t>
|
||||
|
||||
<t t-name="notification-box">
|
||||
<div t-att-class="'alert alert-dismissible alert-' + type" role="alert">
|
||||
<div t-attf-class="o_notification_box alert alert-dismissible alert-{{type}}" role="alert">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true" class="fa fa-times"></span>
|
||||
</button>
|
||||
|
||||
@@ -662,7 +662,7 @@ QUnit.module('basic_fields', {
|
||||
|
||||
this.data.partner.fields.foo.translate = true;
|
||||
|
||||
var multi_lang = _t.database.multi_lang;
|
||||
var multiLang = _t.database.multi_lang;
|
||||
_t.database.multi_lang = true;
|
||||
|
||||
var form = createView({
|
||||
@@ -707,7 +707,7 @@ QUnit.module('basic_fields', {
|
||||
assert.strictEqual($button.length, 0, "should not have a translate button in create mode");
|
||||
form.destroy();
|
||||
|
||||
_t.database.multi_lang = multi_lang;
|
||||
_t.database.multi_lang = multiLang;
|
||||
});
|
||||
|
||||
|
||||
@@ -875,7 +875,7 @@ QUnit.module('basic_fields', {
|
||||
|
||||
this.data.partner.fields.txt.translate = true;
|
||||
|
||||
var multi_lang = _t.database.multi_lang;
|
||||
var multiLang = _t.database.multi_lang;
|
||||
_t.database.multi_lang = true;
|
||||
|
||||
var form = createView({
|
||||
@@ -920,7 +920,7 @@ QUnit.module('basic_fields', {
|
||||
assert.strictEqual($button.length, 0, "should not have a translate button in create mode");
|
||||
form.destroy();
|
||||
|
||||
_t.database.multi_lang = multi_lang;
|
||||
_t.database.multi_lang = multiLang;
|
||||
});
|
||||
|
||||
QUnit.module('FieldBinary');
|
||||
|
||||
@@ -4377,5 +4377,38 @@ QUnit.module('Views', {
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('display translation alert', function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.data.partner.fields.foo.translate = true;
|
||||
|
||||
var multi_lang = _t.database.multi_lang;
|
||||
_t.database.multi_lang = true;
|
||||
|
||||
var form = createView({
|
||||
View: FormView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch: '<form string="Partners">' +
|
||||
'<sheet>' +
|
||||
'<group>' +
|
||||
'<field name="foo"/>' +
|
||||
'</group>' +
|
||||
'</sheet>' +
|
||||
'</form>',
|
||||
res_id: 1,
|
||||
});
|
||||
|
||||
form.$buttons.find('.o_form_button_edit').click();
|
||||
form.$('input[name="foo"]').val("test").trigger("input");
|
||||
form.$buttons.find('.o_form_button_save').click();
|
||||
|
||||
assert.strictEqual(form.$('.o_form_view > .alert > div').length, 1,"should have a translation alert");
|
||||
|
||||
form.destroy();
|
||||
|
||||
_t.database.multi_lang = multi_lang;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user