[FIX] web, document: adapt toolbar to the new views

- Fix the toolbar behaviour since there is no more dataset in the widget.
- Improve the communication beetwen the controller and the toolbar.
- Move the code linked to the document upload in the right file.
- Adapt the tests to mock the search_read done by ´document´ addon
- Adapt the mock server to be able to give a custom toolbar action
This commit is contained in:
fwi-odoo
2017-05-03 13:41:07 +02:00
parent 30384249e1
commit a53335fdb2
12 changed files with 558 additions and 232 deletions
+164 -14
View File
@@ -1,33 +1,183 @@
odoo.define('document.document', function(require) {
odoo.define('document.document', function (require) {
"use strict";
var core = require('web.core');
var Dialog = require('web.Dialog');
var framework = require('web.framework');
var Sidebar = require('web.Sidebar');
var _t = core._t;
Sidebar.include({
init : function(){
/**
* @override
*/
init : function (parent, options) {
this._super.apply(this, arguments);
var view = this.getParent();
if (view.fields_view && view.fields_view.type === "form") {
if (options.viewType === "form") {
this.sections.splice(1, 0, { 'name' : 'files', 'label' : _t('Attachment(s)'), });
this.items.files = [];
}
this.fileuploadId = _.uniqueId('oe_fileupload');
$(window).on(this.fileuploadId, this._onFileUploaded.bind(this));
},
on_attachments_loaded: function(attachments) {
/**
* Get the attachment linked to the record when the toolbar started
*
* @override
*/
start: function () {
var _super = this._super.bind(this);
this._updateAttachments().then(_super);
},
/**
* @override
*/
destroy: function () {
$(window).off(this.fileuploadId);
this._super.apply(this, arguments);
},
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
/**
* @override
*/
updateEnv: function (env) {
this.env = env;
this._updateAttachments().then(this._redraw.bind(this));
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* Process the attachments then rerender the toolbar
*
* @private
* @param {Object} attachments
*/
_processAttachments: function (attachments) {
//to display number in name if more then one attachment which has same name.
var self = this;
_.chain(attachments)
.groupBy(function(attachment) { return attachment.name; })
.each(function(attachment){
if(attachment.length > 1)
_.map(attachment, function(attachment, i){
attachment.name = _.str.sprintf(_t("%s (%s)"), attachment.name, i+1);
});
});
self._super(attachments);
.groupBy(function (attachment) { return attachment.name; })
.each(function (attachment) {
if (attachment.length > 1) {
_.map(attachment, function (attachment, i) {
attachment.name = _.str.sprintf(_t("%s (%s)"), attachment.name, i+1);
});
}
});
_.each(attachments,function (a) {
a.label = a.name;
if (a.type === "binary") {
a.url = '/web/content/' + a.id + '?download=true';
}
});
this.items.files = attachments;
},
/**
* @private
* @override
*/
_redraw: function () {
this._super.apply(this, arguments);
this.$('.o_sidebar_add_attachment .o_form_binary_form')
.change(this._onAddAttachment.bind(this));
this.$('.o_sidebar_delete_attachment')
.click(this._onDeleteAttachment.bind(this));
},
/**
* Update the attachments to be displayed in the attachment section
* of the toolbar
*
* @private
*/
_updateAttachments: function () {
var activeId = this.env.activeIds[0];
if (!activeId) {
this.items.files = [];
return $.when();
} else {
var domain = [
['res_model', '=', this.env.model],
['res_id', '=', activeId],
['type', 'in', ['binary', 'url']]
];
var fields = ['name', 'url', 'type',
'create_uid', 'create_date', 'write_uid', 'write_date'];
return this._rpc({
model: 'ir.attachment',
method: 'search_read',
context: this.env.context,
domain: domain,
fields: fields,
}).then(this._processAttachments.bind(this));
}
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* Method triggered when user click on 'add attachment' and select a file
*
* @private
* @param {Event} event
*/
_onAddAttachment: function (event) {
var $event = $(event.target);
if ($event.val() !== '') {
var $binaryForm = this.$('form.o_form_binary_form');
$binaryForm.submit();
$binaryForm.find('input[type=file]').prop('disabled', true);
$binaryForm.find('button').prop('disabled', true).find('img, span').toggle();
this.$('.o_sidebar_add_attachment a').text(_t('Uploading...'));
framework.blockUI();
}
},
/**
* Method triggered when user delete an attachment
*
* @private
* @param {Event} event
*/
_onDeleteAttachment: function (event) {
event.preventDefault();
var self = this;
var $event = $(event.currentTarget);
var options = {
confirm_callback: function () {
self._rpc({
model: 'ir.attachment',
method: 'unlink',
args: [parseInt($event.attr('data-id'), 10)],
})
.then(self._updateAttachments.bind(self))
.then(self._redraw.bind(self));
}
};
Dialog.confirm(this, _t("Do you really want to delete this attachment ?"), options);
},
/**
* Handler called when the upload is done
*
* @private
*/
_onFileUploaded: function () {
var attachments = Array.prototype.slice.call(arguments, 1);
var uploadErrors = _.filter(attachments, function (attachment) {
return attachment.error;
});
if (uploadErrors.length) {
this.do_warn(_t('Uploading Error'), uploadErrors[0].error);
}
this._updateAttachments().then(this._redraw.bind(this));
framework.unblockUI();
}
});
});
+149 -162
View File
@@ -3,9 +3,6 @@ odoo.define('web.Sidebar', function (require) {
var Context = require('web.Context');
var core = require('web.core');
var data = require('web.data');
var Dialog = require('web.Dialog');
var framework = require('web.framework');
var pyeval = require('web.pyeval');
var Widget = require('web.Widget');
@@ -13,12 +10,25 @@ var QWeb = core.qweb;
var _t = core._t;
var Sidebar = Widget.extend({
init: function(parent, options) {
var self = this;
this._super(parent);
events: {
"click .dropdown-menu li a": "_onDropdownClicked"
},
/**
* @override
*
* @param {Object} options
* @param {Object} options.items
* @param {Object} options.sections
* @param {Object} options.env
* @param {Object} options.actions
*
*/
init: function (parent, options) {
this._super.apply(this, arguments);
this.options = _.defaults(options || {}, {
'editable': true
});
this.env = options.env;
this.sections = options.sections || [
{name: 'print', label: _t('Print')},
{name: 'other', label: _t('Action')},
@@ -27,51 +37,37 @@ var Sidebar = Widget.extend({
print: [],
other: [],
};
this.fileupload_id = _.uniqueId('oe_fileupload');
$(window).on(this.fileupload_id, function() {
var args = [].slice.call(arguments).slice(1);
self.do_attachement_update(self.dataset, self.model_id,args);
framework.unblockUI();
});
if (options.actions) {
this._addToolbarActions(options.actions);
}
},
start: function() {
var self = this;
this._super(this);
/**
* @override
*/
start: function () {
this._super.apply(this, arguments);
this.$el.addClass('btn-group');
this.redraw();
this.$el.on('click','.dropdown-menu li a', function(event) {
var section = $(this).data('section');
var index = $(this).data('index');
var item = self.items[section][index];
if (item.callback) {
item.callback.apply(self, [item]);
} else if (item.action) {
self.on_item_action_clicked(item);
} else if (item.url) {
return true;
}
event.preventDefault();
});
this._redraw();
},
destroy: function() {
$(window).off(this.fileupload_id);
return this._super.apply(this, arguments);
},
redraw: function() {
this.$el.html(QWeb.render('Sidebar', {widget: this}));
// Hides Sidebar sections when item list is empty
this.$('.o_dropdown').each(function() {
if (!$(this).find('li').length) {
$(this).hide();
}
});
this.$("[title]").tooltip({
delay: { show: 500, hide: 0}
});
this.$('.o_sidebar_add_attachment .o_form_binary_form').change(this.on_attachment_changed);
this.$('.o_sidebar_delete_attachment').click(this.on_attachment_delete);
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
/**
* Update the env for the sidebar then rerender it.
*
* @param {Object} env
*/
updateEnv: function (env) {
this.env = env;
this._redraw();
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* For each item added to the section:
*
@@ -86,7 +82,7 @@ var Sidebar = Widget.extend({
* function to call when the item is clicked in the sidebar, called
* with the item descriptor as its first argument (so information
* can be stored as additional keys on the object passed to
* ``add_items``)
* ``_addItems``)
*
* ``classname`` (optional)
* ``@class`` set on the sidebar serialization of the item
@@ -94,135 +90,126 @@ var Sidebar = Widget.extend({
* ``title`` (optional)
* will be set as the item's ``@title`` (tooltip)
*
* @param {String} section_code
* @private
* @param {String} sectionCode
* @param {Array<{label, action | callback[, classname][, title]}>} items
*/
add_items: function(section_code, items) {
_addItems: function (sectionCode, items) {
if (items) {
this.items[section_code].unshift.apply(this.items[section_code],items);
this.redraw();
this.items[sectionCode].unshift.apply(this.items[sectionCode], items);
}
},
add_toolbar: function(toolbar) {
/**
* Method that will add the custom actions to the toolbar
*
* @private
* @param {Object} toolbarActions
*/
_addToolbarActions: function (toolbarActions) {
var self = this;
_.each(['print','action','relate'], function(type) {
var items = toolbar[type];
if (items) {
var actions = _.map(items, function (item) {
return {
label: item.name,
action: item,
};
});
self.add_items(type === 'print' ? 'print' : 'other', actions);
}
});
},
on_item_action_clicked: function(item) {
var self = this;
self.getParent().sidebar_eval_context().done(function (sidebar_eval_context) {
var ids = self.getParent().getSelectedIds();
var domain;
if (self.getParent().getActiveDomain) {
domain = self.getParent().getActiveDomain();
}
else {
domain = $.Deferred().resolve(undefined);
}
if (ids.length === 0) {
new Dialog(this, {title: _t("Warning"), size: 'medium', $content: $("<div/>").html(_t("You must choose at least one record."))}).open();
return false;
}
var dataset = self.getParent().dataset;
var active_ids_context = {
active_id: ids[0],
active_ids: ids,
active_model: dataset.model,
};
$.when(domain).done(function (domain) {
if (domain !== undefined) {
active_ids_context.active_domain = domain;
_.each(['print','action','relate'], function (type) {
if (type in toolbarActions) {
var actions = toolbarActions[type];
if (actions && actions.length) {
var items = _.map(actions, function (action) {
return {
label: action.name,
action: action,
};
});
self._addItems(type === 'print' ? 'print' : 'other', items);
}
var c = pyeval.eval('context',
new Context(
sidebar_eval_context, active_ids_context));
self._rpc({
route: '/web/action/load',
params: {
action_id: item.action.id,
context: new Context(
dataset.get_context(), active_ids_context).eval()
},
})
.done(function(result) {
result.context = new Context(
result.context || {}, active_ids_context)
.set_eval_context(c);
result.flags = result.flags || {};
result.flags.new_window = true;
self.do_action(result, {
on_close: function() {
// reload view
self.getParent().reload();
},
});
});
});
});
},
do_attachement_update: function(dataset, model_id, args) {
this.dataset = dataset;
this.model_id = model_id;
var upload_error = _.filter(args, function(attachment) {return attachment.error;});
if (upload_error.length) {
this.do_warn(_t('Uploading Error'), upload_error[0].error);
}
if (!model_id) {
this.on_attachments_loaded([]);
} else {
var dom = [ ['res_model', '=', dataset.model], ['res_id', '=', model_id], ['type', 'in', ['binary', 'url']] ];
var ds = new data.DataSetSearch(this, 'ir.attachment', dataset.get_context(), dom);
ds.read_slice(['name', 'url', 'type', 'create_uid', 'create_date', 'write_uid', 'write_date'], {}).done(this.on_attachments_loaded);
}
},
on_attachments_loaded: function(attachments) {
_.each(attachments,function(a) {
a.label = a.name;
if(a.type === "binary") {
a.url = '/web/content/' + a.id + '?download=true';
}
});
this.items.files = attachments;
this.redraw();
},
on_attachment_changed: function(e) {
var $e = $(e.target);
if ($e.val() !== '') {
this.$('form.o_form_binary_form').submit();
$e.parent().find('input[type=file]').prop('disabled', true);
$e.parent().find('button').prop('disabled', true).find('img, span').toggle();
this.$('.o_sidebar_add_attachment a').text(_t('Uploading...'));
framework.blockUI();
if ('other' in toolbarActions) {
this._addItems('other', toolbarActions.other);
}
},
on_attachment_delete: function(e) {
e.preventDefault();
e.stopPropagation();
/**
* Performs the action for the item clicked after getting the data
* necessary with a trigger up
*
* @private
* @param {Object} item
*/
_onItemActionClicked: function (item) {
var self = this;
var $e = $(e.currentTarget);
var options = {
confirm_callback: function () {
new data.DataSet(this, 'ir.attachment')
.unlink([parseInt($e.attr('data-id'), 10)])
.done(function() {
self.do_attachement_update(self.dataset, self.model_id);
this.trigger_up('sidebar_data_asked', {
callback: function (env) {
self.env = env;
var activeIdsContext = {
active_id: env.activeIds[0],
active_ids: env.activeIds,
active_model: env.model,
};
if (env.domain) {
activeIdsContext.active_domain = env.domain;
}
var context = pyeval.eval('context', new Context(env.context, activeIdsContext));
self._rpc({
route: '/web/action/load',
params: {
action_id: item.action.id,
context: context,
},
}).done(function (result) {
result.context = new Context(
result.context || {}, activeIdsContext)
.set_eval_context(context);
result.flags = result.flags || {};
result.flags.new_window = true;
self.do_action(result, {
on_close: function () {
self.trigger_up('reload');
},
});
});
}
};
Dialog.confirm(this, _t("Do you really want to delete this attachment ?"), options);
}
});
},
/**
* Method that renders the sidebar when there is a data update
*
* @private
*/
_redraw: function () {
this.$el.html(QWeb.render('Sidebar', {widget: this}));
// Hides Sidebar sections when item list is empty
this.$('.o_dropdown').each(function () {
if (!$(this).find('li').length) {
$(this).hide();
}
});
this.$("[title]").tooltip({
delay: { show: 500, hide: 0}
});
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* Method triggered when the user clicks on a toolbar dropdown
*
* @private
* @param {MouseEvent} event
*/
_onDropdownClicked: function (event) {
var section = $(event.currentTarget).data('section');
var index = $(event.currentTarget).data('index');
var item = this.items[section][index];
if (item.callback) {
item.callback.apply(this, [item]);
} else if (item.action) {
this._onItemActionClicked(item);
} else if (item.url) {
return true;
}
event.preventDefault();
},
});
return Sidebar;
@@ -19,6 +19,7 @@ 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'
}),
/**
* @override
@@ -126,6 +127,14 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
self._setMode('readonly', recordID);
});
},
/**
* Method that will be overriden by the views with the ability to have selected ids
*
* @returns []
*/
getSelectedIds: function () {
return [];
},
/**
* @override
*/
@@ -287,6 +296,19 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
doIt();
}
},
/**
* Returns the new sidebar env
*
* @private
* @return {Object} the new sidebar env
*/
_getSidebarEnv: function () {
return {
context: this.model.get(this.handle).getContext(),
activeIds: this.getSelectedIds(),
model: this.modelName,
};
},
/**
* Used by list and kanban views to determine whether or not to display
* the no content helper (if there is no data in the state to display)
@@ -389,9 +411,15 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
/**
* Helper method, to get the current environment variables from the model
* and notifies the component chain (by bubbling an event up)
*
* @private
*/
_updateEnv: function () {
var env = this.model.get(this.handle, {env: true});
if (this.sidebar) {
var sidebarEnv = this._getSidebarEnv();
this.sidebar.updateEnv(sidebarEnv);
}
this.trigger_up('env_updated', env);
},
/**
@@ -457,6 +485,17 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
this.reload({fieldNames: data.fieldNames});
}
},
/**
* Handler used to get all the data necessary when a custom action is
* performed through the sidebar.
*
* @private
* @param {OdooEvent} event
*/
_onSidebarDataAsked: function (event) {
var sidebarEnv = this._getSidebarEnv();
event.data.callback(sidebarEnv);
},
});
return BasicController;
@@ -21,6 +21,9 @@ var FormController = BasicController.extend({
}),
/**
* @override
*
* @param {boolean} params.hasSidebar
* @param {Object} params.toolbarActions
*/
init: function (parent, model, renderer, params) {
this._super.apply(this, arguments);
@@ -29,7 +32,7 @@ var FormController = BasicController.extend({
this.footerToButtons = params.footerToButtons;
this.defaultButtons = params.defaultButtons;
this.hasSidebar = params.hasSidebar;
this.toolbar = params.toolbar;
this.toolbarActions = params.toolbarActions || {};
},
//--------------------------------------------------------------------------
@@ -71,13 +74,12 @@ var FormController = BasicController.extend({
* Returns the current res_id, wrapped in a list. This is only used by the
* sidebar (and the debugmanager)
*
* @todo This should be private. Need to change sidebar code
* @override
*
* @returns {number[]} either [current res_id] or []
*/
getSelectedIds: function () {
var env = this.model.get(this.handle, {env: true});
// FIX ME : fix sidebar widget
return env.currentId ? [env.currentId] : [];
},
/**
@@ -137,12 +139,6 @@ var FormController = BasicController.extend({
**/
renderSidebar: function ($node) {
if (!this.sidebar && this.hasSidebar) {
this.sidebar = new Sidebar(this, {
editable: this.is_action_enabled('edit')
});
if (this.toolbar) {
this.sidebar.add_toolbar(this.toolbar);
}
var otherItems = [];
if (this.is_action_enabled('delete')) {
otherItems.push({
@@ -156,7 +152,16 @@ var FormController = BasicController.extend({
callback: this._onDuplicateRecord.bind(this),
});
}
this.sidebar.add_items('other', otherItems);
this.sidebar = new Sidebar(this, {
editable: this.is_action_enabled('edit'),
viewType: 'form',
env: {
context: this.model.get(this.handle).getContext(),
activeIds: this.getSelectedIds(),
model: this.modelName,
},
actions: _.extend(this.toolbarActions, {other: otherItems}),
});
this.sidebar.appendTo($node);
// Show or hide the sidebar according to the view mode
@@ -30,7 +30,7 @@ var FormView = BasicView.extend({
this.loadParams.parentID = params.parentID;
this.controllerParams.hasSidebar = params.sidebar;
this.controllerParams.toolbar = false;
this.controllerParams.toolbarActions = viewInfo.toolbar;
this.controllerParams.footerToButtons = params.footer_to_buttons;
if ('action' in params && 'flags' in params.action) {
this.controllerParams.footerToButtons = params.action.flags.footer_to_buttons;
@@ -30,16 +30,15 @@ var ListController = BasicController.extend({
* @constructor
* @override
* @param {Object} params
* @param {Object} params
* @param {boolean} params.editable
* @param {boolean} params.hasSidebar
* @param {boolean} params.hasToolbar
* @param {Object} params.toolbarActions
* @param {boolean} params.noLeaf
*/
init: function (parent, model, renderer, params) {
this._super.apply(this, arguments);
this.hasSidebar = params.hasSidebar;
this.hasToolbar = params.hasToolbar;
this.toolbarActions = params.toolbarActions || {};
this.editable = params.editable;
this.noLeaf = params.noLeaf;
this.selectedRecords = []; // there is no selected record by default
@@ -72,7 +71,7 @@ var ListController = BasicController.extend({
* if the header checkbox has been checked. This is done by evaluating the
* search results, and then adding the dataset domain (i.e. action domain).
*
* @todo This is done only for the sidebar. The full mechanism is wrong,
* @todo This is done only for the data export. The full mechanism is wrong,
* this method should be private, most of the code in the sidebar should be
* moved to the controller, and we should not use the getParent method...
*
@@ -99,7 +98,7 @@ var ListController = BasicController.extend({
* Returns the list of currently selected res_ids (with the check boxes on
* the left)
*
* @todo This should be private. Need to change sidebar code
* @override
*
* @returns {number[]} list of res_ids
*/
@@ -135,12 +134,6 @@ var ListController = BasicController.extend({
*/
renderSidebar: function ($node) {
if (this.hasSidebar && !this.sidebar) {
this.sidebar = new Sidebar(this, {
editable: this.is_action_enabled('edit')
});
if (this.hasToolbar) {
this.sidebar.add_toolbar(this.fields_view.toolbar);
}
var other = [{
label: _t("Export"),
callback: this._onExportData.bind(this)
@@ -160,8 +153,16 @@ var ListController = BasicController.extend({
label: _t('Delete'),
callback: this._onDeleteSelectedRecords.bind(this)
});
this.sidebar = new Sidebar(this, {
editable: this.is_action_enabled('edit'),
env: {
context: this.model.get(this.handle).getContext(),
activeIds: this.getSelectedIds(),
model: this.modelName,
},
actions: _.extend(this.toolbarActions, {other: other}),
});
}
this.sidebar.add_items('other', other);
this.sidebar.appendTo($node);
this._toggleSidebar();
@@ -232,6 +233,15 @@ var ListController = BasicController.extend({
var state = this.model.get(this.handle);
return this.renderer.confirmSave(state, id);
},
/**
* @override
* @private
*/
_getSidebarEnv: function() {
var env = this._super.apply(this, arguments);
var record = this.model.get(this.handle);
return _.extend(env, {domain: record.getDomain()});
},
/**
* Allows to change the mode of a single row.
*
@@ -380,6 +390,13 @@ var ListController = BasicController.extend({
this.selectedRecords = event.data.selection;
this._toggleSidebar();
},
/**
* @override
*/
_onSidebarDataAsked: function (event) {
var env = this._getSidebarEnv();
event.data.callback(env);
},
/**
* Called when clicking on 'Archive' or 'Unarchive' in the sidebar.
*
@@ -41,6 +41,7 @@ var ListView = BasicView.extend({
this.controllerParams.editable = arch.attrs.editable;
this.controllerParams.hasSidebar = params.sidebar;
this.controllerParams.toolbarActions = viewInfo.toolbar;
this.controllerParams.activeActions.delete = true;
this.controllerParams.noLeaf = !!this.loadParams.context.group_by_no_leaf;
this.controllerParams.mode = mode;
+4 -4
View File
@@ -241,12 +241,12 @@
</li>
<li t-if="section.name == 'files' and widget.options.editable" class="o_sidebar_add_attachment">
<t t-call="HiddenInputFile">
<t t-set="fileupload_id" t-value="widget.fileupload_id"/>
<t t-set="fileupload_id" t-value="widget.fileuploadId"/>
<t t-set="fileupload_action" t-translation="off">/web/binary/upload_attachment</t>
<t t-set="multi_upload" t-value="true"/>
<input type="hidden" name="model" t-att-value="widget.dataset and widget.dataset.model"/>
<input type="hidden" name="id" t-att-value="widget.model_id"/>
<input type="hidden" name="session_id" t-att-value="widget.session.session_id" t-if="widget.session.override_session"/>
<input type="hidden" name="model" t-att-value="widget.env and widget.env.model"/>
<input type="hidden" name="id" t-att-value="widget.env.activeIds[0]"/>
<input type="hidden" name="session_id" t-att-value="widget.getSession().session_id" t-if="widget.getSession().override_session"/>
<span>Add...</span>
</t>
</li>
@@ -61,12 +61,15 @@ var MockServer = Class.extend({
* @param {string} model a model name (that should be in this.data)
* @returns {Object} an object with 2 keys: arch and fields
*/
fieldsViewGet: function (arch, model) {
fieldsViewGet: function (arch, model, toolbar) {
var fields = $.extend(true, {}, this.data[model].fields);
var fvg = this._fieldsViewGet(arch, model, fields);
var fields_views = {};
fields_views[fvg.type] = fvg;
data_manager.processViews(fields_views, fields);
if (toolbar) {
fvg.toolbar = toolbar;
}
return fields_views[fvg.type];
},
/**
@@ -126,7 +126,7 @@ function createAsyncView(params) {
// add mock environment: mock server, session, fieldviewget, ...
var mockServer = addMockEnvironment(widget, params);
var viewInfo = mockServer.fieldsViewGet(params.arch, params.model);
var viewInfo = mockServer.fieldsViewGet(params.arch, params.model, params.toolbar);
// create the view
var viewOptions = {
+113 -27
View File
@@ -1082,6 +1082,12 @@ QUnit.module('Views', {
'</form>',
viewOptions: {sidebar: true},
res_id: 1,
mockRPC: function(route, args) {
if (args.method === 'search_read' && args.model === 'ir.attachment') {
return $.when([]);
}
return this._super.apply(this, arguments);
},
});
assert.ok(!form.sidebar.$el.hasClass('o_hidden'), 'sidebar should be visible');
@@ -1221,6 +1227,12 @@ QUnit.module('Views', {
'</form>',
res_id: 1,
viewOptions: {sidebar: true},
mockRPC: function(route, args) {
if (args.method === 'search_read' && args.model === 'ir.attachment') {
return $.when([]);
}
return this._super.apply(this, arguments);
},
});
assert.strictEqual(form.get('title'), 'first record',
@@ -1668,6 +1680,12 @@ QUnit.module('Views', {
arch: '<form string="Partners"><field name="foo"></field></form>',
res_id: 1,
viewOptions: {sidebar: true},
mockRPC: function(route, args) {
if (args.method === 'search_read' && args.model === 'ir.attachment') {
return $.when([]);
}
return this._super.apply(this, arguments);
},
});
form.$buttons.find('.o_form_button_edit').click();
@@ -2009,6 +2027,12 @@ QUnit.module('Views', {
sidebar: true,
},
res_id: 1,
mockRPC: function(route, args) {
if (args.method === 'search_read' && args.model === 'ir.attachment') {
return $.when([]);
}
return this._super.apply(this, arguments);
},
});
assert.strictEqual(form.pager.$('.o_pager_value').text(), "1", 'pager value should be 1');
@@ -2048,6 +2072,9 @@ QUnit.module('Views', {
},
res_id: 1,
mockRPC: function (route, args) {
if (args.method === 'search_read' && args.model === 'ir.attachment') {
return $.when([]);
}
assert.step(args.method);
return this._super.apply(this, arguments);
}
@@ -3598,47 +3625,106 @@ QUnit.module('Views', {
});
QUnit.test('onchanges are applied before checking if it can be saved', function (assert) {
assert.expect(4);
assert.expect(4);
this.data.partner.onchanges.foo = function (obj) {};
this.data.partner.fields.foo.required = true;
this.data.partner.onchanges.foo = function (obj) {};
this.data.partner.fields.foo.required = true;
var def = $.Deferred();
var def = $.Deferred();
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<sheet><group>' +
'<field name="foo"/>' +
'</group></sheet>' +
'</form>',
res_id: 2,
mockRPC: function (route, args) {
var result = this._super.apply(this, arguments);
assert.step(args.method);
if (args.method === 'onchange') {
return def.then(function () {
return result;
});
}
return result;
},
intercepts: {
warning: function () {
assert.step('warning');
},
},
});
form.$buttons.find('.o_form_button_edit').click();
form.$('input[name="foo"]').val('').trigger("input");
form.$buttons.find('.o_form_button_save').click();
def.resolve();
assert.verifySteps(['read', 'onchange', 'warning'])
form.destroy();
});
QUnit.test('display toolbar', function (assert) {
assert.expect(7);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
res_id: 1,
arch: '<form string="Partners">' +
'<sheet><group>' +
'<field name="foo"/>' +
'</group></sheet>' +
'<group><field name="bar"/></group>' +
'</form>',
res_id: 2,
mockRPC: function (route, args) {
var result = this._super.apply(this, arguments);
assert.step(args.method);
if (args.method === 'onchange') {
return def.then(function () {
return result;
});
}
return result;
toolbar: {
action: [{
model_name: 'partner',
name: 'Action partner',
type: 'ir.actions.server',
usage: 'ir_actions_server',
}],
print: [],
},
intercepts: {
warning: function () {
assert.step('warning');
},
viewOptions: {
sidebar: true,
},
mockRPC: function(route, args) {
if (route === '/web/action/load') {
assert.strictEqual(args.context.active_id, 1,
"the active_id shoud be 1.");
assert.deepEqual(args.context.active_ids, [1],
"the active_ids should be an array with 1 inside.");
return $.when({});
}
if (args.method === 'search_read' && args.model === 'ir.attachment') {
return $.when([]);
}
return this._super.apply(this, arguments);
},
});
form.$buttons.find('.o_form_button_edit').click();
form.$('input[name="foo"]').val('').trigger("input");
form.$buttons.find('.o_form_button_save').click();
var $dropdowns = $('.o_web_client .o_control_panel .btn-group .o_dropdown_toggler_btn');
assert.strictEqual($dropdowns.length, 3,
"there should be 3 dropdowns (print, attachment, action) in the toolbar.");
var $actions = $('.o_web_client .o_control_panel .btn-group .dropdown-menu')[2].children;
assert.strictEqual($actions.length, 3,
"there should be 3 actions");
var $customAction = $('.o_web_client .o_control_panel .btn-group .dropdown-menu li a')[2];
assert.strictEqual($customAction.text.trim(), 'Action partner',
"the custom action should have 'Action partner' as name");
testUtils.intercept(form, 'do_action', function (event) {
var context = event.data.action.context.__contexts[1];
assert.strictEqual(context.active_id, 1,
"the active_id shoud be 1.");
assert.deepEqual(context.active_ids, [1],
"the active_ids should be an array with 1 inside.");
});
$customAction.click();
def.resolve();
assert.verifySteps(['read', 'onchange', 'warning'])
form.destroy();
});
@@ -574,6 +574,9 @@ QUnit.module('Views', {
viewOptions: {sidebar: true},
arch: '<tree><field name="foo"/></tree>',
mockRPC: function (route) {
if (route === '/web/dataset/call_kw/ir.attachment/search_read') {
return $.when([]);
}
assert.step(route);
return this._super.apply(this, arguments);
},
@@ -1693,6 +1696,41 @@ QUnit.module('Views', {
list.destroy();
});
QUnit.test('display toolbar', function (assert) {
assert.expect(3);
var list = createView({
View: ListView,
model: 'event',
data: this.data,
arch: '<tree><field name="name"/></tree>',
toolbar: {
action: [{
model_name: 'event',
name: 'Action event',
type: 'ir.actions.server',
usage: 'ir_actions_server',
}],
print: [],
},
viewOptions: {
sidebar: true,
},
});
var $dropdowns = $('.o_web_client .o_control_panel .btn-group .o_dropdown_toggler_btn');
assert.strictEqual($dropdowns.length, 2,
"there should be 2 dropdowns in the toolbar.");
var $actions = $('.o_web_client .o_control_panel .btn-group .dropdown-menu')[1].children;
assert.strictEqual($actions.length, 3,
"there should be 3 actions");
var $customAction = $('.o_web_client .o_control_panel .btn-group .dropdown-menu li a')[2];
assert.strictEqual($customAction.text.trim(), 'Action event',
"the custom action should have 'Action event' as name");
list.destroy();
});
QUnit.test('edit list line after line deletion', function (assert) {
assert.expect(5);