[FIX] web: execute action from new record and go back

Let's assume a form view with a readonly many2one field with a
default value. When creating a new record, the user can click on
the many2one value, which should open the related record in a form
view (stacked in the breadcrumbs).

Before this rev., this didn't work: we actually came back to the
previous view/action in the breadcrumbs, when trying to open the
related record.

closes odoo/odoo#33172

Signed-off-by: VincentSchippefilt <VincentSchippefilt@users.noreply.github.com>
This commit is contained in:
Aaron Bohy
2019-07-18 09:48:07 +00:00
parent f9565d6334
commit e0c7c8e288
4 changed files with 92 additions and 7 deletions
@@ -122,8 +122,11 @@ var AbstractController = mvc.Controller.extend(ActionMixin, {
* @override
*/
canBeRemoved: function () {
// AAB: get rid of this option when on_hashchange mechanism is improved
return this.discardChanges(undefined, { readonlyIfRealDiscard: true });
// AAB: get rid of 'readonlyIfRealDiscard' option when on_hashchange mechanism is improved
return this.discardChanges(undefined, {
noAbandon: true,
readonlyIfRealDiscard: true,
});
},
/**
* Discards the changes made on the record associated to the given ID, or
@@ -137,9 +140,10 @@ var AbstractController = mvc.Controller.extend(ActionMixin, {
*
* @param {string} [recordID]
* if not given, we consider all the changes made by the controller
* @param {Object} [options]
* @returns {Promise} resolved if properly discarded, rejected otherwise
*/
discardChanges: function (recordID) {
discardChanges: function (recordID, options) {
return Promise.resolve();
},
/**
@@ -373,14 +373,16 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
* the new record to be in edit mode too, but the view manager calls
* this function as the URL changes...) @todo get rid of this when
* the webclient/action_manager's hashchange mechanism is improved.
* @param {boolean} [options.noAbandon=false]
* @returns {Promise}
*/
_discardChanges: function (recordID, options) {
var self = this;
recordID = recordID || this.handle;
options = options || {};
return this.canBeDiscarded(recordID)
.then(function (needDiscard) {
if (options && options.readonlyIfRealDiscard && !needDiscard) {
if (options.noAbandon || (options.readonlyIfRealDiscard && !needDiscard)) {
return;
}
self.model.discardChanges(recordID);
@@ -50,13 +50,18 @@ var FormController = BasicController.extend({
this.autofocus();
},
/**
* Force mode back to readonly. Whenever we leave a form view, it is saved,
* and should no longer be in edit mode.
* This hook is called when a form view is restored (by clicking on the
* breadcrumbs). In general, we force mode back to readonly, because
* whenever we leave a form view by stacking another action on the top of
* it, it is saved, and should no longer be in edit mode. However, there is
* a special case for new records for which we still want to be in 'edit'
* as no record has been created (changes have been discarded before
* leaving).
*
* @override
*/
willRestore: function () {
this.mode = 'readonly';
this.mode = this.model.isNew(this.handle) ? 'edit' : 'readonly';
},
//--------------------------------------------------------------------------
@@ -3347,6 +3347,80 @@ QUnit.module('ActionManager', {
actionManager.destroy();
});
QUnit.test('execute action from dirty, new record, and come back', async function (assert) {
assert.expect(19);
this.data.partner.fields.bar.default = 1;
this.archs['partner,false,form'] = '<form>' +
'<field name="foo"/>' +
'<field name="bar" readonly="1"/>' +
'</form>';
var actionManager = await createActionManager({
actions: this.actions,
archs: this.archs,
data: this.data,
mockRPC: function (route, args) {
assert.step(args.method || route);
if (args.method === 'get_formview_action') {
return Promise.resolve({
res_id: 1,
res_model: 'partner',
type: 'ir.actions.act_window',
views: [[false, 'form']],
});
}
return this._super.apply(this, arguments);
},
intercepts: {
do_action: function (ev) {
actionManager.doAction(ev.data.action, {});
},
},
});
// execute an action and create a new record
await actionManager.doAction(3);
await testUtils.dom.click(actionManager.$('.o_list_button_add'));
assert.containsOnce(actionManager, '.o_form_view.o_form_editable');
assert.containsOnce(actionManager, '.o_form_uri:contains(First record)');
assert.strictEqual(actionManager.$('.o_control_panel .breadcrumb-item').text(),
"PartnersNew");
// set form view dirty and open m2o record
await testUtils.fields.editInput(actionManager.$('input[name=foo]'), 'val');
await testUtils.dom.click(actionManager.$('.o_form_uri:contains(First record)'));
assert.containsOnce($('body'), '.modal'); // confirm discard dialog
// confirm discard changes
await testUtils.dom.click($('.modal .modal-footer .btn-primary'));
assert.containsOnce(actionManager, '.o_form_view.o_form_readonly');
assert.strictEqual(actionManager.$('.o_control_panel .breadcrumb-item').text(),
"PartnersNewFirst record");
// go back to New using the breadcrumbs
await testUtils.dom.click(actionManager.$('.o_control_panel .breadcrumb-item:nth(1) a'));
assert.containsOnce(actionManager, '.o_form_view.o_form_editable');
assert.strictEqual(actionManager.$('.o_control_panel .breadcrumb-item').text(),
"PartnersNew");
assert.verifySteps([
'/web/action/load', // action 3
'load_views', // views of action 3
'/web/dataset/search_read', // list
'default_get', // form (create)
'name_get', // m2o in form
'get_formview_action', // click on m2o
'load_views', // form view of dynamic action
'read', // form
'default_get', // form (create)
'name_get', // m2o in form
]);
actionManager.destroy();
});
QUnit.module('Actions in target="new"');
QUnit.test('can execute act_window actions in target="new"', async function (assert) {